Skip to content

chore: version packages#215

Open
connectum-release-bot[bot] wants to merge 1 commit into
mainfrom
changeset-release/main
Open

chore: version packages#215
connectum-release-bot[bot] wants to merge 1 commit into
mainfrom
changeset-release/main

Conversation

@connectum-release-bot

@connectum-release-bot connectum-release-bot Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Version Packages PR generated by the Changesets release workflow. Merging it publishes all packages to npm (OIDC + provenance) and creates the draft GitHub Release. Notes below are produced by scripts/build-release-notes.mjs (highlights-first, deduplicated); full per-package detail lives in each package's CHANGELOG.md.

Highlights

  • Reliable AMQP publishing — opt-in publishRetry on @connectum/events-amqp auto-retries connection-class publish failures (never nacks/timeouts) with bounded backoff and honest at-least-once semantics (#222), and the new drainPublishTimeout on @connectum/events gives in-flight publishes a bounded drain window during stop() (#220).
  • Deterministic AMQP failure policyinitialConnectMaxRetries bounds the first connect (#219), and treatTopologyErrorAsFatal stops infinite recovery on deterministic topology drift (404/406) at runtime, mirroring the 1.2.0 startup fail-fast (#218).
  • Observable AMQP lifecycle — the new discriminated onLifecycle callback delivers the full connection-lifecycle event union in guaranteed order (#216), and AmqpTopologyError now carries a machine-readable .object identifying the failing exchange/queue/binding (#217).
  • Broker-free AMQP testing — the new @connectum/events-amqp/testing subpath ships a programmable FakeAmqpAdapter: FIFO publish outcomes with the real typed errors, a deterministic recovery control surface, and delivery with settlement counts (#224).
  • Typed adapter DI@connectum/events exports the EventAdapterFactory type for per-consumer adapter wiring and test seams (#223).

Repo-wide changes

These changeset entries appear identically across multiple packages and are listed once.

  • #214 e2b613b Thanks @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 (recovery: allow a custom delay strategy (calculateDelay option) amqp-node/amqplib#855, recovery: separate retry budget for the initial connect 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).

    Affects: @connectum/events, @connectum/events-amqp (2 packages).

Package changes

@connectum/events@1.3.0

Minor

  • #223 774ef46 Thanks @intech! - 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.
  • #220 8962afa Thanks @intech! - 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).

@connectum/events-amqp@1.3.0

Minor

  • #222 3a76225 Thanks @intech! - 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.
  • #216 82f2c29 Thanks @intech! - 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.
  • #219 d21e3c8 Thanks @intech! - 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 recovery: separate retry budget for the initial connect amqp-node/amqplib#856 (this implementation becomes a passthrough if it lands).
  • #217 f0e9040 Thanks @intech! - 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.
  • #218 24e73f6 Thanks @intech! - 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.
  • #224 ddc9ec7 Thanks @intech! - 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.

@github-actions github-actions Bot added type:chore Maintenance: refactoring, dependencies, CI/CD pkg:core @connectum/core (Layer 0: Server Foundation) pkg:interceptors @connectum/interceptors (Layer 1) pkg:healthcheck @connectum/healthcheck (Layer 1) pkg:reflection @connectum/reflection (Layer 1) pkg:otel @connectum/otel (Layer 2: OpenTelemetry) pkg:testing @connectum/testing (Layer 2) pkg:cli @connectum/cli (Layer 3) pkg:protoc-gen-catalog @connectum/protoc-gen-catalog (buf plugin: service catalog codegen) labels Jul 4, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jul 4, 2026

Copy link
Copy Markdown

Open in StackBlitz

@connectum/auth

npm i https://pkg.pr.new/@connectum/auth@215

@connectum/cli

npm i https://pkg.pr.new/@connectum/cli@215

@connectum/core

npm i https://pkg.pr.new/@connectum/core@215

@connectum/events

npm i https://pkg.pr.new/@connectum/events@215

@connectum/events-amqp

npm i https://pkg.pr.new/@connectum/events-amqp@215

@connectum/events-kafka

npm i https://pkg.pr.new/@connectum/events-kafka@215

@connectum/events-nats

npm i https://pkg.pr.new/@connectum/events-nats@215

@connectum/events-redis

npm i https://pkg.pr.new/@connectum/events-redis@215

@connectum/healthcheck

npm i https://pkg.pr.new/@connectum/healthcheck@215

@connectum/interceptors

npm i https://pkg.pr.new/@connectum/interceptors@215

@connectum/otel

npm i https://pkg.pr.new/@connectum/otel@215

@connectum/protoc-gen-catalog

npm i https://pkg.pr.new/@connectum/protoc-gen-catalog@215

@connectum/reflection

npm i https://pkg.pr.new/@connectum/reflection@215

@connectum/test-fixtures

npm i https://pkg.pr.new/@connectum/test-fixtures@215

@connectum/testing

npm i https://pkg.pr.new/@connectum/testing@215

commit: e8967cd

@connectum-release-bot
connectum-release-bot Bot force-pushed the changeset-release/main branch 9 times, most recently from 8fd8dc3 to 222c024 Compare July 5, 2026 22:11
@connectum-release-bot
connectum-release-bot Bot force-pushed the changeset-release/main branch from 222c024 to e8967cd Compare July 6, 2026 03:02
@intech intech self-assigned this Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pkg:cli @connectum/cli (Layer 3) pkg:core @connectum/core (Layer 0: Server Foundation) pkg:healthcheck @connectum/healthcheck (Layer 1) pkg:interceptors @connectum/interceptors (Layer 1) pkg:otel @connectum/otel (Layer 2: OpenTelemetry) pkg:protoc-gen-catalog @connectum/protoc-gen-catalog (buf plugin: service catalog codegen) pkg:reflection @connectum/reflection (Layer 1) pkg:testing @connectum/testing (Layer 2) type:chore Maintenance: refactoring, dependencies, CI/CD

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant