@furystack/cross-node-bus 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +49 -0
  3. package/esm/cross-node-bus-telemetry.d.ts +74 -0
  4. package/esm/cross-node-bus-telemetry.d.ts.map +1 -0
  5. package/esm/cross-node-bus-telemetry.js +28 -0
  6. package/esm/cross-node-bus-telemetry.js.map +1 -0
  7. package/esm/cross-node-bus-telemetry.spec.d.ts +2 -0
  8. package/esm/cross-node-bus-telemetry.spec.d.ts.map +1 -0
  9. package/esm/cross-node-bus-telemetry.spec.js +115 -0
  10. package/esm/cross-node-bus-telemetry.spec.js.map +1 -0
  11. package/esm/cross-node-bus.d.ts +78 -0
  12. package/esm/cross-node-bus.d.ts.map +1 -0
  13. package/esm/cross-node-bus.js +18 -0
  14. package/esm/cross-node-bus.js.map +1 -0
  15. package/esm/cross-node-bus.spec.d.ts +2 -0
  16. package/esm/cross-node-bus.spec.d.ts.map +1 -0
  17. package/esm/cross-node-bus.spec.js +123 -0
  18. package/esm/cross-node-bus.spec.js.map +1 -0
  19. package/esm/define-in-process-cross-node-bus.d.ts +26 -0
  20. package/esm/define-in-process-cross-node-bus.d.ts.map +1 -0
  21. package/esm/define-in-process-cross-node-bus.js +27 -0
  22. package/esm/define-in-process-cross-node-bus.js.map +1 -0
  23. package/esm/errors.d.ts +21 -0
  24. package/esm/errors.d.ts.map +1 -0
  25. package/esm/errors.js +30 -0
  26. package/esm/errors.js.map +1 -0
  27. package/esm/errors.spec.d.ts +2 -0
  28. package/esm/errors.spec.d.ts.map +1 -0
  29. package/esm/errors.spec.js +30 -0
  30. package/esm/errors.spec.js.map +1 -0
  31. package/esm/in-process-cross-node-bus.d.ts +58 -0
  32. package/esm/in-process-cross-node-bus.d.ts.map +1 -0
  33. package/esm/in-process-cross-node-bus.js +196 -0
  34. package/esm/in-process-cross-node-bus.js.map +1 -0
  35. package/esm/in-process-cross-node-bus.spec.d.ts +2 -0
  36. package/esm/in-process-cross-node-bus.spec.d.ts.map +1 -0
  37. package/esm/in-process-cross-node-bus.spec.js +737 -0
  38. package/esm/in-process-cross-node-bus.spec.js.map +1 -0
  39. package/esm/index.d.ts +8 -0
  40. package/esm/index.d.ts.map +1 -0
  41. package/esm/index.js +7 -0
  42. package/esm/index.js.map +1 -0
  43. package/esm/memory-broker.d.ts +74 -0
  44. package/esm/memory-broker.d.ts.map +1 -0
  45. package/esm/memory-broker.js +156 -0
  46. package/esm/memory-broker.js.map +1 -0
  47. package/esm/memory-broker.spec.d.ts +2 -0
  48. package/esm/memory-broker.spec.d.ts.map +1 -0
  49. package/esm/memory-broker.spec.js +497 -0
  50. package/esm/memory-broker.spec.js.map +1 -0
  51. package/esm/testing/create-in-process-bus-network.d.ts +49 -0
  52. package/esm/testing/create-in-process-bus-network.d.ts.map +1 -0
  53. package/esm/testing/create-in-process-bus-network.js +54 -0
  54. package/esm/testing/create-in-process-bus-network.js.map +1 -0
  55. package/esm/testing/create-in-process-bus-network.spec.d.ts +2 -0
  56. package/esm/testing/create-in-process-bus-network.spec.d.ts.map +1 -0
  57. package/esm/testing/create-in-process-bus-network.spec.js +142 -0
  58. package/esm/testing/create-in-process-bus-network.spec.js.map +1 -0
  59. package/esm/testing/index.d.ts +2 -0
  60. package/esm/testing/index.d.ts.map +1 -0
  61. package/esm/testing/index.js +2 -0
  62. package/esm/testing/index.js.map +1 -0
  63. package/esm/types.d.ts +35 -0
  64. package/esm/types.d.ts.map +1 -0
  65. package/esm/types.js +2 -0
  66. package/esm/types.js.map +1 -0
  67. package/package.json +56 -0
  68. package/src/cross-node-bus-telemetry.spec.ts +44 -0
  69. package/src/cross-node-bus-telemetry.ts +69 -0
  70. package/src/cross-node-bus.spec.ts +41 -0
  71. package/src/cross-node-bus.ts +92 -0
  72. package/src/define-in-process-cross-node-bus.ts +38 -0
  73. package/src/errors.spec.ts +32 -0
  74. package/src/errors.ts +38 -0
  75. package/src/in-process-cross-node-bus.spec.ts +428 -0
  76. package/src/in-process-cross-node-bus.ts +248 -0
  77. package/src/index.ts +7 -0
  78. package/src/memory-broker.spec.ts +282 -0
  79. package/src/memory-broker.ts +199 -0
  80. package/src/testing/create-in-process-bus-network.spec.ts +73 -0
  81. package/src/testing/create-in-process-bus-network.ts +87 -0
  82. package/src/testing/index.ts +1 -0
  83. package/src/types.ts +35 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,78 @@
1
+ # Changelog
2
+
3
+ ## [1.0.0] - 2026-05-21
4
+
5
+ ### 💥 Breaking Changes
6
+
7
+ ### Initial 1.0.0 release
8
+
9
+ First public release of `@furystack/cross-node-bus` — a transport-agnostic publish/subscribe primitive for FuryStack apps that scale beyond a single process. There is no migration path from a previous version because none exists; this section is required by the major-release contract.
10
+
11
+ ### ✨ Features
12
+
13
+ ### `CrossNodeBus` — shared, typed, multi-node event bus
14
+
15
+ A pluggable pub/sub primitive that lets every subsystem coordinate across nodes via the same shared facade. Subsystems and apps build typed wrappers (e.g. `IdentityEventBus`, `EntityChangeBus`) on top of one bus instance per injector tree.
16
+
17
+ ```typescript
18
+ import { CrossNodeBus } from '@furystack/cross-node-bus'
19
+
20
+ const bus = injector.get(CrossNodeBus)
21
+ using sub = bus.subscribe('my-topic', ({ payload, originId }) => {
22
+ console.log('received', payload, 'from', originId)
23
+ })
24
+ await bus.publish('my-topic', { hello: 'world' })
25
+ ```
26
+
27
+ Self-delivery is on by default: a publisher receives its own messages. Subscribers that only care about sibling traffic use `subscribeRemoteOnly(topic, handler)`.
28
+
29
+ ### In-process default adapter
30
+
31
+ `CrossNodeBus` resolves to an in-process implementation by default — single-node deployments work without any configuration. The default adapter:
32
+
33
+ - Assigns numeric monotonic seq tokens per topic.
34
+ - Retains the last 1 000 messages per topic for replay.
35
+ - Implements `replay`, `oldestSeq`, and `compareSeq` so facades can do delta sync against the in-process bus the same way they would against Redis.
36
+
37
+ Multi-node deployments override the binding with a transport adapter (see `@furystack/redis-cross-node-bus`).
38
+
39
+ ### Capability flags + fail-loud registration
40
+
41
+ Every adapter exposes a `capabilities` descriptor (`persistent`, `replay`, `assignsSequence`). Facades assert the flags they need at registration time so misconfigured deployments fail loudly rather than serving stale data.
42
+
43
+ ```typescript
44
+ const bus = injector.get(CrossNodeBus)
45
+ if (!bus.capabilities.replay) {
46
+ throw new Error('This facade requires replay-capable transport')
47
+ }
48
+ ```
49
+
50
+ ### `replay()` + `compareSeq()` + `oldestSeq()` for delta sync
51
+
52
+ Adapters that retain a window of past messages let consumers reconnect with a `lastSeq` and replay just the gap. `compareSeq` lets facades order seq tokens without leaking the adapter-specific encoding (in-process: integer counters; Redis Streams: `<ms>-<n>`). When the requested seq predates the retained window, `replay()` throws `ReplayWindowExceededError` so facades fall back to a snapshot.
53
+
54
+ ### `subscribeForeign()` for explicit cross-service eavesdrop
55
+
56
+ Cross-service traffic is opt-in and greppable: a service that wants to observe another service's topic prefix calls `subscribeForeign(prefix, topic, handler)`. Adapters that lack the underlying capability throw at registration time. There is no implicit cross-service fan-out.
57
+
58
+ ### Test helper: `createInProcessBusNetwork`
59
+
60
+ Exposed under `@furystack/cross-node-bus/testing`. Mints N in-process `CrossNodeBus` instances backed by a single shared `MemoryBroker`, so multi-node behaviour can be unit-tested without spinning up a real broker:
61
+
62
+ ```typescript
63
+ import { createInProcessBusNetwork } from '@furystack/cross-node-bus/testing'
64
+
65
+ using network = createInProcessBusNetwork({ count: 3 })
66
+ const [a, b, c] = network.buses
67
+ // a.publish(...) is observed by b and c
68
+ ```
69
+
70
+ ### Telemetry hooks
71
+
72
+ `CrossNodeBusTelemetry` emits `onCrossNodePublished`, `onCrossNodeReceived`, `onCrossNodeError`, and `onCrossNodeWindowEvicted` events with `topic`, `originId`, `byteLength` / `lagMs` / error-and-phase / eviction context — wire it into existing logging without touching adapter code. The shared sink is exposed via `CrossNodeBusTelemetryToken` so adapter factories (in-process or transport-specific) inject the same hub.
73
+
74
+ `onCrossNodeWindowEvicted` only fires for adapters that own their replay buffer (the in-process default today). Network-broker adapters that delegate trimming to the broker — Redis Streams' `MAXLEN`, NATS JetStream's max-bytes — cannot observe individual evictions on the client side; consumers needing that signal should read it from the broker's native metrics.
75
+
76
+ ### ⬆️ Dependencies
77
+
78
+ - Bumped `@types/node` to `^25.9.1` and `vitest` to `^4.1.7`. No source changes — dev-tooling bump only.
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # @furystack/cross-node-bus
2
+
3
+ Transport-agnostic publish/subscribe primitive for FuryStack. Provides the
4
+ `CrossNodeBus` interface, a default in-process adapter, and a testing
5
+ harness for multi-instance scenarios. Concrete cross-process adapters
6
+ (Redis Streams, NATS, …) ship in their own packages.
7
+
8
+ See `docs/internal/cross-node-bus-spike.md` for the full design.
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install @furystack/cross-node-bus
14
+ # or
15
+ yarn add @furystack/cross-node-bus
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ The default factory resolves an `InProcessCrossNodeBus`. Single-node
21
+ deployments work without further configuration. Multi-node deployments
22
+ bind a transport adapter such as `@furystack/redis-cross-node-bus`.
23
+
24
+ ```ts
25
+ import { createInjector } from '@furystack/inject'
26
+ import { CrossNodeBus } from '@furystack/cross-node-bus'
27
+
28
+ await using injector = createInjector()
29
+ const bus = injector.get(CrossNodeBus)
30
+
31
+ using sub = bus.subscribe('my-topic', (message) => {
32
+ console.log(message.originId, message.payload)
33
+ })
34
+
35
+ await bus.publish('my-topic', { hello: 'world' })
36
+ ```
37
+
38
+ ### Testing harness
39
+
40
+ The `@furystack/cross-node-bus/testing` subpath ships a multi-instance
41
+ in-process harness so facade authors can write multi-node integration
42
+ tests without spinning up a broker.
43
+
44
+ ```ts
45
+ import { createInProcessBusNetwork } from '@furystack/cross-node-bus/testing'
46
+
47
+ using network = createInProcessBusNetwork({ count: 2 })
48
+ const [a, b] = network.buses
49
+ ```
@@ -0,0 +1,74 @@
1
+ import { type Token } from '@furystack/inject';
2
+ import { EventHub, type ListenerErrorPayload } from '@furystack/utils';
3
+ /**
4
+ * Phase of the bus pipeline an `onCrossNodeError` event refers to. `serialize`
5
+ * is reserved for future adapters that fail when JSON-encoding payloads;
6
+ * the in-process default never produces it.
7
+ */
8
+ export type CrossNodeBusErrorPhase = 'publish' | 'subscribe' | 'subscribeForeign' | 'replay' | 'serialize';
9
+ /**
10
+ * Union of telemetry signals emitted by every {@link CrossNodeBus} adapter.
11
+ * Each adapter forwards into the same hub so subscribers can observe the
12
+ * whole bus surface from one place — independent of which transport is
13
+ * bound.
14
+ */
15
+ export type CrossNodeBusTelemetryEvents = {
16
+ /** Fired after a message has been accepted by the transport. */
17
+ onCrossNodePublished: {
18
+ topic: string;
19
+ originId: string;
20
+ byteLength: number;
21
+ };
22
+ /**
23
+ * Fired once per message arriving at the bus, before local fan-out.
24
+ * `lagMs = Date.now() - Date.parse(message.emittedAt)`; clock skew can
25
+ * produce negative values, which adapters report verbatim.
26
+ */
27
+ onCrossNodeReceived: {
28
+ topic: string;
29
+ originId: string;
30
+ lagMs: number;
31
+ };
32
+ onCrossNodeError: {
33
+ topic: string;
34
+ error: unknown;
35
+ phase: CrossNodeBusErrorPhase;
36
+ };
37
+ /**
38
+ * Fired when an adapter that owns its replay buffer drops a retained
39
+ * message to honor the configured replay window. Operators alert on the
40
+ * trend so the window can be tuned before reconnecting clients start
41
+ * hitting `ReplayWindowExceededError`.
42
+ *
43
+ * Only adapters that own the buffer emit this signal — today that is
44
+ * {@link InProcessCrossNodeBus} via {@link MemoryBroker}. Network-broker
45
+ * adapters that delegate trimming to the broker (Redis Streams' `MAXLEN`,
46
+ * NATS JetStream's max-bytes) cannot observe individual evictions on the
47
+ * client side; consumers needing that signal should read it from the
48
+ * broker's native metrics (e.g. `redis_streams_length` from the Prom
49
+ * exporter).
50
+ */
51
+ onCrossNodeWindowEvicted: {
52
+ topic: string;
53
+ evictedSeq: string;
54
+ retainedCount: number;
55
+ };
56
+ onListenerError: ListenerErrorPayload;
57
+ };
58
+ /**
59
+ * Application-facing telemetry surface for the cross-node bus. Subscribers
60
+ * use the standard {@link EventHub} `addListener` / `subscribe` API.
61
+ */
62
+ export declare class CrossNodeBusTelemetry extends EventHub<CrossNodeBusTelemetryEvents> {
63
+ }
64
+ /**
65
+ * DI token for the shared {@link CrossNodeBusTelemetry} instance.
66
+ *
67
+ * Singleton because the bus token itself is singleton — co-locating both at
68
+ * the root injector keeps every override factory (including transport
69
+ * adapters) free to inject telemetry without lifetime-compatibility
70
+ * gymnastics. Each test still gets isolation by minting its own root
71
+ * injector with `createInjector()`.
72
+ */
73
+ export declare const CrossNodeBusTelemetryToken: Token<CrossNodeBusTelemetry, 'singleton'>;
74
+ //# sourceMappingURL=cross-node-bus-telemetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-node-bus-telemetry.d.ts","sourceRoot":"","sources":["../src/cross-node-bus-telemetry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAC7D,OAAO,EAAE,QAAQ,EAAE,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAEtE;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,WAAW,GAAG,kBAAkB,GAAG,QAAQ,GAAG,WAAW,CAAA;AAE1G;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,gEAAgE;IAChE,oBAAoB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7E;;;;OAIG;IACH,mBAAmB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IACvE,gBAAgB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,sBAAsB,CAAA;KAAE,CAAA;IAClF;;;;;;;;;;;;;OAaG;IACH,wBAAwB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAA;IACtF,eAAe,EAAE,oBAAoB,CAAA;CACtC,CAAA;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,QAAQ,CAAC,2BAA2B,CAAC;CAAG;AAEnF;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,EAAE,KAAK,CAAC,qBAAqB,EAAE,WAAW,CAS/E,CAAA"}
@@ -0,0 +1,28 @@
1
+ import { defineService } from '@furystack/inject';
2
+ import { EventHub } from '@furystack/utils';
3
+ /**
4
+ * Application-facing telemetry surface for the cross-node bus. Subscribers
5
+ * use the standard {@link EventHub} `addListener` / `subscribe` API.
6
+ */
7
+ export class CrossNodeBusTelemetry extends EventHub {
8
+ }
9
+ /**
10
+ * DI token for the shared {@link CrossNodeBusTelemetry} instance.
11
+ *
12
+ * Singleton because the bus token itself is singleton — co-locating both at
13
+ * the root injector keeps every override factory (including transport
14
+ * adapters) free to inject telemetry without lifetime-compatibility
15
+ * gymnastics. Each test still gets isolation by minting its own root
16
+ * injector with `createInjector()`.
17
+ */
18
+ export const CrossNodeBusTelemetryToken = defineService({
19
+ name: 'furystack/cross-node-bus/CrossNodeBusTelemetry',
20
+ lifetime: 'singleton',
21
+ factory: ({ onDispose }) => {
22
+ const telemetry = new CrossNodeBusTelemetry();
23
+ // eslint-disable-next-line furystack/prefer-using-wrapper -- delegated to onDispose
24
+ onDispose(() => telemetry[Symbol.dispose]());
25
+ return telemetry;
26
+ },
27
+ });
28
+ //# sourceMappingURL=cross-node-bus-telemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-node-bus-telemetry.js","sourceRoot":"","sources":["../src/cross-node-bus-telemetry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAc,MAAM,mBAAmB,CAAA;AAC7D,OAAO,EAAE,QAAQ,EAA6B,MAAM,kBAAkB,CAAA;AA2CtE;;;GAGG;AACH,MAAM,OAAO,qBAAsB,SAAQ,QAAqC;CAAG;AAEnF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAA8C,aAAa,CAAC;IACjG,IAAI,EAAE,gDAAgD;IACtD,QAAQ,EAAE,WAAW;IACrB,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE;QACzB,MAAM,SAAS,GAAG,IAAI,qBAAqB,EAAE,CAAA;QAC7C,oFAAoF;QACpF,SAAS,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QAC5C,OAAO,SAAS,CAAA;IAClB,CAAC;CACF,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cross-node-bus-telemetry.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-node-bus-telemetry.spec.d.ts","sourceRoot":"","sources":["../src/cross-node-bus-telemetry.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,115 @@
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
53
+ import { createInjector } from '@furystack/inject';
54
+ import { describe, expect, it, vi } from 'vitest';
55
+ import { CrossNodeBusTelemetry, CrossNodeBusTelemetryToken } from './cross-node-bus-telemetry.js';
56
+ describe('CrossNodeBusTelemetryToken', () => {
57
+ it('resolves a CrossNodeBusTelemetry instance', async () => {
58
+ const env_1 = { stack: [], error: void 0, hasError: false };
59
+ try {
60
+ const injector = __addDisposableResource(env_1, createInjector(), true);
61
+ const telemetry = injector.get(CrossNodeBusTelemetryToken);
62
+ expect(telemetry).toBeInstanceOf(CrossNodeBusTelemetry);
63
+ }
64
+ catch (e_1) {
65
+ env_1.error = e_1;
66
+ env_1.hasError = true;
67
+ }
68
+ finally {
69
+ const result_1 = __disposeResources(env_1);
70
+ if (result_1)
71
+ await result_1;
72
+ }
73
+ });
74
+ it('disposes the telemetry instance with the injector scope', async () => {
75
+ const injector = createInjector();
76
+ const telemetry = injector.get(CrossNodeBusTelemetryToken);
77
+ const handler = vi.fn();
78
+ telemetry.addListener('onCrossNodePublished', handler);
79
+ await injector[Symbol.asyncDispose]();
80
+ telemetry.emit('onCrossNodePublished', { topic: 'x', originId: 'y', byteLength: 0 });
81
+ expect(handler).not.toHaveBeenCalled();
82
+ });
83
+ it('shares the singleton instance across child scopes', async () => {
84
+ const env_2 = { stack: [], error: void 0, hasError: false };
85
+ try {
86
+ const injector = __addDisposableResource(env_2, createInjector(), true);
87
+ const scopeA = __addDisposableResource(env_2, injector.createScope({ owner: 'a' }), true);
88
+ const scopeB = __addDisposableResource(env_2, injector.createScope({ owner: 'b' }), true);
89
+ const telA = scopeA.get(CrossNodeBusTelemetryToken);
90
+ const telB = scopeB.get(CrossNodeBusTelemetryToken);
91
+ expect(telA).toBe(telB);
92
+ }
93
+ catch (e_2) {
94
+ env_2.error = e_2;
95
+ env_2.hasError = true;
96
+ }
97
+ finally {
98
+ const result_2 = __disposeResources(env_2);
99
+ if (result_2)
100
+ await result_2;
101
+ }
102
+ });
103
+ it('issues independent instances per root injector', () => {
104
+ const a = createInjector();
105
+ const b = createInjector();
106
+ try {
107
+ expect(a.get(CrossNodeBusTelemetryToken)).not.toBe(b.get(CrossNodeBusTelemetryToken));
108
+ }
109
+ finally {
110
+ void a[Symbol.asyncDispose]();
111
+ void b[Symbol.asyncDispose]();
112
+ }
113
+ });
114
+ });
115
+ //# sourceMappingURL=cross-node-bus-telemetry.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-node-bus-telemetry.spec.js","sourceRoot":"","sources":["../src/cross-node-bus-telemetry.spec.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AACjD,OAAO,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAA;AAEjG,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC1C,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;;;YACzD,MAAY,QAAQ,kCAAG,cAAc,EAAE,OAAA,CAAA;YACvC,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAA;YAC1D,MAAM,CAAC,SAAS,CAAC,CAAC,cAAc,CAAC,qBAAqB,CAAC,CAAA;;;;;;;;;;;KACxD,CAAC,CAAA;IAEF,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACvE,MAAM,QAAQ,GAAG,cAAc,EAAE,CAAA;QACjC,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAA;QAC1D,MAAM,OAAO,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;QACvB,SAAS,CAAC,WAAW,CAAC,sBAAsB,EAAE,OAAO,CAAC,CAAA;QACtD,MAAM,QAAQ,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAA;QAErC,SAAS,CAAC,IAAI,CAAC,sBAAsB,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAA;QACpF,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAA;IACxC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;;;YACjE,MAAY,QAAQ,kCAAG,cAAc,EAAE,OAAA,CAAA;YACvC,MAAY,MAAM,kCAAG,QAAQ,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,OAAA,CAAA;YACzD,MAAY,MAAM,kCAAG,QAAQ,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,OAAA,CAAA;YAEzD,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAA;YACnD,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAA;YAEnD,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;;;;;;;;;;;KACxB,CAAC,CAAA;IAEF,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,CAAC,GAAG,cAAc,EAAE,CAAA;QAC1B,MAAM,CAAC,GAAG,cAAc,EAAE,CAAA;QAC1B,IAAI,CAAC;YACH,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAA;QACvF,CAAC;gBAAS,CAAC;YACT,KAAK,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAA;YAC7B,KAAK,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAA;QAC/B,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,78 @@
1
+ import { type Token } from '@furystack/inject';
2
+ import type { BusMessage, CrossNodeBusCapabilities } from './types.js';
3
+ /**
4
+ * Transport-agnostic publish/subscribe primitive. Implementations talk to a
5
+ * concrete broker (in-process map, Redis Streams, …); facades layer typed
6
+ * event contracts on top.
7
+ *
8
+ * Self-delivery is on by default — a publisher receives its own messages.
9
+ * Subscribers that need a local-vs-remote distinction either filter on
10
+ * `message.originId === bus.nodeId` or use
11
+ * {@link CrossNodeBus.subscribeRemoteOnly}.
12
+ */
13
+ export interface CrossNodeBus extends Disposable {
14
+ /** Stable, unique id of this node. Included in every published message. */
15
+ readonly nodeId: string;
16
+ /** Static description of what this adapter can do. */
17
+ readonly capabilities: CrossNodeBusCapabilities;
18
+ /**
19
+ * Publishes `payload` on `topic`. Resolves once the message has been
20
+ * accepted by the underlying transport (not when it has been delivered to
21
+ * all subscribers).
22
+ */
23
+ publish(topic: string, payload: unknown): Promise<void>;
24
+ /**
25
+ * Subscribes to every message published on `topic`, including ones
26
+ * originating from this node.
27
+ */
28
+ subscribe(topic: string, handler: (message: BusMessage) => void): Disposable;
29
+ /**
30
+ * Convenience for the common "I only care about messages from other nodes"
31
+ * pattern. Equivalent to {@link CrossNodeBus.subscribe} + filter on
32
+ * `message.originId !== bus.nodeId`.
33
+ */
34
+ subscribeRemoteOnly(topic: string, handler: (message: BusMessage) => void): Disposable;
35
+ /**
36
+ * Subscribe to a topic owned by another `topicPrefix`. Explicit, greppable
37
+ * cross-service eavesdrop. Adapters that lack the underlying capability
38
+ * throw at registration time.
39
+ */
40
+ subscribeForeign(prefix: string, topic: string, handler: (message: BusMessage) => void): Disposable;
41
+ /**
42
+ * Replay messages on `topic` whose `seq` is greater than `fromSeq`. Throws
43
+ * synchronously when {@link CrossNodeBusCapabilities.replay} is `false` or
44
+ * when `fromSeq` is older than the adapter's retained window — facades
45
+ * fall back to a full snapshot in the latter case.
46
+ */
47
+ replay(topic: string, fromSeq: string): AsyncIterable<BusMessage>;
48
+ /**
49
+ * Compares two adapter-issued seq tokens from the **same topic**. Returns a
50
+ * negative number when `a` precedes `b`, zero when equal, a positive number
51
+ * when `a` follows `b`. Facades use this for dedup and "have we seen newer?"
52
+ * checks without leaking the adapter-specific seq encoding.
53
+ *
54
+ * Behavior across topics, adapters, or for tokens this adapter never issued
55
+ * is undefined.
56
+ */
57
+ compareSeq(a: string, b: string): number;
58
+ /**
59
+ * Returns the oldest retained seq for `topic`, or `undefined` when nothing
60
+ * is currently retained. Throws synchronously when
61
+ * {@link CrossNodeBusCapabilities.replay} is `false`. Facades use this to
62
+ * decide whether a delta replay is feasible before calling
63
+ * {@link CrossNodeBus.replay}.
64
+ */
65
+ oldestSeq(topic: string): string | undefined;
66
+ }
67
+ /**
68
+ * Shared {@link CrossNodeBus} token. Resolves an `InProcessCrossNodeBus` by
69
+ * default — single-node deployments work without configuration. Multi-node
70
+ * deployments override the binding with a transport adapter, e.g.
71
+ * `defineRedisCrossNodeBusAdapter({ … })`.
72
+ *
73
+ * Singleton: a single bus per injector tree is the right semantic for
74
+ * cross-process publish/subscribe. Tests get isolation by minting their own
75
+ * root injector with `createInjector()`.
76
+ */
77
+ export declare const CrossNodeBus: Token<CrossNodeBus, 'singleton'>;
78
+ //# sourceMappingURL=cross-node-bus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-node-bus.d.ts","sourceRoot":"","sources":["../src/cross-node-bus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAE7D,OAAO,KAAK,EAAE,UAAU,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AAEtE;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IAEvB,sDAAsD;IACtD,QAAQ,CAAC,YAAY,EAAE,wBAAwB,CAAA;IAE/C;;;;OAIG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEvD;;;OAGG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,IAAI,GAAG,UAAU,CAAA;IAE5E;;;;OAIG;IACH,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,IAAI,GAAG,UAAU,CAAA;IAEtF;;;;OAIG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,IAAI,GAAG,UAAU,CAAA;IAEnG;;;;;OAKG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,CAAA;IAEjE;;;;;;;;OAQG;IACH,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAExC;;;;;;OAMG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CAC7C;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,WAAW,CAIxD,CAAA"}
@@ -0,0 +1,18 @@
1
+ import { defineService } from '@furystack/inject';
2
+ import { defineInProcessCrossNodeBus } from './define-in-process-cross-node-bus.js';
3
+ /**
4
+ * Shared {@link CrossNodeBus} token. Resolves an `InProcessCrossNodeBus` by
5
+ * default — single-node deployments work without configuration. Multi-node
6
+ * deployments override the binding with a transport adapter, e.g.
7
+ * `defineRedisCrossNodeBusAdapter({ … })`.
8
+ *
9
+ * Singleton: a single bus per injector tree is the right semantic for
10
+ * cross-process publish/subscribe. Tests get isolation by minting their own
11
+ * root injector with `createInjector()`.
12
+ */
13
+ export const CrossNodeBus = defineService({
14
+ name: 'furystack/cross-node-bus/CrossNodeBus',
15
+ lifetime: 'singleton',
16
+ factory: defineInProcessCrossNodeBus(),
17
+ });
18
+ //# sourceMappingURL=cross-node-bus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-node-bus.js","sourceRoot":"","sources":["../src/cross-node-bus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAc,MAAM,mBAAmB,CAAA;AAC7D,OAAO,EAAE,2BAA2B,EAAE,MAAM,uCAAuC,CAAA;AA4EnF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,YAAY,GAAqC,aAAa,CAAC;IAC1E,IAAI,EAAE,uCAAuC;IAC7C,QAAQ,EAAE,WAAW;IACrB,OAAO,EAAE,2BAA2B,EAAE;CACvC,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cross-node-bus.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-node-bus.spec.d.ts","sourceRoot":"","sources":["../src/cross-node-bus.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,123 @@
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
53
+ import { createInjector } from '@furystack/inject';
54
+ import { describe, expect, it, vi } from 'vitest';
55
+ import { CrossNodeBus } from './cross-node-bus.js';
56
+ import { CrossNodeBusTelemetryToken } from './cross-node-bus-telemetry.js';
57
+ import { defineInProcessCrossNodeBus } from './define-in-process-cross-node-bus.js';
58
+ import { InProcessCrossNodeBus } from './in-process-cross-node-bus.js';
59
+ describe('CrossNodeBus token', () => {
60
+ it('resolves an InProcessCrossNodeBus by default', async () => {
61
+ const env_1 = { stack: [], error: void 0, hasError: false };
62
+ try {
63
+ const injector = __addDisposableResource(env_1, createInjector(), true);
64
+ const bus = injector.get(CrossNodeBus);
65
+ expect(bus).toBeInstanceOf(InProcessCrossNodeBus);
66
+ }
67
+ catch (e_1) {
68
+ env_1.error = e_1;
69
+ env_1.hasError = true;
70
+ }
71
+ finally {
72
+ const result_1 = __disposeResources(env_1);
73
+ if (result_1)
74
+ await result_1;
75
+ }
76
+ });
77
+ it('disposes the bus when the injector scope tears down', async () => {
78
+ const injector = createInjector();
79
+ const bus = injector.get(CrossNodeBus);
80
+ await injector[Symbol.asyncDispose]();
81
+ expect(() => bus.subscribe('topic', () => undefined)).toThrow(/disposed/);
82
+ });
83
+ it('honors override bindings via defineInProcessCrossNodeBus', async () => {
84
+ const env_2 = { stack: [], error: void 0, hasError: false };
85
+ try {
86
+ const injector = __addDisposableResource(env_2, createInjector(), true);
87
+ injector.bind(CrossNodeBus, defineInProcessCrossNodeBus({ nodeId: 'svc-a-1', topicPrefix: 'svc-a/' }));
88
+ const bus = injector.get(CrossNodeBus);
89
+ expect(bus.nodeId).toBe('svc-a-1');
90
+ }
91
+ catch (e_2) {
92
+ env_2.error = e_2;
93
+ env_2.hasError = true;
94
+ }
95
+ finally {
96
+ const result_2 = __disposeResources(env_2);
97
+ if (result_2)
98
+ await result_2;
99
+ }
100
+ });
101
+ it('publishes drive the scoped CrossNodeBusTelemetryToken', async () => {
102
+ const env_3 = { stack: [], error: void 0, hasError: false };
103
+ try {
104
+ const injector = __addDisposableResource(env_3, createInjector(), true);
105
+ const telemetry = injector.get(CrossNodeBusTelemetryToken);
106
+ const handler = vi.fn();
107
+ const _sub = __addDisposableResource(env_3, telemetry.subscribe('onCrossNodePublished', handler), false);
108
+ const bus = injector.get(CrossNodeBus);
109
+ await bus.publish('topic', { hi: 'there' });
110
+ expect(handler).toHaveBeenCalledTimes(1);
111
+ }
112
+ catch (e_3) {
113
+ env_3.error = e_3;
114
+ env_3.hasError = true;
115
+ }
116
+ finally {
117
+ const result_3 = __disposeResources(env_3);
118
+ if (result_3)
119
+ await result_3;
120
+ }
121
+ });
122
+ });
123
+ //# sourceMappingURL=cross-node-bus.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-node-bus.spec.js","sourceRoot":"","sources":["../src/cross-node-bus.spec.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAA;AAC1E,OAAO,EAAE,2BAA2B,EAAE,MAAM,uCAAuC,CAAA;AACnF,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAA;AAEtE,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;;;YAC5D,MAAY,QAAQ,kCAAG,cAAc,EAAE,OAAA,CAAA;YACvC,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;YACtC,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,qBAAqB,CAAC,CAAA;;;;;;;;;;;KAClD,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,QAAQ,GAAG,cAAc,EAAE,CAAA;QACjC,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;QACtC,MAAM,QAAQ,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAA;QACrC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;IAC3E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;;;YACxE,MAAY,QAAQ,kCAAG,cAAc,EAAE,OAAA,CAAA;YACvC,QAAQ,CAAC,IAAI,CAAC,YAAY,EAAE,2BAA2B,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAA;YAEtG,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;YACtC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;;;;;;;;;;;KACnC,CAAC,CAAA;IAEF,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;;;YACrE,MAAY,QAAQ,kCAAG,cAAc,EAAE,OAAA,CAAA;YACvC,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAA;YAC1D,MAAM,OAAO,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;YACvB,MAAM,IAAI,kCAAG,SAAS,CAAC,SAAS,CAAC,sBAAsB,EAAE,OAAO,CAAC,QAAA,CAAA;YAEjE,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;YACtC,MAAM,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAA;YAE3C,MAAM,CAAC,OAAO,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAA;;;;;;;;;;;KACzC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,26 @@
1
+ import type { ServiceFactory } from '@furystack/inject';
2
+ import { type InProcessCrossNodeBusOptions } from './in-process-cross-node-bus.js';
3
+ import type { CrossNodeBus } from './cross-node-bus.js';
4
+ /**
5
+ * Options accepted by {@link defineInProcessCrossNodeBus}. `telemetry` is
6
+ * intentionally absent — the factory always injects
7
+ * {@link CrossNodeBusTelemetryToken} from the surrounding scope.
8
+ */
9
+ export type DefineInProcessCrossNodeBusOptions = Omit<InProcessCrossNodeBusOptions, 'telemetry'>;
10
+ /**
11
+ * Returns a {@link ServiceFactory} bound to {@link CrossNodeBus}. Use it to
12
+ * override the default factory at boot:
13
+ *
14
+ * ```ts
15
+ * injector.bind(
16
+ * CrossNodeBus,
17
+ * defineInProcessCrossNodeBus({ topicPrefix: 'svc-a/' }),
18
+ * )
19
+ * ```
20
+ *
21
+ * Mirrors the `defineXxxCrossNodeBusAdapter` shape future transport
22
+ * adapters expose. Wires telemetry and disposal into the surrounding
23
+ * injector scope.
24
+ */
25
+ export declare const defineInProcessCrossNodeBus: (options?: DefineInProcessCrossNodeBusOptions) => ServiceFactory<CrossNodeBus>;
26
+ //# sourceMappingURL=define-in-process-cross-node-bus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-in-process-cross-node-bus.d.ts","sourceRoot":"","sources":["../src/define-in-process-cross-node-bus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAEvD,OAAO,EAAyB,KAAK,4BAA4B,EAAE,MAAM,gCAAgC,CAAA;AACzG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAEvD;;;;GAIG;AACH,MAAM,MAAM,kCAAkC,GAAG,IAAI,CAAC,4BAA4B,EAAE,WAAW,CAAC,CAAA;AAEhG;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,2BAA2B,GACtC,UAAS,kCAAuC,KAC/C,cAAc,CAAC,YAAY,CAQ7B,CAAA"}