@miiajs/messaging 0.1.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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -0
  3. package/dist/decorators.d.ts +149 -0
  4. package/dist/decorators.d.ts.map +1 -0
  5. package/dist/decorators.js +112 -0
  6. package/dist/decorators.js.map +1 -0
  7. package/dist/group-name.d.ts +33 -0
  8. package/dist/group-name.d.ts.map +1 -0
  9. package/dist/group-name.js +25 -0
  10. package/dist/group-name.js.map +1 -0
  11. package/dist/idempotency.d.ts +66 -0
  12. package/dist/idempotency.d.ts.map +1 -0
  13. package/dist/idempotency.js +42 -0
  14. package/dist/idempotency.js.map +1 -0
  15. package/dist/in-memory-transport.d.ts +60 -0
  16. package/dist/in-memory-transport.d.ts.map +1 -0
  17. package/dist/in-memory-transport.js +143 -0
  18. package/dist/in-memory-transport.js.map +1 -0
  19. package/dist/index.d.ts +16 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +11 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/message-bus.d.ts +83 -0
  24. package/dist/message-bus.d.ts.map +1 -0
  25. package/dist/message-bus.js +218 -0
  26. package/dist/message-bus.js.map +1 -0
  27. package/dist/messaging.module.d.ts +76 -0
  28. package/dist/messaging.module.d.ts.map +1 -0
  29. package/dist/messaging.module.js +74 -0
  30. package/dist/messaging.module.js.map +1 -0
  31. package/dist/retry.d.ts +7 -0
  32. package/dist/retry.d.ts.map +1 -0
  33. package/dist/retry.js +10 -0
  34. package/dist/retry.js.map +1 -0
  35. package/dist/tokens.d.ts +19 -0
  36. package/dist/tokens.d.ts.map +1 -0
  37. package/dist/tokens.js +26 -0
  38. package/dist/tokens.js.map +1 -0
  39. package/dist/types.d.ts +165 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/types.js +15 -0
  42. package/dist/types.js.map +1 -0
  43. package/package.json +55 -0
@@ -0,0 +1,143 @@
1
+ import { Logger } from '@miiajs/core';
2
+ import { dlqTopic, nextBackoffMs } from './retry.js';
3
+ import { DEFAULT_RETRY, } from './types.js';
4
+ const DEFAULT_DRAIN_TIMEOUT_MS = 5000;
5
+ /**
6
+ * In-process message transport. Fire-and-forget delivery via `queueMicrotask`,
7
+ * exponential backoff retry via `setTimeout`, auto-DLQ via re-publishing to
8
+ * `<topic>.dlq`.
9
+ *
10
+ * NOT persistent. Process crash mid-retry loses pending messages - do not use
11
+ * for durability-sensitive workloads. Use `@miiajs/messaging-redis` or another
12
+ * broker-backed transport in production.
13
+ *
14
+ * **Dispatch capability:** sliding-only, no competing consumers. The in-memory
15
+ * transport is single-process: there is no broker to round-robin messages
16
+ * between multiple handlers in a shared `group`. Auto-derived per-handler
17
+ * groups work normally (each handler is its own subscription, fan-out is
18
+ * automatic). Handlers requesting `mode: 'batch'` or sharing an explicit
19
+ * `group` are rejected at `MessageBus.onReady()`.
20
+ */
21
+ export class InMemoryTransport {
22
+ supportedModes = ['sliding'];
23
+ defaultMode = 'sliding';
24
+ supportsCompetingConsumers = false;
25
+ subs = new Map();
26
+ retry;
27
+ cloneOnPublish;
28
+ drainTimeoutMs;
29
+ logger = new Logger('InMemoryTransport');
30
+ pendingTimers = new Set();
31
+ pendingDeliveries = new Set();
32
+ destroying = false;
33
+ constructor(options = {}) {
34
+ this.retry = { ...DEFAULT_RETRY, ...options.retry };
35
+ this.cloneOnPublish = options.cloneOnPublish ?? false;
36
+ this.drainTimeoutMs = options.drainTimeoutMs ?? DEFAULT_DRAIN_TIMEOUT_MS;
37
+ }
38
+ async publish(envelope) {
39
+ const subs = this.subs.get(envelope.topic);
40
+ if (!subs || subs.length === 0)
41
+ return;
42
+ // Fire-and-forget: resolve immediately, handlers run in microtask.
43
+ queueMicrotask(() => {
44
+ for (const sub of subs) {
45
+ this.deliver(sub, envelope);
46
+ }
47
+ });
48
+ }
49
+ async subscribe(topic, handler, _options) {
50
+ // SubscribeOptions.group and .concurrency are meaningless in a single
51
+ // process - local fan-out always delivers to every subscriber.
52
+ const sub = { topic, handler };
53
+ const list = this.subs.get(topic) ?? [];
54
+ list.push(sub);
55
+ this.subs.set(topic, list);
56
+ return {
57
+ unsubscribe: async () => {
58
+ const list = this.subs.get(topic);
59
+ if (!list)
60
+ return;
61
+ const idx = list.indexOf(sub);
62
+ if (idx >= 0)
63
+ list.splice(idx, 1);
64
+ if (list.length === 0)
65
+ this.subs.delete(topic);
66
+ },
67
+ };
68
+ }
69
+ async onDestroy() {
70
+ this.destroying = true;
71
+ await this.waitForDrain();
72
+ for (const timer of this.pendingTimers)
73
+ clearTimeout(timer);
74
+ this.pendingTimers.clear();
75
+ this.subs.clear();
76
+ }
77
+ /** Tracking wrapper - every (initial and retry) delivery flows through here. */
78
+ deliver(sub, envelope) {
79
+ if (this.destroying)
80
+ return; // refuse new work during shutdown
81
+ const promise = this.runDelivery(sub, envelope);
82
+ this.pendingDeliveries.add(promise);
83
+ promise.finally(() => this.pendingDeliveries.delete(promise));
84
+ }
85
+ async runDelivery(sub, envelope) {
86
+ const delivered = this.cloneOnPublish ? { ...envelope, payload: structuredClone(envelope.payload) } : envelope;
87
+ let result;
88
+ try {
89
+ result = await sub.handler(delivered);
90
+ }
91
+ catch (err) {
92
+ result = { status: 'nack', error: err instanceof Error ? err : new Error(String(err)) };
93
+ }
94
+ if (result.status === 'ack')
95
+ return;
96
+ // Nack: retry or DLQ.
97
+ if (envelope.meta.attempt < this.retry.maxAttempts) {
98
+ const delay = nextBackoffMs(envelope.meta.attempt, this.retry);
99
+ const timer = setTimeout(() => {
100
+ this.pendingTimers.delete(timer);
101
+ // Retry routes back through deliver() so it's tracked in pendingDeliveries.
102
+ this.deliver(sub, {
103
+ ...envelope,
104
+ meta: { ...envelope.meta, attempt: envelope.meta.attempt + 1 },
105
+ });
106
+ }, delay);
107
+ this.pendingTimers.add(timer);
108
+ return;
109
+ }
110
+ // Final failure.
111
+ this.logger.error(`Message ${envelope.id} on ${envelope.topic} exhausted ${this.retry.maxAttempts} attempts`, result.error.stack ?? result.error.message);
112
+ if (this.retry.dlq) {
113
+ await this.publish({
114
+ ...envelope,
115
+ topic: dlqTopic(envelope.topic),
116
+ meta: { ...envelope.meta, lastError: result.error.message },
117
+ });
118
+ }
119
+ }
120
+ async waitForDrain() {
121
+ if (this.drainTimeoutMs <= 0 || this.pendingDeliveries.size === 0)
122
+ return;
123
+ const drainPromise = Promise.allSettled([...this.pendingDeliveries]);
124
+ let timer = null;
125
+ const timeoutPromise = new Promise((r) => {
126
+ timer = setTimeout(() => r('timeout'), this.drainTimeoutMs);
127
+ });
128
+ try {
129
+ const result = await Promise.race([drainPromise.then(() => 'drained'), timeoutPromise]);
130
+ if (result === 'timeout') {
131
+ this.logger.warn(`Drain timeout: ${this.pendingDeliveries.size} handler(s) still in flight`);
132
+ }
133
+ }
134
+ finally {
135
+ if (timer)
136
+ clearTimeout(timer);
137
+ }
138
+ }
139
+ }
140
+ export function inMemoryTransport(options = {}) {
141
+ return new InMemoryTransport(options);
142
+ }
143
+ //# sourceMappingURL=in-memory-transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-memory-transport.js","sourceRoot":"","sources":["../src/in-memory-transport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export { MessageBus } from './message-bus.js';
2
+ export { Idempotent, IDEMPOTENT, On, ON } from './decorators.js';
3
+ export type { IdempotentMeta, OnMeta } from './decorators.js';
4
+ export { IDEMPOTENCY_STORE, MemoryIdempotencyStore, memoryIdempotencyStore } from './idempotency.js';
5
+ export type { IdempotencyStore, MemoryIdempotencyStoreOptions } from './idempotency.js';
6
+ export { DEFAULT_RETRY, MESSAGE_TRANSPORT } from './types.js';
7
+ export type { DispatchMode, MessageEnvelope, MessageMeta, MessageTransport, HandlerResult, RetryConfig, SubscribeOptions, Subscription, } from './types.js';
8
+ export { dlqTopic, nextBackoffMs } from './retry.js';
9
+ export { deriveGroupName } from './group-name.js';
10
+ export type { DeriveGroupNameInput } from './group-name.js';
11
+ export { InMemoryTransport, inMemoryTransport } from './in-memory-transport.js';
12
+ export type { InMemoryTransportOptions } from './in-memory-transport.js';
13
+ export { MessagingModule } from './messaging.module.js';
14
+ export type { DispatchDefaults, MessagingModuleOptions } from './messaging.module.js';
15
+ export { getMessageBusToken, getMessageTransportToken, getIdempotencyStoreToken } from './tokens.js';
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AAChE,YAAY,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAE7D,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AACpG,YAAY,EAAE,gBAAgB,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAA;AAEvF,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC7D,YAAY,EACV,YAAY,EACZ,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,YAAY,GACb,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAEpD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,YAAY,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAE3D,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC/E,YAAY,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAA;AAExE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACvD,YAAY,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAErF,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ // @miiajs/messaging - public API
2
+ export { MessageBus } from './message-bus.js';
3
+ export { Idempotent, IDEMPOTENT, On, ON } from './decorators.js';
4
+ export { IDEMPOTENCY_STORE, MemoryIdempotencyStore, memoryIdempotencyStore } from './idempotency.js';
5
+ export { DEFAULT_RETRY, MESSAGE_TRANSPORT } from './types.js';
6
+ export { dlqTopic, nextBackoffMs } from './retry.js';
7
+ export { deriveGroupName } from './group-name.js';
8
+ export { InMemoryTransport, inMemoryTransport } from './in-memory-transport.js';
9
+ export { MessagingModule } from './messaging.module.js';
10
+ export { getMessageBusToken, getMessageTransportToken, getIdempotencyStoreToken } from './tokens.js';
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iCAAiC;AAEjC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AAGhE,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AAGpG,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAY7D,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAEpD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAGjD,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAG/E,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAGvD,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,83 @@
1
+ import type { IdempotencyStore } from './idempotency.js';
2
+ import { type DispatchMode, type MessageMeta, type MessageTransport } from './types.js';
3
+ /** Bus-level dispatch defaults forwarded from `MessagingModuleOptions.dispatch`. */
4
+ interface DispatchDefaults {
5
+ mode?: DispatchMode;
6
+ concurrency?: number;
7
+ }
8
+ /** Options bag for the `MessageBus` constructor 4th argument. */
9
+ interface MessageBusOptions {
10
+ dispatch?: DispatchDefaults | null;
11
+ appName?: string | null;
12
+ }
13
+ /**
14
+ * Central message bus. Inject from any provider:
15
+ *
16
+ * ```ts
17
+ * @Injectable()
18
+ * class OrderService {
19
+ * private bus = inject(MessageBus)
20
+ *
21
+ * async placeOrder(order: Order) {
22
+ * await this.bus.publish('order.placed', order)
23
+ * }
24
+ * }
25
+ * ```
26
+ *
27
+ * `@On` handlers are discovered via `DiscoveryService` during `onReady()`.
28
+ * Each `@On` becomes its own broker subscription with an auto-derived
29
+ * consumer group `<topic>__<ClassName>_<methodName>` (or
30
+ * `<appName>:<topic>__<ClassName>_<methodName>` when `MessagingModule.configure`
31
+ * provides `appName`). Handlers run independently - retry, ack/nack,
32
+ * mode/concurrency are isolated per handler.
33
+ *
34
+ * For multi-bus setups, use `MessagingModule.configure(opts, name)` and inject
35
+ * the named bus via `inject<MessageBus>(getMessageBusToken(name))`. Handlers
36
+ * target a specific bus via `@On(topic, { bus: name })`.
37
+ *
38
+ * `MessageBus.publish` and `MessageTransport.publish` share the method name
39
+ * but live at different layers: `MessageBus.publish(topic, payload, meta)`
40
+ * is the high-level API that fills in `id`/`timestamp`/`attempt` and hands
41
+ * a fully-formed envelope to `MessageTransport.publish(envelope)`. Both can
42
+ * appear in stack traces; signature shape (envelope vs payload+meta) makes
43
+ * the layer obvious.
44
+ */
45
+ export declare class MessageBus {
46
+ private transport;
47
+ private idempotencyStore;
48
+ private busName;
49
+ private options;
50
+ private discovery;
51
+ private resolver;
52
+ private logger;
53
+ private subscriptions;
54
+ constructor(transport: MessageTransport, idempotencyStore: IdempotencyStore | null, busName: string | null, options?: MessageBusOptions);
55
+ onReady(): Promise<void>;
56
+ private getIdempotentMeta;
57
+ /**
58
+ * Publish a message. Meta fields are optional and additive - the framework
59
+ * fills in `id`, `timestamp`, and `attempt`. Composes onto
60
+ * `MessageTransport.publish(envelope)` internally.
61
+ *
62
+ * For distributed tracing, populate `traceparent` (and optionally `tracestate`)
63
+ * from your tracing library so consumers can continue the trace.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * import { trace } from '@opentelemetry/api'
68
+ *
69
+ * const span = trace.getActiveSpan()
70
+ * const ctx = span?.spanContext()
71
+ * await bus.publish('order.placed', order, {
72
+ * traceparent: ctx
73
+ * ? `00-${ctx.traceId}-${ctx.spanId}-${ctx.traceFlags.toString(16).padStart(2, '0')}`
74
+ * : undefined,
75
+ * })
76
+ * ```
77
+ */
78
+ publish<T>(topic: string, payload: T, meta?: Partial<Pick<MessageMeta, 'correlationId' | 'causationId' | 'traceparent' | 'tracestate'>>): Promise<void>;
79
+ onDestroy(): Promise<void>;
80
+ private dispatch;
81
+ }
82
+ export {};
83
+ //# sourceMappingURL=message-bus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-bus.d.ts","sourceRoot":"","sources":["../src/message-bus.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAExD,OAAO,EACL,KAAK,YAAY,EAGjB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EAEtB,MAAM,YAAY,CAAA;AAEnB,oFAAoF;AACpF,UAAU,gBAAgB;IACxB,IAAI,CAAC,EAAE,YAAY,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,iEAAiE;AACjE,UAAU,iBAAiB;IACzB,QAAQ,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAA;IAClC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,UAAU;IASnB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,gBAAgB;IACxB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,OAAO;IAVjB,OAAO,CAAC,SAAS,CAA2B;IAC5C,OAAO,CAAC,QAAQ,CAAmB;IAEnC,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,aAAa,CAAqB;gBAGhC,SAAS,EAAE,gBAAgB,EAC3B,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,EACzC,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,OAAO,GAAE,iBAAsB;IAKnC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA8G9B,OAAO,CAAC,iBAAiB;IAKzB;;;;;;;;;;;;;;;;;;;;OAoBG;IACG,OAAO,CAAC,CAAC,EACb,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,EACV,IAAI,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,eAAe,GAAG,aAAa,GAAG,aAAa,GAAG,YAAY,CAAC,CAAC,GAChG,OAAO,CAAC,IAAI,CAAC;IAcV,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;YAOlB,QAAQ;CA6CvB"}
@@ -0,0 +1,218 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { DiscoveryService, getMeta, inject, Logger, Resolver } from '@miiajs/core';
3
+ import { IDEMPOTENT, ON } from './decorators.js';
4
+ import { deriveGroupName } from './group-name.js';
5
+ import { getMessageBusToken } from './tokens.js';
6
+ import {} from './types.js';
7
+ /**
8
+ * Central message bus. Inject from any provider:
9
+ *
10
+ * ```ts
11
+ * @Injectable()
12
+ * class OrderService {
13
+ * private bus = inject(MessageBus)
14
+ *
15
+ * async placeOrder(order: Order) {
16
+ * await this.bus.publish('order.placed', order)
17
+ * }
18
+ * }
19
+ * ```
20
+ *
21
+ * `@On` handlers are discovered via `DiscoveryService` during `onReady()`.
22
+ * Each `@On` becomes its own broker subscription with an auto-derived
23
+ * consumer group `<topic>__<ClassName>_<methodName>` (or
24
+ * `<appName>:<topic>__<ClassName>_<methodName>` when `MessagingModule.configure`
25
+ * provides `appName`). Handlers run independently - retry, ack/nack,
26
+ * mode/concurrency are isolated per handler.
27
+ *
28
+ * For multi-bus setups, use `MessagingModule.configure(opts, name)` and inject
29
+ * the named bus via `inject<MessageBus>(getMessageBusToken(name))`. Handlers
30
+ * target a specific bus via `@On(topic, { bus: name })`.
31
+ *
32
+ * `MessageBus.publish` and `MessageTransport.publish` share the method name
33
+ * but live at different layers: `MessageBus.publish(topic, payload, meta)`
34
+ * is the high-level API that fills in `id`/`timestamp`/`attempt` and hands
35
+ * a fully-formed envelope to `MessageTransport.publish(envelope)`. Both can
36
+ * appear in stack traces; signature shape (envelope vs payload+meta) makes
37
+ * the layer obvious.
38
+ */
39
+ export class MessageBus {
40
+ transport;
41
+ idempotencyStore;
42
+ busName;
43
+ options;
44
+ // Field-init for shared globals (idiomatic MiiaJS pattern).
45
+ discovery = inject(DiscoveryService);
46
+ resolver = inject(Resolver);
47
+ // Logger init in constructor body because it depends on busName.
48
+ logger;
49
+ subscriptions = [];
50
+ constructor(transport, idempotencyStore, busName, options = {}) {
51
+ this.transport = transport;
52
+ this.idempotencyStore = idempotencyStore;
53
+ this.busName = busName;
54
+ this.options = options;
55
+ this.logger = new Logger(busName ? `MessageBus:${busName}` : 'MessageBus');
56
+ }
57
+ async onReady() {
58
+ const allHandlers = this.discovery.getMethodsWithMeta(ON);
59
+ // Validation: every referenced bus name must point to a registered bus.
60
+ // Each bus instance runs this check; first onReady to fail throws.
61
+ for (const h of allHandlers) {
62
+ const refBus = h.metadata.bus ?? null;
63
+ const token = getMessageBusToken(refBus ?? undefined);
64
+ if (!this.resolver.has(token)) {
65
+ throw new Error(`[messaging] @On on ${h.ctor.name}.${h.methodName} references bus '${refBus ?? '<default>'}', ` +
66
+ `but no MessagingModule.configure(opts${refBus ? `, '${refBus}'` : ''}) is registered.`);
67
+ }
68
+ }
69
+ // Filter to handlers targeting THIS bus.
70
+ const handlers = allHandlers.filter((h) => (h.metadata.bus ?? null) === this.busName);
71
+ const transportName = this.transport.constructor.name;
72
+ const dispatchDefaults = this.options.dispatch ?? null;
73
+ const appName = this.options.appName ?? null;
74
+ for (const h of handlers) {
75
+ const meta = h.metadata;
76
+ // Startup validation: @Idempotent without configured store → fail fast.
77
+ const idemMeta = this.getIdempotentMeta(h.ctor, h.methodName);
78
+ if (idemMeta && !this.idempotencyStore) {
79
+ throw new Error(`[messaging] @Idempotent on ${h.ctor.name}.${h.methodName} requires an IdempotencyStore. ` +
80
+ `Pass \`idempotency: memoryIdempotencyStore()\` (dev) or ` +
81
+ `\`idempotency: redisIdempotencyStore({...})\` (prod) to MessagingModule.configure().`);
82
+ }
83
+ // broadcast and explicit group are conceptually opposite intents.
84
+ if (meta.broadcast && meta.group) {
85
+ throw new Error(`[messaging] @On on ${h.ctor.name}.${h.methodName}: broadcast and group are mutually exclusive. ` +
86
+ `Use broadcast for cluster-wide fan-out, or group for an explicit competing-consumers pool, not both.`);
87
+ }
88
+ // @Idempotent default key is `${envelope.id}:${ctor}.${method}` - shared
89
+ // across replicas. With broadcast, every replica receives a copy; the
90
+ // first replica claims the key and the rest see a stale claim → silent
91
+ // skip → broadcast effectively becomes "first wins". Reject at startup
92
+ // unless the user provided an explicit key callback (presumably scoped
93
+ // per-process).
94
+ if (meta.broadcast && idemMeta && !idemMeta.key) {
95
+ throw new Error(`[messaging] @On on ${h.ctor.name}.${h.methodName} combines broadcast: true with @Idempotent ` +
96
+ `using the default key, but the default key is shared across replicas - the first replica to ` +
97
+ `claim it would silently skip the handler on every other replica, breaking broadcast semantics. ` +
98
+ `Either remove @Idempotent (broadcast usually targets non-idempotent local state like cache invalidation) ` +
99
+ `or provide an explicit \`key\` callback that scopes the claim per replica.`);
100
+ }
101
+ // Explicit broker group requires the transport to support
102
+ // competing-consumers semantic (broker round-robin within a group).
103
+ if (meta.group && !this.transport.supportsCompetingConsumers) {
104
+ throw new Error(`[messaging] @On on ${h.ctor.name}.${h.methodName} on bus '${this.busName ?? '<default>'}' ` +
105
+ `declares explicit group '${meta.group}', but transport ${transportName} does not support ` +
106
+ `competing consumers. Either remove the group option (use the auto-derived per-handler group) ` +
107
+ `or use a transport with broker-side consumer groups (e.g. RedisStreamsTransport).`);
108
+ }
109
+ const effectiveMode = meta.mode ?? dispatchDefaults?.mode ?? this.transport.defaultMode;
110
+ const effectiveConcurrency = meta.concurrency ?? dispatchDefaults?.concurrency ?? 1;
111
+ if (!this.transport.supportedModes.includes(effectiveMode)) {
112
+ const supported = this.transport.supportedModes.map((m) => `'${m}'`).join(', ');
113
+ throw new Error(`[messaging] @On on ${h.ctor.name}.${h.methodName} on bus '${this.busName ?? '<default>'}' ` +
114
+ `for topic '${meta.topic}' requests dispatch mode '${effectiveMode}' which is not supported by ` +
115
+ `${transportName}. Supported modes: [${supported}].`);
116
+ }
117
+ if (effectiveConcurrency <= 0 || !Number.isFinite(effectiveConcurrency)) {
118
+ throw new Error(`[messaging] @On on ${h.ctor.name}.${h.methodName} for topic '${meta.topic}' resolves to ` +
119
+ `concurrency=${effectiveConcurrency}, which is invalid. Concurrency must be a positive integer ` +
120
+ `(minimum 1).`);
121
+ }
122
+ const group = deriveGroupName({
123
+ topic: meta.topic,
124
+ ctorName: h.ctor.name,
125
+ methodName: h.methodName,
126
+ appName,
127
+ explicitGroup: meta.group,
128
+ broadcast: meta.broadcast,
129
+ });
130
+ const sub = await this.transport.subscribe(meta.topic, async (envelope) => this.dispatch(h, envelope), {
131
+ group,
132
+ concurrency: effectiveConcurrency,
133
+ mode: effectiveMode,
134
+ broadcast: meta.broadcast,
135
+ });
136
+ this.subscriptions.push(sub);
137
+ }
138
+ }
139
+ getIdempotentMeta(ctor, methodName) {
140
+ const map = getMeta(ctor, IDEMPOTENT);
141
+ return map?.get(methodName);
142
+ }
143
+ /**
144
+ * Publish a message. Meta fields are optional and additive - the framework
145
+ * fills in `id`, `timestamp`, and `attempt`. Composes onto
146
+ * `MessageTransport.publish(envelope)` internally.
147
+ *
148
+ * For distributed tracing, populate `traceparent` (and optionally `tracestate`)
149
+ * from your tracing library so consumers can continue the trace.
150
+ *
151
+ * @example
152
+ * ```ts
153
+ * import { trace } from '@opentelemetry/api'
154
+ *
155
+ * const span = trace.getActiveSpan()
156
+ * const ctx = span?.spanContext()
157
+ * await bus.publish('order.placed', order, {
158
+ * traceparent: ctx
159
+ * ? `00-${ctx.traceId}-${ctx.spanId}-${ctx.traceFlags.toString(16).padStart(2, '0')}`
160
+ * : undefined,
161
+ * })
162
+ * ```
163
+ */
164
+ async publish(topic, payload, meta) {
165
+ const envelope = {
166
+ id: randomUUID(),
167
+ topic,
168
+ payload,
169
+ meta: {
170
+ timestamp: Date.now(),
171
+ attempt: 1,
172
+ ...meta,
173
+ },
174
+ };
175
+ await this.transport.publish(envelope);
176
+ }
177
+ async onDestroy() {
178
+ for (const sub of this.subscriptions) {
179
+ await sub.unsubscribe();
180
+ }
181
+ this.subscriptions = [];
182
+ }
183
+ async dispatch(h, envelope) {
184
+ const callHandler = () => h.instance[h.methodName](envelope.payload, envelope.meta);
185
+ const idemMeta = this.getIdempotentMeta(h.ctor, h.methodName);
186
+ if (!idemMeta) {
187
+ try {
188
+ await callHandler();
189
+ return { status: 'ack' };
190
+ }
191
+ catch (err) {
192
+ this.logger.error(`Handler ${h.ctor.name}.${h.methodName} failed for ${envelope.topic} (attempt ${envelope.meta.attempt})`, err instanceof Error ? (err.stack ?? err.message) : String(err));
193
+ return { status: 'nack', error: err instanceof Error ? err : new Error(String(err)) };
194
+ }
195
+ }
196
+ // @Idempotent path: claim → run → release on failure.
197
+ // Per-handler default key avoids cross-handler conflicts; user can override
198
+ // via `key` to widen scope (e.g. share across handlers).
199
+ const id = idemMeta.key?.(envelope.payload, envelope.meta) ?? `${envelope.id}:${h.ctor.name}.${h.methodName}`;
200
+ const claimed = await this.idempotencyStore.claim(id, idemMeta.ttl);
201
+ if (!claimed)
202
+ return { status: 'ack' }; // already processed - silently skip
203
+ try {
204
+ await callHandler();
205
+ return { status: 'ack' };
206
+ }
207
+ catch (err) {
208
+ // Release so a retry can re-claim and re-process. Failure to release
209
+ // is logged but does not mask the original handler error.
210
+ await this.idempotencyStore.release(id).catch((releaseErr) => {
211
+ this.logger.error(`Failed to release idempotency claim ${id}`, String(releaseErr));
212
+ });
213
+ this.logger.error(`Handler ${h.ctor.name}.${h.methodName} failed for ${envelope.topic} (attempt ${envelope.meta.attempt})`, err instanceof Error ? (err.stack ?? err.message) : String(err));
214
+ return { status: 'nack', error: err instanceof Error ? err : new Error(String(err)) };
215
+ }
216
+ }
217
+ }
218
+ //# sourceMappingURL=message-bus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-bus.js","sourceRoot":"","sources":["../src/message-bus.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ import type { ConfiguredModule, OptionsOrFactory } from '@miiajs/core';
2
+ import { type IdempotencyStore } from './idempotency.js';
3
+ import { type DispatchMode, type MessageTransport } from './types.js';
4
+ /**
5
+ * Bus-level defaults for dispatch behavior. Per-handler `@On({ mode, concurrency })`
6
+ * overrides these; if neither this nor the handler declares a value, the
7
+ * transport's `defaultMode` and a concurrency of `1` apply.
8
+ */
9
+ export interface DispatchDefaults {
10
+ mode?: DispatchMode;
11
+ concurrency?: number;
12
+ }
13
+ export interface MessagingModuleOptions {
14
+ transport: MessageTransport;
15
+ /**
16
+ * Optional idempotency store for `@Idempotent` handlers. MessageBus throws
17
+ * at startup if any handler is annotated with `@Idempotent` but no store
18
+ * is configured here.
19
+ */
20
+ idempotency?: IdempotencyStore;
21
+ /**
22
+ * Bus-wide dispatch defaults. Resolution chain at `MessageBus.onReady()`:
23
+ *
24
+ * `@On({ mode, concurrency })` > this `dispatch` > `transport.defaultMode` (and 1).
25
+ */
26
+ dispatch?: DispatchDefaults;
27
+ /**
28
+ * Application namespace for auto-derived consumer group names. When set,
29
+ * groups become `${appName}:${topic}__${ClassName}_${methodName}`. Use to
30
+ * avoid collisions when multiple services share a broker and have handlers
31
+ * with overlapping class names - without `appName`, their auto-derived
32
+ * groups collide and the broker round-robins messages between unrelated
33
+ * services.
34
+ *
35
+ * Does NOT prefix:
36
+ * - topics (those are shared cross-service contract)
37
+ * - explicit `@On({ group: '...' })` (user-controlled, full-qualified)
38
+ *
39
+ * Recommended for any production deployment with shared infrastructure.
40
+ */
41
+ appName?: string;
42
+ }
43
+ /**
44
+ * Dynamic module that registers a message transport, optional idempotency
45
+ * store, and `MessageBus` in the container.
46
+ *
47
+ * Pass an optional `name` to register a **named bus** alongside the default
48
+ * one - useful when an app needs more than one transport (Kafka + Redis,
49
+ * internal in-memory bus + external Redis, etc.). Handlers target a specific
50
+ * bus via `@On(topic, { bus: name })`.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * // Static form, default bus
55
+ * MessagingModule.configure({ transport: inMemoryTransport() })
56
+ *
57
+ * // Factory form with DI access
58
+ * MessagingModule.configure((resolve) => ({
59
+ * transport: redisStreamsTransport({
60
+ * url: resolve(ConfigService).getOrThrow('REDIS_URL'),
61
+ * }),
62
+ * }))
63
+ *
64
+ * // Multi-bus: default + named
65
+ * MessagingModule.configure({ transport: redisStreamsTransport({ url: '...' }) })
66
+ * MessagingModule.configure({ transport: kafkaTransport({ ... }) }, 'kafka')
67
+ *
68
+ * // In a handler
69
+ * @On('order.placed') // default bus
70
+ * @On('legacy.user', { bus: 'kafka' }) // named bus
71
+ * ```
72
+ */
73
+ export declare class MessagingModule {
74
+ static configure(optionsOrFactory: OptionsOrFactory<MessagingModuleOptions>, name?: string): ConfiguredModule;
75
+ }
76
+ //# sourceMappingURL=messaging.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messaging.module.d.ts","sourceRoot":"","sources":["../src/messaging.module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAe,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAGnF,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAExD,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAErE;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,YAAY,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,gBAAgB,CAAA;IAC3B;;;;OAIG;IACH,WAAW,CAAC,EAAE,gBAAgB,CAAA;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,gBAAgB,CAAA;IAC3B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,eAAe;IAC1B,MAAM,CAAC,SAAS,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,sBAAsB,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,gBAAgB;CAqC9G"}