diff --git a/.changeset/brave-owls-persist.md b/.changeset/brave-owls-persist.md deleted file mode 100644 index acb74d5..0000000 --- a/.changeset/brave-owls-persist.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@connectum/events-amqp": minor ---- - -feat: `publishRetry` — opt-in bounded publish retry for connection-class outcomes (#195) - -- New top-level `publishRetry: boolean | AmqpPublishRetryOptions`: a `publish()` failing with `AmqpConnectionError` (publish during a recovery window; in-flight confirm lost to a drop) retries in place — a short broker blip becomes a transparent delay instead of an instant rejection. Backoff mirrors the recovery formula; `maxRetries` defaults to a bounded `5`. `AmqpPublishTimeoutError` joins only via `retryOnTimeout: true`. -- The auto-retry boundary is the exported **`isAutoRetriablePublishError`** — deliberately narrower than the at-least-once republish matrix (a broker nack is republish-safe by policy but never auto-retried inline; deterministic outcomes never retry). Docs distinguish the two boundaries explicitly. -- At-least-once framing documented honestly: a retry after an in-flight confirm loss may duplicate; `x-event-id`/`messageId` stay stable across attempts (incl. `externalContract`) as the consumer-side dedup anchor. -- Shutdown-aware and drain-covered: the loop aborts promptly on `disconnect()` (interruptible backoff) and lives inside the `adapter.publish()` promise, so the bus-level `drainPublishTimeout` covers retries automatically. Under single-flight correlation, retries hold the chain (ordering preserved; head-of-line blocking documented). The publish channel is re-resolved per attempt. -- Default off — publish behavior unchanged unless opted in. diff --git a/.changeset/calm-doves-inject.md b/.changeset/calm-doves-inject.md deleted file mode 100644 index 6c5f594..0000000 --- a/.changeset/calm-doves-inject.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@connectum/events": minor ---- - -feat: named `EventAdapterFactory` type + official DI/testing guidance (#204) - -- New exported `EventAdapterFactory` (`() => EventAdapter`) — the previously inline factory shape of `createBroadcastSubscribers`' `adapter` option, now a named public type (per-adapter named types are deliberately not added). -- README gains a "Dependency Injection and Testing" section: the primary pattern is injecting an `EventAdapter` instance at the composition root (a configured test double does not fit a zero-argument factory without a wrapper); the factory is the secondary pattern for per-consumer connections (broadcast reactors). Test-double guidance: `MemoryAdapter` for the generic happy path; broker-specific failure semantics via the upcoming `@connectum/events-amqp/testing` fake (#203). -- Types + docs only — zero runtime change. diff --git a/.changeset/gentle-clouds-repeat.md b/.changeset/gentle-clouds-repeat.md deleted file mode 100644 index a36b9c2..0000000 --- a/.changeset/gentle-clouds-repeat.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@connectum/events": patch -"@connectum/events-amqp": patch ---- - -docs: recovery backoff tuning and publisher shutdown guidance - -- **events-amqp**: accurate reconnect-delay semantics in README and `AmqpRecoveryOptions` JSDoc — the amqplib v2 strategy is symmetric jitter around the exponential base (not equal-jitter), with the cap applied before jitter (hence the overshoot above `maxDelay`). Documented the exact full-jitter workaround (`jitter: 1` + halved `initialDelay`/`maxDelay` → delay uniform in `[0, intended cap]`, verified against amqplib 2.0.1) with a fragility caveat and upstream tracking links (amqp-node/amqplib#855, amqp-node/amqplib#856). -- **events**: new "Publishers and Shutdown" README section — `stop()` drains consumer handlers only; await-before-stop recipe for at-least-once producers; the stopping-gate limitation for publishes from draining handlers (#212); the planned opt-in `drainPublishTimeout` (#196). diff --git a/.changeset/lucky-panthers-relax.md b/.changeset/lucky-panthers-relax.md deleted file mode 100644 index 9263c34..0000000 --- a/.changeset/lucky-panthers-relax.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@connectum/events-amqp": minor ---- - -feat: discriminated `onLifecycle` connection-lifecycle callback (#197) - -- New `lifecycle.onLifecycle(event)` — a single discriminated union (`type`: `connected` | `disconnected` | `reconnecting` | `reconnect-failed` | `setup-failed` | `blocked` | `unblocked`) with exactly-once semantics pinned by integration tests. `connected` carries `reconnected: boolean`; `blocked`/`unblocked` surface RabbitMQ flow control (`connection.blocked`) for the first time. Scope: per-retry events of the *initial* connect loop are not yet surfaced (tracked in #198). -- Setting `onLifecycle` (like `onSetupFailed` / `failFastOnInitialSetupError`) enables the startup validation probe — one extra short-lived connection plus a topology validation pass at `connect()` (recovery enabled required) — so `setup-failed { initial: true }` is delivered for a deterministic misconfiguration at boot. -- Lifecycle callbacks must not throw: exceptions are now isolated in dispatch (a throwing callback can no longer make amqplib's recovery close a healthy connection or skip reconnect scheduling; the union and flat surfaces cannot starve each other). -- The flat callbacks (`onConnected`, `onDisconnected`, `onReconnecting`, `onReconnectFailed`, `onSetupFailed`) are now a compatibility shim over the union and are `@deprecated` since 1.3.0 (removal not before 2.0). When both are set, flat callbacks fire after `onLifecycle`. -- **Behavior fix (documented):** a socket-level connection cut fired `onDisconnected` twice — once via the raw connection `error` re-emit and once via the recovery `disconnect` event; it now fires exactly once per drop on both surfaces. Disconnect-counter metrics of existing consumers will roughly halve. A graceful server close was and remains single-fire. -- **Behavior fix (documented, `recovery: false` mode):** `disconnected` is now delivered once per connection loss on the connection `close` (with the preceding `error` kept as the cause) — including a server-forced graceful close, which previously surfaced no event at all. The adapter's own `disconnect()` and a failed-setup discard do not emit it. -- Hardening: the startup probe connection now carries an `error` listener (a broker drop during the probe window could previously crash the process via an unhandled `error` event); a stale reconnect-attempt counter no longer leaks into a later `connect()` incarnation. diff --git a/.changeset/olive-pandas-drain.md b/.changeset/olive-pandas-drain.md deleted file mode 100644 index 5644c38..0000000 --- a/.changeset/olive-pandas-drain.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@connectum/events": minor ---- - -feat: `drainPublishTimeout` — opt-in symmetric publish drain on shutdown (#196) - -- New `EventBusOptions.drainPublishTimeout`: during `stop()`, wait up to the budget for in-flight `publish()` promises (started before `stop()`) to settle, before the adapter disconnects and would fail their confirms. Bus-level (L1): zero adapter-contract changes — nats/kafka/redis/amqp get the drain for free. -- Runs concurrently with the handler drain (`drainTimeout`) — shutdown waits for the slower of the two budgets, never their sum. -- Tracked promises carry a no-op observer: a publish settling (even rejecting) after the deadline never becomes an `unhandledRejection`; the caller's own `publish()` promise is unaffected. -- Default `undefined` (and `0`/negative) — disabled: `stop()` behavior stays bit-for-bit (pinned by a regression test). -- Documented limitation: publishes issued from draining handlers are not covered — the stopping gate rejects them (relay-pattern design tracked in #212). diff --git a/.changeset/proud-eagles-march.md b/.changeset/proud-eagles-march.md deleted file mode 100644 index 67c0617..0000000 --- a/.changeset/proud-eagles-march.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@connectum/events-amqp": minor ---- - -feat: `initialConnectMaxRetries` — bounded, observable initial connect (#198) - -- New `recovery.initialConnectMaxRetries` (N retries = N+1 attempts, mirroring `maxRetries` semantics): expresses "bounded startup, unbounded steady-state", which a single `maxRetries` cannot (its counter resets on every success). Default unset — behavior unchanged. -- When set, the adapter owns the initial window with a bounded validate-connect loop — the 1.2.0 startup probe folds into it (validation IS each attempt, no extra connects). Budget exhaustion rejects `connect()` with a typed `AmqpConnectionError` after a terminal `reconnect-failed` — never a silent block. -- **Initial-window observability**: per-attempt lifecycle events are now surfaced during the bounded phase (`reconnecting { attempt, delay }`, `setup-failed { initial: true, attempt }`) — previously the initial retry loop ran inside amqplib before any wiring could attach and was silent. This closes the documented scope gap from the `onLifecycle`/`treatTopologyErrorAsFatal` releases. -- Backoff replicates amqplib's steady-state formula exactly (same knobs, same cap-before-jitter semantics; pinned by unit tests against the documented formula). `failFastOnInitialSetupError` still short-circuits deterministic topology errors immediately; the backoff sleep is interruptible by `disconnect()`. -- Upstream native support remains tracked in amqp-node/amqplib#856 (this implementation becomes a passthrough if it lands). diff --git a/.changeset/tidy-lions-search.md b/.changeset/tidy-lions-search.md deleted file mode 100644 index c6c6b02..0000000 --- a/.changeset/tidy-lions-search.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@connectum/events-amqp": minor ---- - -feat: machine-readable `object` on `AmqpTopologyError` (#202) - -- New `AmqpTopologyObject` discriminated union — `{ kind: 'exchange' | 'queue', name }` or `{ kind: 'binding', source, destination, destinationType, routingKey }` (a binding has no name of its own) — exposed as `AmqpTopologyError.object` and exported from the barrel. -- Populated structurally at every broker declare/check/consume site (`applyTopology` check and assert modes per object, subscribe-path queue declaration/bindings/check-mode verification, consume failures), so CI drift checks and observability never parse broker-reply text. `object.kind` says *what* was being declared; *why* it failed stays with the error class and `cause`. One documented exception: the config-validation error for a malformed binding declaration (neither `queue` nor `exchange` set) carries no `object` — its destination is exactly the missing piece. -- `AmqpTopologyError` constructor now takes an options-bag `{ cause?, object? }`. Construction stays bit-for-bit compatible: message-only instances install no own `cause`/`object` keys (pinned by unit tests), so spread clones and own-property log serializers see no new keys unless an object is actually supplied. diff --git a/.changeset/wise-falcons-guard.md b/.changeset/wise-falcons-guard.md deleted file mode 100644 index fdf4ef1..0000000 --- a/.changeset/wise-falcons-guard.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@connectum/events-amqp": minor ---- - -feat: `treatTopologyErrorAsFatal` — stop recovery on deterministic topology drift (#201) - -- New opt-in top-level option: when topology drift makes recovery attempts fail deterministically (a checked queue/exchange deleted, an incompatible redeclare), the adapter stops the reconnect cycle on the first such failure instead of retrying forever — it reports `setup-failed` then the terminal `reconnect-failed` lifecycle event, and subsequent publishes fail fast with `AmqpConnectionError`. -- The gate reads the AMQP reply code of the failure cause — `404` NOT_FOUND / `406` PRECONDITION_FAILED are deterministic; transient causes wrapped into `AmqpTopologyError` during a setup pass (`320` connection-forced, `541` internal-error, `405` resource-locked, mid-setup connection drops) stay in normal recovery. `instanceof AmqpTopologyError` alone is deliberately NOT the gate. The RabbitMQ cluster classic-queue outage 404 ("home node ... down or inaccessible") is explicitly excluded as transient. -- The stop is deterministic and complete: the recovery cycle's stopped flag flips synchronously inside the `connect-failed` handler, before amqplib schedules the next retry; subscription records are cleared (consumers are dead; a later `connect()` starts from a clean slate — pinned by a reconnect-after-fatal integration test). A fatal classification racing the adapter's own `disconnect()` is suppressed (no terminal events after a graceful stop began). -- A `subscribe()` parked in the recovering wrapper's waiter queue when the cycle dies now rejects with the typed `AmqpConnectionError` (was amqplib's plain `Error("Connection closed")`). -- Scope: steady-state recovery only; boot-time drift remains `failFastOnInitialSetupError`'s job. Setting both covers boot and steady state; the remaining gap (broker unreachable at `connect()` with drift surfacing before the first successful connect) is covered by neither flag until #198. Default `false` — behavior unchanged unless opted in. diff --git a/.changeset/witty-badgers-pretend.md b/.changeset/witty-badgers-pretend.md deleted file mode 100644 index f565382..0000000 --- a/.changeset/witty-badgers-pretend.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@connectum/events-amqp": minor ---- - -feat: programmable `FakeAmqpAdapter` test double via `@connectum/events-amqp/testing` (#203) - -- New subpath export `@connectum/events-amqp/testing` with `FakeAmqpAdapter` — model AMQP failure semantics in unit tests without a broker: FIFO publish outcomes (`control.nextPublish` with the real typed error classes, incl. `AmqpPublishTimeoutError` — the state-UNKNOWN outcome no real broker reproduces deterministically), deterministic connection lifecycle (`dropConnection`/`completeRecovery`/`exhaustRecovery`/`failSetup`/`block`), wildcard + competing-consumer delivery with a settlement result (`{ delivered, acked, nacked, requeued, failed }`), and a `published` record of bus-facing publishes. -- Parity by construction and by pinning: lifecycle events go through the real adapter's dispatch (canonical union ordering, deprecated flat shim, exception isolation); the state machine mirrors the real adapter (`already connected` from live/recovering/retries-exhausted states; mid-recovery `subscribe()` parks and settles with the recovery outcome; `setup-failed`/fail-fast gate on `AmqpTopologyError` like the real probe); incoming envelope headers honored and stripped like the real consumer; handler rejections swallowed like the real nack-on-error path; `instanceof` holds across the subpath boundary (tsup `splitting: true` — shared error-class chunk, pinned by a dist-level test). -- Runtime-pure: the subpath pulls neither `amqplib` nor `node:test` into the consumer graph (only `@connectum/events` + `node:crypto`). -- Documented divergences: no timing simulation (recovery advances via explicit control calls); settlement is recorded, not broker-driven (re-deliver with `attempt + 1` to model redelivery); no wire-level envelope on `published`; report-and-proceed on a non-fail-fast startup setup failure. diff --git a/packages/auth/CHANGELOG.md b/packages/auth/CHANGELOG.md index 1e14665..f1fac50 100644 --- a/packages/auth/CHANGELOG.md +++ b/packages/auth/CHANGELOG.md @@ -1,5 +1,12 @@ # @connectum/auth +## 1.3.0 + +### Patch Changes + +- Updated dependencies []: + - @connectum/core@1.3.0 + ## 1.2.0 ### Patch Changes diff --git a/packages/auth/package.json b/packages/auth/package.json index aaee816..868b30a 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@connectum/auth", - "version": "1.2.0", + "version": "1.3.0", "description": "Authentication and authorization interceptors for Connectum", "type": "module", "main": "./dist/index.js", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 061bdc6..600f678 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,7 @@ # @connectum/cli +## 1.3.0 + ## 1.2.0 ## 1.1.0 diff --git a/packages/cli/package.json b/packages/cli/package.json index d909360..d5e1c99 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@connectum/cli", - "version": "1.2.0", + "version": "1.3.0", "description": "CLI tools for Connectum framework: proto sync, service discovery", "type": "module", "main": "./dist/index.js", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 0e4e52b..84988ad 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,7 @@ # @connectum/core +## 1.3.0 + ## 1.2.0 ## 1.1.0 diff --git a/packages/core/package.json b/packages/core/package.json index 96739d4..49cf6c2 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@connectum/core", - "version": "1.2.0", + "version": "1.3.0", "description": "Main server factory with protocol plugin system for Connectum", "type": "module", "main": "./dist/index.js", diff --git a/packages/events-amqp/CHANGELOG.md b/packages/events-amqp/CHANGELOG.md index e1b425c..ac0ea4c 100644 --- a/packages/events-amqp/CHANGELOG.md +++ b/packages/events-amqp/CHANGELOG.md @@ -1,5 +1,63 @@ # @connectum/events-amqp +## 1.3.0 + +### Minor Changes + +- [#222](https://github.com/Connectum-Framework/connectum/pull/222) [`3a76225`](https://github.com/Connectum-Framework/connectum/commit/3a76225e8ec826bd859d4a0acbdc7dff0d3718bb) Thanks [@intech](https://github.com/intech)! - feat: `publishRetry` — opt-in bounded publish retry for connection-class outcomes ([#195](https://github.com/Connectum-Framework/connectum/issues/195)) + + - New top-level `publishRetry: boolean | AmqpPublishRetryOptions`: a `publish()` failing with `AmqpConnectionError` (publish during a recovery window; in-flight confirm lost to a drop) retries in place — a short broker blip becomes a transparent delay instead of an instant rejection. Backoff mirrors the recovery formula; `maxRetries` defaults to a bounded `5`. `AmqpPublishTimeoutError` joins only via `retryOnTimeout: true`. + - The auto-retry boundary is the exported **`isAutoRetriablePublishError`** — deliberately narrower than the at-least-once republish matrix (a broker nack is republish-safe by policy but never auto-retried inline; deterministic outcomes never retry). Docs distinguish the two boundaries explicitly. + - At-least-once framing documented honestly: a retry after an in-flight confirm loss may duplicate; `x-event-id`/`messageId` stay stable across attempts (incl. `externalContract`) as the consumer-side dedup anchor. + - Shutdown-aware and drain-covered: the loop aborts promptly on `disconnect()` (interruptible backoff) and lives inside the `adapter.publish()` promise, so the bus-level `drainPublishTimeout` covers retries automatically. Under single-flight correlation, retries hold the chain (ordering preserved; head-of-line blocking documented). The publish channel is re-resolved per attempt. + - Default off — publish behavior unchanged unless opted in. + +- [#216](https://github.com/Connectum-Framework/connectum/pull/216) [`82f2c29`](https://github.com/Connectum-Framework/connectum/commit/82f2c299d35e1261e463b71504cbad7da044db02) Thanks [@intech](https://github.com/intech)! - feat: discriminated `onLifecycle` connection-lifecycle callback ([#197](https://github.com/Connectum-Framework/connectum/issues/197)) + + - New `lifecycle.onLifecycle(event)` — a single discriminated union (`type`: `connected` | `disconnected` | `reconnecting` | `reconnect-failed` | `setup-failed` | `blocked` | `unblocked`) with exactly-once semantics pinned by integration tests. `connected` carries `reconnected: boolean`; `blocked`/`unblocked` surface RabbitMQ flow control (`connection.blocked`) for the first time. Scope: per-retry events of the _initial_ connect loop are not yet surfaced (tracked in [#198](https://github.com/Connectum-Framework/connectum/issues/198)). + - Setting `onLifecycle` (like `onSetupFailed` / `failFastOnInitialSetupError`) enables the startup validation probe — one extra short-lived connection plus a topology validation pass at `connect()` (recovery enabled required) — so `setup-failed { initial: true }` is delivered for a deterministic misconfiguration at boot. + - Lifecycle callbacks must not throw: exceptions are now isolated in dispatch (a throwing callback can no longer make amqplib's recovery close a healthy connection or skip reconnect scheduling; the union and flat surfaces cannot starve each other). + - The flat callbacks (`onConnected`, `onDisconnected`, `onReconnecting`, `onReconnectFailed`, `onSetupFailed`) are now a compatibility shim over the union and are `@deprecated` since 1.3.0 (removal not before 2.0). When both are set, flat callbacks fire after `onLifecycle`. + - **Behavior fix (documented):** a socket-level connection cut fired `onDisconnected` twice — once via the raw connection `error` re-emit and once via the recovery `disconnect` event; it now fires exactly once per drop on both surfaces. Disconnect-counter metrics of existing consumers will roughly halve. A graceful server close was and remains single-fire. + - **Behavior fix (documented, `recovery: false` mode):** `disconnected` is now delivered once per connection loss on the connection `close` (with the preceding `error` kept as the cause) — including a server-forced graceful close, which previously surfaced no event at all. The adapter's own `disconnect()` and a failed-setup discard do not emit it. + - Hardening: the startup probe connection now carries an `error` listener (a broker drop during the probe window could previously crash the process via an unhandled `error` event); a stale reconnect-attempt counter no longer leaks into a later `connect()` incarnation. + +- [#219](https://github.com/Connectum-Framework/connectum/pull/219) [`d21e3c8`](https://github.com/Connectum-Framework/connectum/commit/d21e3c83f9d799a055cbff3e9594875c85a80869) Thanks [@intech](https://github.com/intech)! - feat: `initialConnectMaxRetries` — bounded, observable initial connect ([#198](https://github.com/Connectum-Framework/connectum/issues/198)) + + - New `recovery.initialConnectMaxRetries` (N retries = N+1 attempts, mirroring `maxRetries` semantics): expresses "bounded startup, unbounded steady-state", which a single `maxRetries` cannot (its counter resets on every success). Default unset — behavior unchanged. + - When set, the adapter owns the initial window with a bounded validate-connect loop — the 1.2.0 startup probe folds into it (validation IS each attempt, no extra connects). Budget exhaustion rejects `connect()` with a typed `AmqpConnectionError` after a terminal `reconnect-failed` — never a silent block. + - **Initial-window observability**: per-attempt lifecycle events are now surfaced during the bounded phase (`reconnecting { attempt, delay }`, `setup-failed { initial: true, attempt }`) — previously the initial retry loop ran inside amqplib before any wiring could attach and was silent. This closes the documented scope gap from the `onLifecycle`/`treatTopologyErrorAsFatal` releases. + - Backoff replicates amqplib's steady-state formula exactly (same knobs, same cap-before-jitter semantics; pinned by unit tests against the documented formula). `failFastOnInitialSetupError` still short-circuits deterministic topology errors immediately; the backoff sleep is interruptible by `disconnect()`. + - Upstream native support remains tracked in amqp-node/amqplib#856 (this implementation becomes a passthrough if it lands). + +- [#217](https://github.com/Connectum-Framework/connectum/pull/217) [`f0e9040`](https://github.com/Connectum-Framework/connectum/commit/f0e90409ab15023aa2fad6ea6f42e30669e6a365) Thanks [@intech](https://github.com/intech)! - feat: machine-readable `object` on `AmqpTopologyError` ([#202](https://github.com/Connectum-Framework/connectum/issues/202)) + + - New `AmqpTopologyObject` discriminated union — `{ kind: 'exchange' | 'queue', name }` or `{ kind: 'binding', source, destination, destinationType, routingKey }` (a binding has no name of its own) — exposed as `AmqpTopologyError.object` and exported from the barrel. + - Populated structurally at every broker declare/check/consume site (`applyTopology` check and assert modes per object, subscribe-path queue declaration/bindings/check-mode verification, consume failures), so CI drift checks and observability never parse broker-reply text. `object.kind` says _what_ was being declared; _why_ it failed stays with the error class and `cause`. One documented exception: the config-validation error for a malformed binding declaration (neither `queue` nor `exchange` set) carries no `object` — its destination is exactly the missing piece. + - `AmqpTopologyError` constructor now takes an options-bag `{ cause?, object? }`. Construction stays bit-for-bit compatible: message-only instances install no own `cause`/`object` keys (pinned by unit tests), so spread clones and own-property log serializers see no new keys unless an object is actually supplied. + +- [#218](https://github.com/Connectum-Framework/connectum/pull/218) [`24e73f6`](https://github.com/Connectum-Framework/connectum/commit/24e73f6533e317e9932011e0f867d2e256861f52) Thanks [@intech](https://github.com/intech)! - feat: `treatTopologyErrorAsFatal` — stop recovery on deterministic topology drift ([#201](https://github.com/Connectum-Framework/connectum/issues/201)) + + - New opt-in top-level option: when topology drift makes recovery attempts fail deterministically (a checked queue/exchange deleted, an incompatible redeclare), the adapter stops the reconnect cycle on the first such failure instead of retrying forever — it reports `setup-failed` then the terminal `reconnect-failed` lifecycle event, and subsequent publishes fail fast with `AmqpConnectionError`. + - The gate reads the AMQP reply code of the failure cause — `404` NOT_FOUND / `406` PRECONDITION_FAILED are deterministic; transient causes wrapped into `AmqpTopologyError` during a setup pass (`320` connection-forced, `541` internal-error, `405` resource-locked, mid-setup connection drops) stay in normal recovery. `instanceof AmqpTopologyError` alone is deliberately NOT the gate. The RabbitMQ cluster classic-queue outage 404 ("home node ... down or inaccessible") is explicitly excluded as transient. + - The stop is deterministic and complete: the recovery cycle's stopped flag flips synchronously inside the `connect-failed` handler, before amqplib schedules the next retry; subscription records are cleared (consumers are dead; a later `connect()` starts from a clean slate — pinned by a reconnect-after-fatal integration test). A fatal classification racing the adapter's own `disconnect()` is suppressed (no terminal events after a graceful stop began). + - A `subscribe()` parked in the recovering wrapper's waiter queue when the cycle dies now rejects with the typed `AmqpConnectionError` (was amqplib's plain `Error("Connection closed")`). + - Scope: steady-state recovery only; boot-time drift remains `failFastOnInitialSetupError`'s job. Setting both covers boot and steady state; the remaining gap (broker unreachable at `connect()` with drift surfacing before the first successful connect) is covered by neither flag until [#198](https://github.com/Connectum-Framework/connectum/issues/198). Default `false` — behavior unchanged unless opted in. + +- [#224](https://github.com/Connectum-Framework/connectum/pull/224) [`ddc9ec7`](https://github.com/Connectum-Framework/connectum/commit/ddc9ec7c19fab079573acbb51372b957a8eeb15b) Thanks [@intech](https://github.com/intech)! - feat: programmable `FakeAmqpAdapter` test double via `@connectum/events-amqp/testing` ([#203](https://github.com/Connectum-Framework/connectum/issues/203)) + + - New subpath export `@connectum/events-amqp/testing` with `FakeAmqpAdapter` — model AMQP failure semantics in unit tests without a broker: FIFO publish outcomes (`control.nextPublish` with the real typed error classes, incl. `AmqpPublishTimeoutError` — the state-UNKNOWN outcome no real broker reproduces deterministically), deterministic connection lifecycle (`dropConnection`/`completeRecovery`/`exhaustRecovery`/`failSetup`/`block`), wildcard + competing-consumer delivery with a settlement result (`{ delivered, acked, nacked, requeued, failed }`), and a `published` record of bus-facing publishes. + - Parity by construction and by pinning: lifecycle events go through the real adapter's dispatch (canonical union ordering, deprecated flat shim, exception isolation); the state machine mirrors the real adapter (`already connected` from live/recovering/retries-exhausted states; mid-recovery `subscribe()` parks and settles with the recovery outcome; `setup-failed`/fail-fast gate on `AmqpTopologyError` like the real probe); incoming envelope headers honored and stripped like the real consumer; handler rejections swallowed like the real nack-on-error path; `instanceof` holds across the subpath boundary (tsup `splitting: true` — shared error-class chunk, pinned by a dist-level test). + - Runtime-pure: the subpath pulls neither `amqplib` nor `node:test` into the consumer graph (only `@connectum/events` + `node:crypto`). + - Documented divergences: no timing simulation (recovery advances via explicit control calls); settlement is recorded, not broker-driven (re-deliver with `attempt + 1` to model redelivery); no wire-level envelope on `published`; report-and-proceed on a non-fail-fast startup setup failure. + +### Patch Changes + +- [#214](https://github.com/Connectum-Framework/connectum/pull/214) [`e2b613b`](https://github.com/Connectum-Framework/connectum/commit/e2b613bad73024bf5b00c41717c063aac3665859) Thanks [@intech](https://github.com/intech)! - docs: recovery backoff tuning and publisher shutdown guidance + + - **events-amqp**: accurate reconnect-delay semantics in README and `AmqpRecoveryOptions` JSDoc — the amqplib v2 strategy is symmetric jitter around the exponential base (not equal-jitter), with the cap applied before jitter (hence the overshoot above `maxDelay`). Documented the exact full-jitter workaround (`jitter: 1` + halved `initialDelay`/`maxDelay` → delay uniform in `[0, intended cap]`, verified against amqplib 2.0.1) with a fragility caveat and upstream tracking links (amqp-node/amqplib#855, amqp-node/amqplib#856). + - **events**: new "Publishers and Shutdown" README section — `stop()` drains consumer handlers only; await-before-stop recipe for at-least-once producers; the stopping-gate limitation for publishes from draining handlers ([#212](https://github.com/Connectum-Framework/connectum/issues/212)); the planned opt-in `drainPublishTimeout` ([#196](https://github.com/Connectum-Framework/connectum/issues/196)). + ## 1.2.0 ### Minor Changes diff --git a/packages/events-amqp/package.json b/packages/events-amqp/package.json index d2c51f3..7199157 100644 --- a/packages/events-amqp/package.json +++ b/packages/events-amqp/package.json @@ -1,6 +1,6 @@ { "name": "@connectum/events-amqp", - "version": "1.2.0", + "version": "1.3.0", "description": "AMQP/RabbitMQ adapter for @connectum/events", "type": "module", "main": "./dist/index.js", diff --git a/packages/events-kafka/CHANGELOG.md b/packages/events-kafka/CHANGELOG.md index ee3bb6d..22228ca 100644 --- a/packages/events-kafka/CHANGELOG.md +++ b/packages/events-kafka/CHANGELOG.md @@ -1,5 +1,7 @@ # @connectum/events-kafka +## 1.3.0 + ## 1.2.0 ## 1.1.0 diff --git a/packages/events-kafka/package.json b/packages/events-kafka/package.json index 77cfbd5..75521ce 100644 --- a/packages/events-kafka/package.json +++ b/packages/events-kafka/package.json @@ -1,6 +1,6 @@ { "name": "@connectum/events-kafka", - "version": "1.2.0", + "version": "1.3.0", "description": "Kafka/Redpanda adapter for @connectum/events", "type": "module", "main": "./dist/index.js", diff --git a/packages/events-nats/CHANGELOG.md b/packages/events-nats/CHANGELOG.md index 55dbae0..8bca454 100644 --- a/packages/events-nats/CHANGELOG.md +++ b/packages/events-nats/CHANGELOG.md @@ -1,5 +1,7 @@ # @connectum/events-nats +## 1.3.0 + ## 1.2.0 ## 1.1.0 diff --git a/packages/events-nats/package.json b/packages/events-nats/package.json index 3aad278..5c82d2d 100644 --- a/packages/events-nats/package.json +++ b/packages/events-nats/package.json @@ -1,6 +1,6 @@ { "name": "@connectum/events-nats", - "version": "1.2.0", + "version": "1.3.0", "description": "NATS JetStream adapter for @connectum/events", "type": "module", "main": "./dist/index.js", diff --git a/packages/events-redis/CHANGELOG.md b/packages/events-redis/CHANGELOG.md index 29d6b80..2732b85 100644 --- a/packages/events-redis/CHANGELOG.md +++ b/packages/events-redis/CHANGELOG.md @@ -1,5 +1,7 @@ # @connectum/events-redis +## 1.3.0 + ## 1.2.0 ## 1.1.0 diff --git a/packages/events-redis/package.json b/packages/events-redis/package.json index 7754b74..c894eaf 100644 --- a/packages/events-redis/package.json +++ b/packages/events-redis/package.json @@ -1,6 +1,6 @@ { "name": "@connectum/events-redis", - "version": "1.2.0", + "version": "1.3.0", "description": "Redis Streams adapter for @connectum/events", "type": "module", "main": "./dist/index.js", diff --git a/packages/events/CHANGELOG.md b/packages/events/CHANGELOG.md index 27acfef..80a3196 100644 --- a/packages/events/CHANGELOG.md +++ b/packages/events/CHANGELOG.md @@ -1,5 +1,33 @@ # @connectum/events +## 1.3.0 + +### Minor Changes + +- [#223](https://github.com/Connectum-Framework/connectum/pull/223) [`774ef46`](https://github.com/Connectum-Framework/connectum/commit/774ef46e743ce528b554115553cddf19233bf52b) Thanks [@intech](https://github.com/intech)! - feat: named `EventAdapterFactory` type + official DI/testing guidance ([#204](https://github.com/Connectum-Framework/connectum/issues/204)) + + - New exported `EventAdapterFactory` (`() => EventAdapter`) — the previously inline factory shape of `createBroadcastSubscribers`' `adapter` option, now a named public type (per-adapter named types are deliberately not added). + - README gains a "Dependency Injection and Testing" section: the primary pattern is injecting an `EventAdapter` instance at the composition root (a configured test double does not fit a zero-argument factory without a wrapper); the factory is the secondary pattern for per-consumer connections (broadcast reactors). Test-double guidance: `MemoryAdapter` for the generic happy path; broker-specific failure semantics via the upcoming `@connectum/events-amqp/testing` fake ([#203](https://github.com/Connectum-Framework/connectum/issues/203)). + - Types + docs only — zero runtime change. + +- [#220](https://github.com/Connectum-Framework/connectum/pull/220) [`8962afa`](https://github.com/Connectum-Framework/connectum/commit/8962afa7d50cc67c1a385e0441a8ff00378871f6) Thanks [@intech](https://github.com/intech)! - feat: `drainPublishTimeout` — opt-in symmetric publish drain on shutdown ([#196](https://github.com/Connectum-Framework/connectum/issues/196)) + + - New `EventBusOptions.drainPublishTimeout`: during `stop()`, wait up to the budget for in-flight `publish()` promises (started before `stop()`) to settle, before the adapter disconnects and would fail their confirms. Bus-level (L1): zero adapter-contract changes — nats/kafka/redis/amqp get the drain for free. + - Runs concurrently with the handler drain (`drainTimeout`) — shutdown waits for the slower of the two budgets, never their sum. + - Tracked promises carry a no-op observer: a publish settling (even rejecting) after the deadline never becomes an `unhandledRejection`; the caller's own `publish()` promise is unaffected. + - Default `undefined` (and `0`/negative) — disabled: `stop()` behavior stays bit-for-bit (pinned by a regression test). + - Documented limitation: publishes issued from draining handlers are not covered — the stopping gate rejects them (relay-pattern design tracked in [#212](https://github.com/Connectum-Framework/connectum/issues/212)). + +### Patch Changes + +- [#214](https://github.com/Connectum-Framework/connectum/pull/214) [`e2b613b`](https://github.com/Connectum-Framework/connectum/commit/e2b613bad73024bf5b00c41717c063aac3665859) Thanks [@intech](https://github.com/intech)! - docs: recovery backoff tuning and publisher shutdown guidance + + - **events-amqp**: accurate reconnect-delay semantics in README and `AmqpRecoveryOptions` JSDoc — the amqplib v2 strategy is symmetric jitter around the exponential base (not equal-jitter), with the cap applied before jitter (hence the overshoot above `maxDelay`). Documented the exact full-jitter workaround (`jitter: 1` + halved `initialDelay`/`maxDelay` → delay uniform in `[0, intended cap]`, verified against amqplib 2.0.1) with a fragility caveat and upstream tracking links (amqp-node/amqplib#855, amqp-node/amqplib#856). + - **events**: new "Publishers and Shutdown" README section — `stop()` drains consumer handlers only; await-before-stop recipe for at-least-once producers; the stopping-gate limitation for publishes from draining handlers ([#212](https://github.com/Connectum-Framework/connectum/issues/212)); the planned opt-in `drainPublishTimeout` ([#196](https://github.com/Connectum-Framework/connectum/issues/196)). + +- Updated dependencies []: + - @connectum/core@1.3.0 + ## 1.2.0 ### Patch Changes diff --git a/packages/events/package.json b/packages/events/package.json index 2d23903..b015c91 100644 --- a/packages/events/package.json +++ b/packages/events/package.json @@ -1,6 +1,6 @@ { "name": "@connectum/events", - "version": "1.2.0", + "version": "1.3.0", "description": "Universal event adapter layer for Connectum: proto-first pub/sub with pluggable broker adapters", "type": "module", "main": "./dist/index.js", diff --git a/packages/healthcheck/CHANGELOG.md b/packages/healthcheck/CHANGELOG.md index 7a0d730..f4b9a0d 100644 --- a/packages/healthcheck/CHANGELOG.md +++ b/packages/healthcheck/CHANGELOG.md @@ -1,5 +1,7 @@ # @connectum/healthcheck +## 1.3.0 + ## 1.2.0 ## 1.1.0 diff --git a/packages/healthcheck/package.json b/packages/healthcheck/package.json index 2876d6a..4cea78b 100644 --- a/packages/healthcheck/package.json +++ b/packages/healthcheck/package.json @@ -1,6 +1,6 @@ { "name": "@connectum/healthcheck", - "version": "1.2.0", + "version": "1.3.0", "description": "Healthcheck protocol for Connectum: gRPC Health Check + HTTP endpoints", "type": "module", "main": "./dist/index.js", diff --git a/packages/interceptors/CHANGELOG.md b/packages/interceptors/CHANGELOG.md index b52a7c0..4559b71 100644 --- a/packages/interceptors/CHANGELOG.md +++ b/packages/interceptors/CHANGELOG.md @@ -1,5 +1,12 @@ # @connectum/interceptors +## 1.3.0 + +### Patch Changes + +- Updated dependencies []: + - @connectum/core@1.3.0 + ## 1.2.0 ### Patch Changes diff --git a/packages/interceptors/package.json b/packages/interceptors/package.json index c4af437..95b2a79 100644 --- a/packages/interceptors/package.json +++ b/packages/interceptors/package.json @@ -1,6 +1,6 @@ { "name": "@connectum/interceptors", - "version": "1.2.0", + "version": "1.3.0", "description": "ConnectRPC interceptors for Connectum", "type": "module", "main": "./dist/index.js", diff --git a/packages/otel/CHANGELOG.md b/packages/otel/CHANGELOG.md index 1ea9bc0..568e4e2 100644 --- a/packages/otel/CHANGELOG.md +++ b/packages/otel/CHANGELOG.md @@ -1,5 +1,7 @@ # @connectum/otel +## 1.3.0 + ## 1.2.0 ## 1.1.0 diff --git a/packages/otel/package.json b/packages/otel/package.json index 9eba4b9..2474859 100644 --- a/packages/otel/package.json +++ b/packages/otel/package.json @@ -1,6 +1,6 @@ { "name": "@connectum/otel", - "version": "1.2.0", + "version": "1.3.0", "description": "OpenTelemetry instrumentation for Connectum", "type": "module", "main": "./dist/index.js", diff --git a/packages/protoc-gen-catalog/CHANGELOG.md b/packages/protoc-gen-catalog/CHANGELOG.md index 7ab3c03..0c48ef1 100644 --- a/packages/protoc-gen-catalog/CHANGELOG.md +++ b/packages/protoc-gen-catalog/CHANGELOG.md @@ -1,5 +1,7 @@ # @connectum/protoc-gen-catalog +## 1.3.0 + ## 1.2.0 ## 1.1.0 diff --git a/packages/protoc-gen-catalog/package.json b/packages/protoc-gen-catalog/package.json index d538e8e..9ea2290 100644 --- a/packages/protoc-gen-catalog/package.json +++ b/packages/protoc-gen-catalog/package.json @@ -1,6 +1,6 @@ { "name": "@connectum/protoc-gen-catalog", - "version": "1.2.0", + "version": "1.3.0", "description": "Buf/protoc plugin that generates a Connectum service catalog (ctx.call / ctx.stream typing) from proto files.", "type": "module", "main": "./dist/plugin.js", diff --git a/packages/reflection/CHANGELOG.md b/packages/reflection/CHANGELOG.md index 41c37dc..1581382 100644 --- a/packages/reflection/CHANGELOG.md +++ b/packages/reflection/CHANGELOG.md @@ -1,5 +1,7 @@ # @connectum/reflection +## 1.3.0 + ## 1.2.0 ## 1.1.0 diff --git a/packages/reflection/package.json b/packages/reflection/package.json index a05898d..bc48ff0 100644 --- a/packages/reflection/package.json +++ b/packages/reflection/package.json @@ -1,6 +1,6 @@ { "name": "@connectum/reflection", - "version": "1.2.0", + "version": "1.3.0", "description": "gRPC Server Reflection protocol for Connectum (v1 + v1alpha)", "type": "module", "main": "./dist/index.js", diff --git a/packages/test-fixtures/CHANGELOG.md b/packages/test-fixtures/CHANGELOG.md index 283d791..0f0d3ff 100644 --- a/packages/test-fixtures/CHANGELOG.md +++ b/packages/test-fixtures/CHANGELOG.md @@ -1,5 +1,7 @@ # @connectum/test-fixtures +## 1.3.0 + ## 1.2.0 ## 1.1.0 diff --git a/packages/test-fixtures/package.json b/packages/test-fixtures/package.json index 9d606f7..f624ba8 100644 --- a/packages/test-fixtures/package.json +++ b/packages/test-fixtures/package.json @@ -1,6 +1,6 @@ { "name": "@connectum/test-fixtures", - "version": "1.2.0", + "version": "1.3.0", "description": "Mock factories, assertion helpers, and protobuf descriptor fixtures for Connectum tests", "type": "module", "main": "./dist/index.js", diff --git a/packages/testing/CHANGELOG.md b/packages/testing/CHANGELOG.md index 3cec778..bca597a 100644 --- a/packages/testing/CHANGELOG.md +++ b/packages/testing/CHANGELOG.md @@ -1,5 +1,13 @@ # @connectum/testing +## 1.3.0 + +### Patch Changes + +- Updated dependencies []: + - @connectum/core@1.3.0 + - @connectum/test-fixtures@1.3.0 + ## 1.2.0 ### Patch Changes diff --git a/packages/testing/package.json b/packages/testing/package.json index 26da1ae..d28571f 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -1,6 +1,6 @@ { "name": "@connectum/testing", - "version": "1.2.0", + "version": "1.3.0", "description": "Testing utilities for Connectum", "private": false, "type": "module",