Skip to content

release: ruststream 0.5#102

Draft
powersemmi wants to merge 8 commits into
mainfrom
v0.5
Draft

release: ruststream 0.5#102
powersemmi wants to merge 8 commits into
mainfrom
v0.5

Conversation

@powersemmi

@powersemmi powersemmi commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Description

Release integration PR: merges the v0.5 integration branch into main to cut ruststream 0.5. This is the umbrella PR for the 0.5 compile-time-context set; its diff fills in as the feature stack lands on v0.5.

0.5 is a breaking release (minor bump pre-1.0):

Merge order (must land on v0.5 before this is ready)

Type of change

  • Breaking change (removing or renaming public API, changing a signature, tightening bounds, or
    raising the MSRV - a minor version bump pre-1.0)
  • This change requires a documentation update

Checklist

  • My code follows the project's style guidelines (just check passes per feature PR)
  • I have performed a self-review of my own code
  • I have made the necessary changes to the documentation (rustdoc and the docs site)
  • My changes generate no new warnings (clippy runs with -D warnings)
  • I have added tests that validate my fix or new feature
  • New and existing tests pass locally (just test)
  • Public items carry a compiling # Examples doctest, and user-facing changes are reflected in
    examples/ where applicable

Draft until the feature stack is merged into v0.5 and the release is cut. Mark ready, then merge v0.5 -> main and publish.

Workspace version 0.4.0 -> 0.5.0 for the 0.5 minor release (breaking:
typed context #93, typed state #94, publisher keys #95). The
ruststream-macros in-workspace dependency range follows: 0.4 -> 0.5.
@powersemmi powersemmi self-assigned this Jun 23, 2026
@powersemmi powersemmi added documentation Improvements or additions to documentation enhancement New feature or request labels Jun 23, 2026
@powersemmi powersemmi added this to the v0.5 milestone Jun 23, 2026
powersemmi and others added 7 commits June 23, 2026 19:51
* feat: add Field/FieldMut compile-time selector traits

The shared foundation for typed per-message context (#93) and typed
application state (#94): zero-sized selector keys resolved by
monomorphization to a direct field read, with no hashing, boxing, or
downcasting. FieldMut adds the write side for middleware scratch values.

* wip(#93): add BuildContext, drop IncomingMessage::extensions

Foundation for the typed per-delivery context: BuildContext<M> builds a
handler context value from the broker message (blanket impl for () = the
zero-field default). IncomingMessage loses the erased extensions()
type-map; brokers expose fields via BuildContext instead. Tree is red
until the Context<'a, C> rewrite and dispatch/publish cascade land.

* wip(#93): Context<'a, C> core - typed cx, context()/set() by key

Context gains the per-delivery context type param C (default ()), holding
a typed cx: C instead of the erased Extensions type-map. get()/insert()
replaced by context(KEY)/set(KEY, v) via Field/FieldMut. publisher() no
longer threads extensions. Callers (dispatch, typed, batch, publishing,
memory, examples) and the After builder + tests still pending.

* wip(#93): thread C through mount path, drop Extensions, green

- HandlerExt<M,C>, scope.handle/push_handle thread the context type so
  hand-written typed-context handlers mount (macro handlers stay C=()).
- Remove the now-unused Extensions type-map type entirely.
- Rewrite context unit tests + the integration test for the typed context
  (broker field by key; middleware scratch via FieldMut; state isolation).
- Fix doc links; example drops the dead extensions snippet.

Lib + all tests + doctests + clippy(-D warnings) + fmt + doc(-D warnings)
+ --no-default-features all green.

* docs(#93): typed per-delivery context in the Context guide

Rewrite the per-delivery section of the Context guide and the lifespan
note from the removed Extensions type-map to the typed per-delivery
context (ctx.context(KEY) / ctx.set(KEY, v), Field / BuildContext).

* feat(#93): thread C through scope mount path and the global blanket stack

scope.subscribe/push_subscribe now carry the context type like
scope.handle, and BlanketLayer::apply<M, C, H> threads it through the
app-global stack (Identity/Stack/Tracing/Metrics + the example layers),
so a hand-written typed-context handler mounts via b.handle/b.subscribe
and is still wrapped by the global stack. The router-builder route path
stays C=() (fast-follow).

* feat(#93): macro and include typed-context support

The #[subscriber] macro detects the context type from the handler's
ctx: &mut Context<'_, C> parameter and threads it through SubscriberDef
(new type Context) and the generated Handler impl. Typed (the decoder),
mount_subscriber, push_subscribe_workers, and the include/include_on
methods carry D::Context with a BuildContext bound, so a macro handler
reads broker fields by key end-to-end. PublishingDef/batch forms and the
router-builder include path stay C=() (documented sub-fast-follow).

#93 complete: green across lib, tests (21 suites), doctests (50),
clippy -D warnings, fmt, doc -D warnings, --no-default-features.
* feat: add Field/FieldMut compile-time selector traits

The shared foundation for typed per-message context (#93) and typed
application state (#94): zero-sized selector keys resolved by
monomorphization to a direct field read, with no hashing, boxing, or
downcasting. FieldMut adds the write side for middleware scratch values.

* wip(#93): add BuildContext, drop IncomingMessage::extensions

Foundation for the typed per-delivery context: BuildContext<M> builds a
handler context value from the broker message (blanket impl for () = the
zero-field default). IncomingMessage loses the erased extensions()
type-map; brokers expose fields via BuildContext instead. Tree is red
until the Context<'a, C> rewrite and dispatch/publish cascade land.

* wip(#93): thread C through mount path, drop Extensions, green

- HandlerExt<M,C>, scope.handle/push_handle thread the context type so
  hand-written typed-context handlers mount (macro handlers stay C=()).
- Remove the now-unused Extensions type-map type entirely.
- Rewrite context unit tests + the integration test for the typed context
  (broker field by key; middleware scratch via FieldMut; state isolation).
- Fix doc links; example drops the dead extensions snippet.

Lib + all tests + doctests + clippy(-D warnings) + fmt + doc(-D warnings)
+ --no-default-features all green.

* feat(#94): typed application state

Replace the `Any`-keyed `State` type-map with a single typed application
state value threaded through the runtime at compile time. The state type
`S` is produced by `on_startup` (the value the hook returns becomes the
state and fixes the app's state type) and read with `ctx.state() -> &S`,
with no lookup, `Option`, or downcast.

- `Context<'a, C, S>`, `Handler<M, C, S>`, and the middleware stack
  (`BlanketLayer::apply<M, C, S, H>`, `Layer` handlers, `Typed`) thread
  `S` parallel to the per-delivery context `C`.
- `RustStream<L, St>` is a producer-model builder: `on_startup` transitions
  `RustStream<L, St> -> RustStream<L, St2>`. `run`, the CLI runner, and
  `build_spec` are now `St`-generic.
- A `#[subscriber]` handler that names the state as the third `Context`
  generic is bound to it; one that names none is generic over the state and
  mounts on any app. Mounting only succeeds when the declared state matches
  the app's, checked at compile time.
- `PublishingDef` carries a `type State` so a publishing reply handler can
  read typed state too. Router registrations thread `St` through
  `RouterDef<B, St>`/`MountRoute`; metadata collection moves to a
  state-independent `RouterHandlers` trait.
- Batch handlers keep a unit-state context (a documented follow-up).

Examples, integration tests, and the Context/Lifespan guides are migrated
to the typed-state API.
* feat: add Field/FieldMut compile-time selector traits

The shared foundation for typed per-message context (#93) and typed
application state (#94): zero-sized selector keys resolved by
monomorphization to a direct field read, with no hashing, boxing, or
downcasting. FieldMut adds the write side for middleware scratch values.

* wip(#93): add BuildContext, drop IncomingMessage::extensions

Foundation for the typed per-delivery context: BuildContext<M> builds a
handler context value from the broker message (blanket impl for () = the
zero-field default). IncomingMessage loses the erased extensions()
type-map; brokers expose fields via BuildContext instead. Tree is red
until the Context<'a, C> rewrite and dispatch/publish cascade land.

* wip(#93): thread C through mount path, drop Extensions, green

- HandlerExt<M,C>, scope.handle/push_handle thread the context type so
  hand-written typed-context handlers mount (macro handlers stay C=()).
- Remove the now-unused Extensions type-map type entirely.
- Rewrite context unit tests + the integration test for the typed context
  (broker field by key; middleware scratch via FieldMut; state isolation).
- Fix doc links; example drops the dead extensions snippet.

Lib + all tests + doctests + clippy(-D warnings) + fmt + doc(-D warnings)
+ --no-default-features all green.

* feat: add Field/FieldMut compile-time selector traits

The shared foundation for typed per-message context (#93) and typed
application state (#94): zero-sized selector keys resolved by
monomorphization to a direct field read, with no hashing, boxing, or
downcasting. FieldMut adds the write side for middleware scratch values.

* wip(#93): add BuildContext, drop IncomingMessage::extensions

Foundation for the typed per-delivery context: BuildContext<M> builds a
handler context value from the broker message (blanket impl for () = the
zero-field default). IncomingMessage loses the erased extensions()
type-map; brokers expose fields via BuildContext instead. Tree is red
until the Context<'a, C> rewrite and dispatch/publish cascade land.

* wip(#93): thread C through mount path, drop Extensions, green

- HandlerExt<M,C>, scope.handle/push_handle thread the context type so
  hand-written typed-context handlers mount (macro handlers stay C=()).
- Remove the now-unused Extensions type-map type entirely.
- Rewrite context unit tests + the integration test for the typed context
  (broker field by key; middleware scratch via FieldMut; state isolation).
- Fix doc links; example drops the dead extensions snippet.

Lib + all tests + doctests + clippy(-D warnings) + fmt + doc(-D warnings)
+ --no-default-features all green.

* feat(#95): publisher keys instead of string names

Replace the string-keyed named-publisher registry with compile-time
publisher keys. A key is a distinct zero-sized type, so a misspelled key
is a compile error (an undeclared identifier) where a misspelled string
name was only a runtime `None`, and the registry slot is the key type's
`TypeId` rather than a hashed string on every publish.

- New `PublisherKey` trait and `publisher_key!` declarative macro to
  declare keys; `Publishers` becomes `HashMap<TypeId, Arc<dyn ErasedPublisher>>`.
- `RustStream::publisher(KEY, p)`, `BrokerScope::publisher(KEY)`, and
  `ctx.publisher(KEY)` take a key in place of a `&str`; the string forms
  are removed.

The publishing example, the cross-broker bridge tests, and the Publishing
guide are migrated to keys.
- `run`/`run_until` and the CLI runner gain `L: Send, St: Send + Sync`
  bounds, and `dispatch` gains `St: Send + Sync`, so the futures the
  runtime spawns are provably `Send` (clippy::future_not_send, denied via
  `-D warnings`).
- Shorten the first doc paragraph of `RouterHandlers` and `RouterSink`
  (clippy::too_long_first_doc_paragraph).
- Drop redundant `std::convert::` qualifications and a
  `Some(..).unwrap_or_default()` in the migrated tests.
- Replace the inline `Field`/per-delivery-context fence in the Context
  guide with a compiled `examples/context_field.rs` snippet, per the
  repo's compiled-snippet rule.
bug_003 - extend typed application state to batch handlers, removing the
per-message/batch asymmetry:
- `SliceHandler<T, S>`, `BatchHandler<M, S>`, and `TypedBatch`/`decode_batch`
  thread the state `S`; `BatchDef::Handler` is relaxed like `SubscriberDef`
  so a stateless batch handler is generic over the state and a state-reading
  one is pinned to its declared type.
- `BatchPublishingDef` gains a `type State` (mirroring `PublishingDef`);
  `spawn_batch_dispatch`/`run_batch` thread the real `Arc<St>` instead of
  discarding it, and the macro's `expand_batch`/`expand_batch_publishing`
  lift the state type out of the handler signature.
- New `batch_handler_reads_typed_state` integration test.

bug_004 - correct two `ReplyPublisher`/`Transactional` doc comments that
still described the removed per-delivery `extensions` commit-token flow.

bug_005 - the Context guide's methods table showed `publisher(name)`; it
takes a compile-time `PublisherKey`, so it now reads `publisher(KEY)`.

bug_008 - the lifespan/context guides claimed a stateless handler "mounts
on any app"; clarify that a `publish(..)` handler instead pins its state to
`()` when none is named.
Clone the recorded values out of the lock so the guard is not held across
`run.await` (clippy::await_holding_lock / significant_drop_tightening on
newer toolchains).
…gistry) (#104)

* refactor: publishers live in typed state, drop the named-publisher registry

With typed application state (#94), a shared publisher is just a typed field
of the state struct, reached with `ctx.state()` - typed end to end, with no
erased runtime lookup. That makes the separate named-publisher registry added
in #95 redundant, so it is removed:

- delete `PublisherKey`, the `publisher_key!` macro, `RustStream::publisher`,
  `BrokerScope::publisher`, `Context::publisher`, and `ScopedPublisher`;
- drop the `Publishers` registry and the per-delivery publish pipeline from
  `Delivery` (only `ctx.publisher` fed them); `ErasedPublisher` stays for the
  `retry_after` fallback;
- the reply path (`publish(..)`) and its app-wide `publish_layer` pipeline are
  unchanged.

To publish from a handler now, put the publisher in the state struct and call
its own API (`ctx.state().egress.publish(..)`). The publishing example, the
cross-broker test (captures the publisher directly), and the Publishing /
Context / Lifespan / Subscribers guides are migrated; the test that asserted
`ctx.publisher` ran the pipeline is removed with the feature.

* refactor: parameterize publishing defs over state, drop the pinned State assoc type

A `publish(..)` handler used to carry `type State` on its def, which forced a
stateless publishing handler to pin `State = ()` and so write
`_ctx: &mut Context<'_, (), AppState>` just to mount on a stateful app - the
asymmetry ultrareview flagged (bug_008). Plain subscribers never had this: a
handler that ignores the state is generic over it.

Split the metadata-only `PublishingDef`/`BatchPublishingDef` from a new
`PublishingCall<S>`/`BatchPublishingCall<S>` that runs the body over state `S`.
A handler that reads the state implements the call trait only for its declared
`S` (mounts on a matching app); one that ignores it is generic over `S` (mounts
on any app), mirroring `Handler<M, C, S>`. The macro now emits the def with
metadata only plus a generic-or-concrete call impl, so a stateless publishing
handler omits the `Context` parameter entirely.

Migrate the examples (respond/validate/confirm/settle drop their unused `_ctx`)
and the context guide's stale "publish handlers pin their state" note.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant