@nestjs-transactional/outbox 1.0.0-alpha.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 (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +396 -0
  3. package/dist/api/completed-event-publications.d.ts +31 -0
  4. package/dist/api/completed-event-publications.js +58 -0
  5. package/dist/api/completed-event-publications.js.map +1 -0
  6. package/dist/api/failed-event-publications.d.ts +36 -0
  7. package/dist/api/failed-event-publications.js +75 -0
  8. package/dist/api/failed-event-publications.js.map +1 -0
  9. package/dist/api/incomplete-event-publications.d.ts +28 -0
  10. package/dist/api/incomplete-event-publications.js +72 -0
  11. package/dist/api/incomplete-event-publications.js.map +1 -0
  12. package/dist/decorators/inject-decorators.d.ts +85 -0
  13. package/dist/decorators/inject-decorators.js +100 -0
  14. package/dist/decorators/inject-decorators.js.map +1 -0
  15. package/dist/decorators/outbox-events-handler.decorator.d.ts +97 -0
  16. package/dist/decorators/outbox-events-handler.decorator.js +58 -0
  17. package/dist/decorators/outbox-events-handler.decorator.js.map +1 -0
  18. package/dist/dispatcher/data-source-outbox-publisher.d.ts +69 -0
  19. package/dist/dispatcher/data-source-outbox-publisher.js +127 -0
  20. package/dist/dispatcher/data-source-outbox-publisher.js.map +1 -0
  21. package/dist/dispatcher/event-publication-processor.d.ts +69 -0
  22. package/dist/dispatcher/event-publication-processor.js +192 -0
  23. package/dist/dispatcher/event-publication-processor.js.map +1 -0
  24. package/dist/dispatcher/outbox-event-publisher.d.ts +129 -0
  25. package/dist/dispatcher/outbox-event-publisher.js +172 -0
  26. package/dist/dispatcher/outbox-event-publisher.js.map +1 -0
  27. package/dist/dispatcher/processor-options.d.ts +24 -0
  28. package/dist/dispatcher/processor-options.js +15 -0
  29. package/dist/dispatcher/processor-options.js.map +1 -0
  30. package/dist/externalization/errors.d.ts +21 -0
  31. package/dist/externalization/errors.js +30 -0
  32. package/dist/externalization/errors.js.map +1 -0
  33. package/dist/externalization/event-externalizer.d.ts +49 -0
  34. package/dist/externalization/event-externalizer.js +14 -0
  35. package/dist/externalization/event-externalizer.js.map +1 -0
  36. package/dist/externalization/externalization-registry.d.ts +56 -0
  37. package/dist/externalization/externalization-registry.js +103 -0
  38. package/dist/externalization/externalization-registry.js.map +1 -0
  39. package/dist/externalization/externalized.decorator.d.ts +121 -0
  40. package/dist/externalization/externalized.decorator.js +87 -0
  41. package/dist/externalization/externalized.decorator.js.map +1 -0
  42. package/dist/externalization/types.d.ts +39 -0
  43. package/dist/externalization/types.js +3 -0
  44. package/dist/externalization/types.js.map +1 -0
  45. package/dist/index.d.ts +38 -0
  46. package/dist/index.js +58 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/interfaces/outbox-event-handler.interface.d.ts +32 -0
  49. package/dist/interfaces/outbox-event-handler.interface.js +3 -0
  50. package/dist/interfaces/outbox-event-handler.interface.js.map +1 -0
  51. package/dist/module/outbox-processing.module.d.ts +29 -0
  52. package/dist/module/outbox-processing.module.js +66 -0
  53. package/dist/module/outbox-processing.module.js.map +1 -0
  54. package/dist/module/outbox.module.d.ts +196 -0
  55. package/dist/module/outbox.module.js +604 -0
  56. package/dist/module/outbox.module.js.map +1 -0
  57. package/dist/recovery/staleness-monitor.d.ts +39 -0
  58. package/dist/recovery/staleness-monitor.js +118 -0
  59. package/dist/recovery/staleness-monitor.js.map +1 -0
  60. package/dist/recovery/startup-recovery.d.ts +37 -0
  61. package/dist/recovery/startup-recovery.js +55 -0
  62. package/dist/recovery/startup-recovery.js.map +1 -0
  63. package/dist/registry/event-publication-registry.d.ts +70 -0
  64. package/dist/registry/event-publication-registry.js +138 -0
  65. package/dist/registry/event-publication-registry.js.map +1 -0
  66. package/dist/registry/listener-registry.d.ts +52 -0
  67. package/dist/registry/listener-registry.js +67 -0
  68. package/dist/registry/listener-registry.js.map +1 -0
  69. package/dist/registry/multi-ds-listener-registrar.d.ts +78 -0
  70. package/dist/registry/multi-ds-listener-registrar.js +112 -0
  71. package/dist/registry/multi-ds-listener-registrar.js.map +1 -0
  72. package/dist/registry/outbox-listener-scanner.d.ts +66 -0
  73. package/dist/registry/outbox-listener-scanner.js +192 -0
  74. package/dist/registry/outbox-listener-scanner.js.map +1 -0
  75. package/dist/repository/event-publication-repository.d.ts +112 -0
  76. package/dist/repository/event-publication-repository.js +6 -0
  77. package/dist/repository/event-publication-repository.js.map +1 -0
  78. package/dist/serialization/event-serializer.d.ts +28 -0
  79. package/dist/serialization/event-serializer.js +6 -0
  80. package/dist/serialization/event-serializer.js.map +1 -0
  81. package/dist/serialization/event-type-registry.d.ts +41 -0
  82. package/dist/serialization/event-type-registry.js +79 -0
  83. package/dist/serialization/event-type-registry.js.map +1 -0
  84. package/dist/serialization/event-type-resolver.d.ts +43 -0
  85. package/dist/serialization/event-type-resolver.js +66 -0
  86. package/dist/serialization/event-type-resolver.js.map +1 -0
  87. package/dist/serialization/json-event-serializer.d.ts +20 -0
  88. package/dist/serialization/json-event-serializer.js +64 -0
  89. package/dist/serialization/json-event-serializer.js.map +1 -0
  90. package/dist/testing/assertable-published-events.d.ts +71 -0
  91. package/dist/testing/assertable-published-events.js +124 -0
  92. package/dist/testing/assertable-published-events.js.map +1 -0
  93. package/dist/testing/in-memory-repository.d.ts +52 -0
  94. package/dist/testing/in-memory-repository.js +230 -0
  95. package/dist/testing/in-memory-repository.js.map +1 -0
  96. package/dist/testing/index.d.ts +4 -0
  97. package/dist/testing/index.js +20 -0
  98. package/dist/testing/index.js.map +1 -0
  99. package/dist/testing/published-events.d.ts +62 -0
  100. package/dist/testing/published-events.js +110 -0
  101. package/dist/testing/published-events.js.map +1 -0
  102. package/dist/tokens/index.d.ts +2 -0
  103. package/dist/tokens/index.js +14 -0
  104. package/dist/tokens/index.js.map +1 -0
  105. package/dist/tokens/token-utils.d.ts +68 -0
  106. package/dist/tokens/token-utils.js +98 -0
  107. package/dist/tokens/token-utils.js.map +1 -0
  108. package/dist/types/completion-mode.d.ts +16 -0
  109. package/dist/types/completion-mode.js +20 -0
  110. package/dist/types/completion-mode.js.map +1 -0
  111. package/dist/types/errors.d.ts +50 -0
  112. package/dist/types/errors.js +64 -0
  113. package/dist/types/errors.js.map +1 -0
  114. package/dist/types/event-publication.d.ts +47 -0
  115. package/dist/types/event-publication.js +3 -0
  116. package/dist/types/event-publication.js.map +1 -0
  117. package/dist/types/publication-status.d.ts +26 -0
  118. package/dist/types/publication-status.js +30 -0
  119. package/dist/types/publication-status.js.map +1 -0
  120. package/dist/types/resubmission-options.d.ts +37 -0
  121. package/dist/types/resubmission-options.js +62 -0
  122. package/dist/types/resubmission-options.js.map +1 -0
  123. package/dist/types/staleness-config.d.ts +18 -0
  124. package/dist/types/staleness-config.js +10 -0
  125. package/dist/types/staleness-config.js.map +1 -0
  126. package/package.json +77 -0
@@ -0,0 +1,78 @@
1
+ import { ModuleRef } from '@nestjs/core';
2
+ import { type OutboxDataSourceNames } from '../dispatcher/outbox-event-publisher';
3
+ /**
4
+ * Cross-package shared DI token for the outbox listener registrar
5
+ * structural port (declared in the cqrs package as
6
+ * `OUTBOX_LISTENER_REGISTRAR`). Defined here via `Symbol.for(...)`
7
+ * with the same key the cqrs package uses, so both packages refer
8
+ * to the same Symbol identity without a direct import in either
9
+ * direction (Convention #8 — same pattern as `WRAPPED_MARKER`).
10
+ *
11
+ * Importing this constant from `@nestjs-transactional/outbox` and
12
+ * importing `OUTBOX_LISTENER_REGISTRAR` from `@nestjs-transactional/cqrs`
13
+ * yields the same Symbol — they are interchangeable in `@Inject(...)`
14
+ * arguments.
15
+ *
16
+ * @internal — used by `OutboxModule.forRoot` to auto-bind the smart
17
+ * registrar. Consumers should keep importing
18
+ * `OUTBOX_LISTENER_REGISTRAR` from `@nestjs-transactional/cqrs` for
19
+ * source-level clarity.
20
+ */
21
+ export declare const OUTBOX_LISTENER_REGISTRAR_TOKEN: unique symbol;
22
+ /** Shape of an entry registered through the structural registrar port. */
23
+ interface RegistrarListenerEntry {
24
+ readonly id: string;
25
+ readonly eventType: string;
26
+ readonly invoke: (event: unknown) => Promise<void>;
27
+ }
28
+ /**
29
+ * Multi-dataSource implementation of the cqrs package's
30
+ * `OutboxListenerRegistrar` structural port (Phase 14.3.1).
31
+ *
32
+ * Walks every per-dataSource {@link EventTypeRegistry} to resolve
33
+ * which dataSource owns each incoming listener's event class, then
34
+ * registers the listener with that dataSource's
35
+ * {@link OutboxListenerRegistry}. The cqrs scanner
36
+ * (`IntegrationEventsHandlerScanner`) injects the structural port
37
+ * blindly — this class is what makes a single inject-point handle
38
+ * arbitrary multi-DS deployments.
39
+ *
40
+ * **Auto-binding.** `OutboxModule.forRoot` registers this class
41
+ * under {@link OUTBOX_LISTENER_REGISTRAR_TOKEN} on the first
42
+ * `forRoot` call. The cqrs `IntegrationEventsHandlerScanner` then
43
+ * picks it up via its `@Optional() @Inject(OUTBOX_LISTENER_REGISTRAR)`
44
+ * — no consumer-side wiring required.
45
+ *
46
+ * **Failure modes** (delegated to {@link resolveDataSourceByEventTypeName}):
47
+ *
48
+ * - Event not registered in any dataSource → throws on registration.
49
+ * - Event registered in more than one dataSource → throws on
50
+ * registration.
51
+ *
52
+ * In both cases the `IntegrationEventsHandlerScanner` propagates the
53
+ * error from its `onModuleInit`, surfacing the misconfiguration at
54
+ * application bootstrap rather than at first dispatch.
55
+ */
56
+ export declare class MultiDsOutboxListenerRegistrar {
57
+ private readonly moduleRef;
58
+ private readonly dataSourceNames;
59
+ private readonly logger;
60
+ constructor(moduleRef: ModuleRef, dataSourceNames: OutboxDataSourceNames);
61
+ /**
62
+ * Register a listener with the per-dataSource
63
+ * {@link OutboxListenerRegistry} whose dataSource owns
64
+ * `listener.eventType`. Throws when the event-type cannot be
65
+ * routed unambiguously — see {@link resolveDataSourceByEventTypeName}.
66
+ */
67
+ register(listener: RegistrarListenerEntry): void;
68
+ /**
69
+ * Collect a Map of dataSource name → {@link EventTypeRegistry}
70
+ * from the live registrations Set. Resolved fresh per `register`
71
+ * call so a registrar surviving across module rebuilds (test
72
+ * scenarios) sees the current state instead of a snapshot taken
73
+ * at construction time.
74
+ */
75
+ private collectEventTypeRegistries;
76
+ }
77
+ export {};
78
+ //# sourceMappingURL=multi-ds-listener-registrar.d.ts.map
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var MultiDsOutboxListenerRegistrar_1;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.MultiDsOutboxListenerRegistrar = exports.OUTBOX_LISTENER_REGISTRAR_TOKEN = void 0;
17
+ const common_1 = require("@nestjs/common");
18
+ const core_1 = require("@nestjs/core");
19
+ const outbox_event_publisher_1 = require("../dispatcher/outbox-event-publisher");
20
+ const event_type_resolver_1 = require("../serialization/event-type-resolver");
21
+ const token_utils_1 = require("../tokens/token-utils");
22
+ /**
23
+ * Cross-package shared DI token for the outbox listener registrar
24
+ * structural port (declared in the cqrs package as
25
+ * `OUTBOX_LISTENER_REGISTRAR`). Defined here via `Symbol.for(...)`
26
+ * with the same key the cqrs package uses, so both packages refer
27
+ * to the same Symbol identity without a direct import in either
28
+ * direction (Convention #8 — same pattern as `WRAPPED_MARKER`).
29
+ *
30
+ * Importing this constant from `@nestjs-transactional/outbox` and
31
+ * importing `OUTBOX_LISTENER_REGISTRAR` from `@nestjs-transactional/cqrs`
32
+ * yields the same Symbol — they are interchangeable in `@Inject(...)`
33
+ * arguments.
34
+ *
35
+ * @internal — used by `OutboxModule.forRoot` to auto-bind the smart
36
+ * registrar. Consumers should keep importing
37
+ * `OUTBOX_LISTENER_REGISTRAR` from `@nestjs-transactional/cqrs` for
38
+ * source-level clarity.
39
+ */
40
+ exports.OUTBOX_LISTENER_REGISTRAR_TOKEN = Symbol.for('@nestjs-transactional/cqrs/outbox-listener-registrar');
41
+ /**
42
+ * Multi-dataSource implementation of the cqrs package's
43
+ * `OutboxListenerRegistrar` structural port (Phase 14.3.1).
44
+ *
45
+ * Walks every per-dataSource {@link EventTypeRegistry} to resolve
46
+ * which dataSource owns each incoming listener's event class, then
47
+ * registers the listener with that dataSource's
48
+ * {@link OutboxListenerRegistry}. The cqrs scanner
49
+ * (`IntegrationEventsHandlerScanner`) injects the structural port
50
+ * blindly — this class is what makes a single inject-point handle
51
+ * arbitrary multi-DS deployments.
52
+ *
53
+ * **Auto-binding.** `OutboxModule.forRoot` registers this class
54
+ * under {@link OUTBOX_LISTENER_REGISTRAR_TOKEN} on the first
55
+ * `forRoot` call. The cqrs `IntegrationEventsHandlerScanner` then
56
+ * picks it up via its `@Optional() @Inject(OUTBOX_LISTENER_REGISTRAR)`
57
+ * — no consumer-side wiring required.
58
+ *
59
+ * **Failure modes** (delegated to {@link resolveDataSourceByEventTypeName}):
60
+ *
61
+ * - Event not registered in any dataSource → throws on registration.
62
+ * - Event registered in more than one dataSource → throws on
63
+ * registration.
64
+ *
65
+ * In both cases the `IntegrationEventsHandlerScanner` propagates the
66
+ * error from its `onModuleInit`, surfacing the misconfiguration at
67
+ * application bootstrap rather than at first dispatch.
68
+ */
69
+ let MultiDsOutboxListenerRegistrar = MultiDsOutboxListenerRegistrar_1 = class MultiDsOutboxListenerRegistrar {
70
+ moduleRef;
71
+ dataSourceNames;
72
+ logger = new common_1.Logger(MultiDsOutboxListenerRegistrar_1.name);
73
+ constructor(moduleRef, dataSourceNames) {
74
+ this.moduleRef = moduleRef;
75
+ this.dataSourceNames = dataSourceNames;
76
+ }
77
+ /**
78
+ * Register a listener with the per-dataSource
79
+ * {@link OutboxListenerRegistry} whose dataSource owns
80
+ * `listener.eventType`. Throws when the event-type cannot be
81
+ * routed unambiguously — see {@link resolveDataSourceByEventTypeName}.
82
+ */
83
+ register(listener) {
84
+ const eventTypeRegistries = this.collectEventTypeRegistries();
85
+ const dataSource = (0, event_type_resolver_1.resolveDataSourceByEventTypeName)(listener.eventType, eventTypeRegistries);
86
+ const registry = this.moduleRef.get((0, token_utils_1.getOutboxListenerRegistryToken)(dataSource), { strict: false });
87
+ registry.register(listener);
88
+ this.logger.debug(`Routed listener id='${listener.id}' for event '${listener.eventType}' → dataSource '${dataSource}'`);
89
+ }
90
+ /**
91
+ * Collect a Map of dataSource name → {@link EventTypeRegistry}
92
+ * from the live registrations Set. Resolved fresh per `register`
93
+ * call so a registrar surviving across module rebuilds (test
94
+ * scenarios) sees the current state instead of a snapshot taken
95
+ * at construction time.
96
+ */
97
+ collectEventTypeRegistries() {
98
+ const registries = new Map();
99
+ for (const dataSource of this.dataSourceNames.keys()) {
100
+ const registry = this.moduleRef.get((0, token_utils_1.getEventTypeRegistryToken)(dataSource), { strict: false });
101
+ registries.set(dataSource, registry);
102
+ }
103
+ return registries;
104
+ }
105
+ };
106
+ exports.MultiDsOutboxListenerRegistrar = MultiDsOutboxListenerRegistrar;
107
+ exports.MultiDsOutboxListenerRegistrar = MultiDsOutboxListenerRegistrar = MultiDsOutboxListenerRegistrar_1 = __decorate([
108
+ (0, common_1.Injectable)(),
109
+ __param(1, (0, common_1.Inject)(outbox_event_publisher_1.OUTBOX_DATA_SOURCE_NAMES)),
110
+ __metadata("design:paramtypes", [core_1.ModuleRef, Object])
111
+ ], MultiDsOutboxListenerRegistrar);
112
+ //# sourceMappingURL=multi-ds-listener-registrar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-ds-listener-registrar.js","sourceRoot":"","sources":["../../src/registry/multi-ds-listener-registrar.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAA4D;AAC5D,uCAAyC;AAEzC,iFAG8C;AAE9C,8EAAwF;AACxF,uDAG+B;AAI/B;;;;;;;;;;;;;;;;;GAiBG;AACU,QAAA,+BAA+B,GAAG,MAAM,CAAC,GAAG,CACvD,sDAAsD,CACvD,CAAC;AASF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEI,IAAM,8BAA8B,sCAApC,MAAM,8BAA8B;IAItB;IAEA;IALF,MAAM,GAAG,IAAI,eAAM,CAAC,gCAA8B,CAAC,IAAI,CAAC,CAAC;IAE1E,YACmB,SAAoB,EAEpB,eAAsC;QAFtC,cAAS,GAAT,SAAS,CAAW;QAEpB,oBAAe,GAAf,eAAe,CAAuB;IACtD,CAAC;IAEJ;;;;;OAKG;IACH,QAAQ,CAAC,QAAgC;QACvC,MAAM,mBAAmB,GAAG,IAAI,CAAC,0BAA0B,EAAE,CAAC;QAC9D,MAAM,UAAU,GAAG,IAAA,sDAAgC,EACjD,QAAQ,CAAC,SAAS,EAClB,mBAAmB,CACpB,CAAC;QAEF,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CACjC,IAAA,4CAA8B,EAAC,UAAU,CAAC,EAC1C,EAAE,MAAM,EAAE,KAAK,EAAE,CAClB,CAAC;QACF,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAE5B,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,uBAAuB,QAAQ,CAAC,EAAE,gBAAgB,QAAQ,CAAC,SAAS,mBAAmB,UAAU,GAAG,CACrG,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACK,0BAA0B;QAChC,MAAM,UAAU,GAAG,IAAI,GAAG,EAA6B,CAAC;QACxD,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC;YACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CACjC,IAAA,uCAAyB,EAAC,UAAU,CAAC,EACrC,EAAE,MAAM,EAAE,KAAK,EAAE,CAClB,CAAC;YACF,UAAU,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QACvC,CAAC;QACD,OAAO,UAAU,CAAC;IACpB,CAAC;CACF,CAAA;AAnDY,wEAA8B;yCAA9B,8BAA8B;IAD1C,IAAA,mBAAU,GAAE;IAMR,WAAA,IAAA,eAAM,EAAC,iDAAwB,CAAC,CAAA;qCADL,gBAAS;GAJ5B,8BAA8B,CAmD1C"}
@@ -0,0 +1,66 @@
1
+ import { type OnModuleInit, type Type } from '@nestjs/common';
2
+ import { DiscoveryService, ModuleRef } from '@nestjs/core';
3
+ import { TransactionManager } from '@nestjs-transactional/core';
4
+ import { type OutboxDataSourceNames } from '../dispatcher/outbox-event-publisher';
5
+ /**
6
+ * Bootstrap-time scanner that walks every provider in the running
7
+ * Nest application, finds classes decorated with
8
+ * `@OutboxEventsHandler`, and registers their `handle` method with
9
+ * the per-dataSource {@link OutboxListenerRegistry} that owns each
10
+ * decorated event class.
11
+ *
12
+ * **Per-dataSource routing (Phase 14.3.1).** Multi-`OutboxModule.forRoot`
13
+ * deployments register events to per-DS `EventTypeRegistry`
14
+ * instances via `OutboxModule.forFeature(events, { dataSource })`.
15
+ * The scanner walks every per-DS `EventTypeRegistry`, resolves which
16
+ * dataSource owns each handler's event classes (via
17
+ * {@link resolveDataSourceByEventTypeName}), and registers the
18
+ * handler with the matching per-DS `OutboxListenerRegistry`.
19
+ *
20
+ * Single-dataSource deployments see the same behaviour they always
21
+ * have: events go to the only registry, handlers register against it.
22
+ *
23
+ * **Constraint: handler events must come from a single dataSource.**
24
+ * A handler subscribing to events spanning multiple dataSources is a
25
+ * configuration error — handlers conceptually belong to one
26
+ * bounded context. The scanner throws at bootstrap with an
27
+ * actionable message naming the offending events and dataSources.
28
+ *
29
+ * Scanning is class-level only. The handler class must expose a
30
+ * `handle(event): Promise<void>` method (enforce this at the type
31
+ * level by implementing `IOutboxEventHandler`).
32
+ *
33
+ * Each registered entry carries a pre-bound `invoke` closure that
34
+ * applies `REQUIRES_NEW` transaction semantics when
35
+ * `newTransaction: true` (the default) and invokes the method
36
+ * directly otherwise.
37
+ *
38
+ * Registration runs in `onModuleInit` so all providers have been
39
+ * instantiated by the time the scan happens.
40
+ */
41
+ export declare class OutboxListenerScanner implements OnModuleInit {
42
+ private readonly discovery;
43
+ private readonly moduleRef;
44
+ private readonly dataSourceNames;
45
+ private readonly transactionManager;
46
+ private readonly logger;
47
+ constructor(discovery: DiscoveryService, moduleRef: ModuleRef, dataSourceNames: OutboxDataSourceNames, transactionManager: TransactionManager);
48
+ onModuleInit(): void;
49
+ /**
50
+ * Resolve which dataSource a handler belongs to by looking up each
51
+ * of its decorated events in the per-DS event-type registries. The
52
+ * handler's events must all resolve to the same dataSource —
53
+ * cross-DS handlers are rejected at bootstrap.
54
+ */
55
+ private resolveHandlerDataSource;
56
+ private collectEventTypeRegistries;
57
+ private collectListenerRegistries;
58
+ }
59
+ /**
60
+ * Compose the stable listener id. Always ends with `#${EventName}`
61
+ * so a single class handling multiple event types gets distinct ids
62
+ * — the registry requires globally-unique ids. Exported so consumers
63
+ * can pre-compute the id for explicit registration / tests.
64
+ */
65
+ export declare function composeListenerId(baseId: string, eventType: Type): string;
66
+ //# sourceMappingURL=outbox-listener-scanner.d.ts.map
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var OutboxListenerScanner_1;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.OutboxListenerScanner = void 0;
17
+ exports.composeListenerId = composeListenerId;
18
+ const common_1 = require("@nestjs/common");
19
+ const core_1 = require("@nestjs/core");
20
+ const core_2 = require("@nestjs-transactional/core");
21
+ const outbox_events_handler_decorator_1 = require("../decorators/outbox-events-handler.decorator");
22
+ const outbox_event_publisher_1 = require("../dispatcher/outbox-event-publisher");
23
+ const event_type_resolver_1 = require("../serialization/event-type-resolver");
24
+ const token_utils_1 = require("../tokens/token-utils");
25
+ const errors_1 = require("../types/errors");
26
+ /**
27
+ * Bootstrap-time scanner that walks every provider in the running
28
+ * Nest application, finds classes decorated with
29
+ * `@OutboxEventsHandler`, and registers their `handle` method with
30
+ * the per-dataSource {@link OutboxListenerRegistry} that owns each
31
+ * decorated event class.
32
+ *
33
+ * **Per-dataSource routing (Phase 14.3.1).** Multi-`OutboxModule.forRoot`
34
+ * deployments register events to per-DS `EventTypeRegistry`
35
+ * instances via `OutboxModule.forFeature(events, { dataSource })`.
36
+ * The scanner walks every per-DS `EventTypeRegistry`, resolves which
37
+ * dataSource owns each handler's event classes (via
38
+ * {@link resolveDataSourceByEventTypeName}), and registers the
39
+ * handler with the matching per-DS `OutboxListenerRegistry`.
40
+ *
41
+ * Single-dataSource deployments see the same behaviour they always
42
+ * have: events go to the only registry, handlers register against it.
43
+ *
44
+ * **Constraint: handler events must come from a single dataSource.**
45
+ * A handler subscribing to events spanning multiple dataSources is a
46
+ * configuration error — handlers conceptually belong to one
47
+ * bounded context. The scanner throws at bootstrap with an
48
+ * actionable message naming the offending events and dataSources.
49
+ *
50
+ * Scanning is class-level only. The handler class must expose a
51
+ * `handle(event): Promise<void>` method (enforce this at the type
52
+ * level by implementing `IOutboxEventHandler`).
53
+ *
54
+ * Each registered entry carries a pre-bound `invoke` closure that
55
+ * applies `REQUIRES_NEW` transaction semantics when
56
+ * `newTransaction: true` (the default) and invokes the method
57
+ * directly otherwise.
58
+ *
59
+ * Registration runs in `onModuleInit` so all providers have been
60
+ * instantiated by the time the scan happens.
61
+ */
62
+ let OutboxListenerScanner = OutboxListenerScanner_1 = class OutboxListenerScanner {
63
+ discovery;
64
+ moduleRef;
65
+ dataSourceNames;
66
+ transactionManager;
67
+ logger = new common_1.Logger(OutboxListenerScanner_1.name);
68
+ constructor(discovery, moduleRef, dataSourceNames, transactionManager) {
69
+ this.discovery = discovery;
70
+ this.moduleRef = moduleRef;
71
+ this.dataSourceNames = dataSourceNames;
72
+ this.transactionManager = transactionManager;
73
+ }
74
+ onModuleInit() {
75
+ const eventTypeRegistries = this.collectEventTypeRegistries();
76
+ const listenerRegistries = this.collectListenerRegistries();
77
+ const providers = this.discovery.getProviders();
78
+ for (const wrapper of providers) {
79
+ if (wrapper.metatype === null ||
80
+ typeof wrapper.metatype !== 'function' ||
81
+ wrapper.instance === null ||
82
+ wrapper.instance === undefined) {
83
+ continue;
84
+ }
85
+ const metadata = (0, outbox_events_handler_decorator_1.getOutboxEventsHandlerMetadata)(wrapper.metatype);
86
+ if (metadata === undefined) {
87
+ continue;
88
+ }
89
+ const className = wrapper.metatype.name ?? 'anonymous';
90
+ const instance = wrapper.instance;
91
+ const rawHandle = instance.handle;
92
+ if (typeof rawHandle !== 'function') {
93
+ this.logger.warn(`@OutboxEventsHandler on ${className}: missing \`handle(event)\` method — skipping`);
94
+ continue;
95
+ }
96
+ const dataSource = this.resolveHandlerDataSource(className, metadata.eventTypes, eventTypeRegistries);
97
+ const registry = listenerRegistries.get(dataSource);
98
+ if (registry === undefined) {
99
+ // Defensive — every dataSource in `dataSourceNames` is also
100
+ // in `listenerRegistries` because both maps are built from
101
+ // the same Set. Throw rather than silently skip.
102
+ throw new errors_1.OutboxError(`@OutboxEventsHandler on ${className}: no OutboxListenerRegistry registered for ` +
103
+ `dataSource '${dataSource}'. This is a framework bug — please open an issue.`);
104
+ }
105
+ const boundHandle = rawHandle.bind(instance);
106
+ const manager = this.transactionManager;
107
+ const { newTransaction } = metadata;
108
+ const invoke = async (event) => {
109
+ if (newTransaction) {
110
+ await manager.run({ propagation: core_2.PropagationMode.REQUIRES_NEW }, async () => {
111
+ await boundHandle(event);
112
+ });
113
+ }
114
+ else {
115
+ await boundHandle(event);
116
+ }
117
+ };
118
+ const baseId = metadata.id ?? instance.constructor.name;
119
+ for (const eventType of metadata.eventTypes) {
120
+ const listenerId = composeListenerId(baseId, eventType);
121
+ registry.register({
122
+ id: listenerId,
123
+ eventType: eventType.name,
124
+ invoke,
125
+ });
126
+ }
127
+ }
128
+ }
129
+ /**
130
+ * Resolve which dataSource a handler belongs to by looking up each
131
+ * of its decorated events in the per-DS event-type registries. The
132
+ * handler's events must all resolve to the same dataSource —
133
+ * cross-DS handlers are rejected at bootstrap.
134
+ */
135
+ resolveHandlerDataSource(className, eventTypes, eventTypeRegistries) {
136
+ const dataSourcePerEvent = new Map();
137
+ for (const eventType of eventTypes) {
138
+ const dataSource = (0, event_type_resolver_1.resolveDataSourceByEventTypeName)(eventType.name, eventTypeRegistries);
139
+ dataSourcePerEvent.set(eventType.name, dataSource);
140
+ }
141
+ const uniqueDataSources = new Set(dataSourcePerEvent.values());
142
+ if (uniqueDataSources.size > 1) {
143
+ const breakdown = [...dataSourcePerEvent]
144
+ .map(([eventName, dataSource]) => ` ${eventName} → '${dataSource}'`)
145
+ .join('\n');
146
+ throw new errors_1.OutboxError(`@OutboxEventsHandler on ${className}: events span multiple dataSources:\n${breakdown}\n` +
147
+ `Handlers must be scoped to a single dataSource. Either:\n` +
148
+ ` • register the events under the same dataSource (OutboxModule.forFeature(...))\n` +
149
+ ` • split the handler into separate classes per dataSource\n` +
150
+ ` • register manually via getOutboxListenerRegistryToken(ds).register(...)`);
151
+ }
152
+ // Exactly one entry — uniqueDataSources is non-empty because
153
+ // metadata.eventTypes is enforced non-empty by the decorator.
154
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
155
+ return [...uniqueDataSources][0];
156
+ }
157
+ collectEventTypeRegistries() {
158
+ const registries = new Map();
159
+ for (const dataSource of this.dataSourceNames.keys()) {
160
+ registries.set(dataSource, this.moduleRef.get((0, token_utils_1.getEventTypeRegistryToken)(dataSource), {
161
+ strict: false,
162
+ }));
163
+ }
164
+ return registries;
165
+ }
166
+ collectListenerRegistries() {
167
+ const registries = new Map();
168
+ for (const dataSource of this.dataSourceNames.keys()) {
169
+ registries.set(dataSource, this.moduleRef.get((0, token_utils_1.getOutboxListenerRegistryToken)(dataSource), {
170
+ strict: false,
171
+ }));
172
+ }
173
+ return registries;
174
+ }
175
+ };
176
+ exports.OutboxListenerScanner = OutboxListenerScanner;
177
+ exports.OutboxListenerScanner = OutboxListenerScanner = OutboxListenerScanner_1 = __decorate([
178
+ (0, common_1.Injectable)(),
179
+ __param(2, (0, common_1.Inject)(outbox_event_publisher_1.OUTBOX_DATA_SOURCE_NAMES)),
180
+ __metadata("design:paramtypes", [core_1.DiscoveryService,
181
+ core_1.ModuleRef, Object, core_2.TransactionManager])
182
+ ], OutboxListenerScanner);
183
+ /**
184
+ * Compose the stable listener id. Always ends with `#${EventName}`
185
+ * so a single class handling multiple event types gets distinct ids
186
+ * — the registry requires globally-unique ids. Exported so consumers
187
+ * can pre-compute the id for explicit registration / tests.
188
+ */
189
+ function composeListenerId(baseId, eventType) {
190
+ return `${baseId}#${eventType.name}`;
191
+ }
192
+ //# sourceMappingURL=outbox-listener-scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbox-listener-scanner.js","sourceRoot":"","sources":["../../src/registry/outbox-listener-scanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,112 @@
1
+ import type { EventPublication, NewEventPublication } from '../types/event-publication';
2
+ import type { PublicationStatus } from '../types/publication-status';
3
+ /**
4
+ * Options accepted by {@link EventPublicationRepository.updateStatus}.
5
+ */
6
+ export interface UpdateStatusOptions {
7
+ /** Set when transitioning to `COMPLETED` so downstream tools can audit. */
8
+ readonly completionDate?: Date;
9
+ /** Populated when transitioning to `FAILED`. */
10
+ readonly failureReason?: string;
11
+ /**
12
+ * Increment `completionAttempts` by one as part of the update.
13
+ * Used when a listener picks up a publication via `tryClaim`.
14
+ */
15
+ readonly incrementAttempts?: boolean;
16
+ /** Set when transitioning to `RESUBMITTED`. */
17
+ readonly lastResubmissionDate?: Date;
18
+ }
19
+ /**
20
+ * Options accepted by {@link EventPublicationRepository.findCompleted}.
21
+ */
22
+ export interface FindCompletedOptions {
23
+ /** Only include publications whose `completionDate` is strictly before this. */
24
+ readonly olderThan?: Date;
25
+ /** Cap the number of returned rows. */
26
+ readonly limit?: number;
27
+ }
28
+ /**
29
+ * Options accepted by {@link EventPublicationRepository.findFailed}.
30
+ */
31
+ export interface FindFailedOptions {
32
+ /** Minimum age (ms since `publicationDate`) for the row to be included. */
33
+ readonly minAge?: number;
34
+ /** Only include rows with `completionAttempts <= maxAttempts`. */
35
+ readonly maxAttempts?: number;
36
+ }
37
+ /**
38
+ * Persistence SPI for event publications.
39
+ *
40
+ * Implementations provide storage (TypeORM, Prisma, MongoDB, ...).
41
+ *
42
+ * All mutating operations are expected to participate in the ambient
43
+ * transaction (through the `AsyncLocalStorage` context provided by
44
+ * `@nestjs-transactional/core`) when one is active, so publication
45
+ * rows are committed atomically with the business data.
46
+ */
47
+ export interface EventPublicationRepository {
48
+ /**
49
+ * Create multiple publications atomically. Called during event
50
+ * publishing — one entry per listener registered for the event.
51
+ */
52
+ createAll(publications: NewEventPublication[]): Promise<EventPublication[]>;
53
+ /** Lookup a publication by its id, or `null` when not found. */
54
+ findById(id: string): Promise<EventPublication | null>;
55
+ /**
56
+ * Update a publication's status. Used for lifecycle transitions:
57
+ * - `PUBLISHED` → `PROCESSING` (a listener has picked it up)
58
+ * - `PROCESSING` → `COMPLETED` (success)
59
+ * - `PROCESSING` → `FAILED` (exception)
60
+ * - `FAILED` → `RESUBMITTED` (operator resubmit)
61
+ */
62
+ updateStatus(id: string, status: PublicationStatus, options?: UpdateStatusOptions): Promise<void>;
63
+ /**
64
+ * Atomically claim a publication: transition `PUBLISHED` or
65
+ * `RESUBMITTED` → `PROCESSING` (and increment `completionAttempts`)
66
+ * iff the current status is one of those. Returns `true` when the
67
+ * claim succeeded, `false` otherwise — used by the dispatcher to
68
+ * prevent double-processing across workers.
69
+ */
70
+ tryClaim(id: string): Promise<boolean>;
71
+ /**
72
+ * Find publications ready for processing (status `PUBLISHED` or
73
+ * `RESUBMITTED`). Production implementations should use
74
+ * `SELECT ... FOR UPDATE SKIP LOCKED` (or equivalent) to be safe
75
+ * against concurrent workers.
76
+ */
77
+ findReadyForProcessing(limit: number): Promise<EventPublication[]>;
78
+ /**
79
+ * Find publications in the given statuses whose `publicationDate` is
80
+ * strictly before `beforeDate`. Used by the staleness monitor.
81
+ */
82
+ findStale(beforeDate: Date, statuses: PublicationStatus[]): Promise<EventPublication[]>;
83
+ /** Find all completed publications, with optional pagination / cutoff. */
84
+ findCompleted(options?: FindCompletedOptions): Promise<EventPublication[]>;
85
+ /**
86
+ * Find all incomplete publications (status other than `COMPLETED`).
87
+ * Backs the `IncompleteEventPublications` operator API.
88
+ */
89
+ findIncomplete(): Promise<EventPublication[]>;
90
+ /**
91
+ * Find failed publications. Backs the `FailedEventPublications`
92
+ * operator API.
93
+ */
94
+ findFailed(options?: FindFailedOptions): Promise<EventPublication[]>;
95
+ /**
96
+ * Delete completed publications whose `completionDate` is strictly
97
+ * before `olderThan` (or all completed publications when the argument
98
+ * is omitted). Returns the number of rows removed.
99
+ */
100
+ deleteCompleted(olderThan?: Date): Promise<number>;
101
+ /**
102
+ * Archive a completed publication. Implementation-specific — typical
103
+ * TypeORM backend copies the row to an archive table before removing
104
+ * it from the hot queue.
105
+ */
106
+ archiveCompleted(id: string): Promise<void>;
107
+ /** Delete a single publication by id. Used for `DELETE` completion mode. */
108
+ delete(id: string): Promise<void>;
109
+ }
110
+ /** DI token for the active {@link EventPublicationRepository}. */
111
+ export declare const EVENT_PUBLICATION_REPOSITORY: unique symbol;
112
+ //# sourceMappingURL=event-publication-repository.d.ts.map
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EVENT_PUBLICATION_REPOSITORY = void 0;
4
+ /** DI token for the active {@link EventPublicationRepository}. */
5
+ exports.EVENT_PUBLICATION_REPOSITORY = Symbol('EVENT_PUBLICATION_REPOSITORY');
6
+ //# sourceMappingURL=event-publication-repository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-publication-repository.js","sourceRoot":"","sources":["../../src/repository/event-publication-repository.ts"],"names":[],"mappings":";;;AAiIA,kEAAkE;AACrD,QAAA,4BAA4B,GAAG,MAAM,CAAC,8BAA8B,CAAC,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Serializes events for storage in the publication log and deserializes
3
+ * them back into application objects when a publication is picked up by
4
+ * a worker.
5
+ *
6
+ * Inspired by Spring Modulith's `EventSerializer`.
7
+ *
8
+ * Implementations are expected to:
9
+ * - throw `SerializationError` when encoding fails (circular refs,
10
+ * unsupported values);
11
+ * - throw `DeserializationError` when decoding fails (malformed payload,
12
+ * schema drift, missing type);
13
+ * - be deterministic so the same input yields the same output.
14
+ */
15
+ export interface EventSerializer {
16
+ /** Encode an event payload into a string suitable for row storage. */
17
+ serialize(event: unknown): string;
18
+ /**
19
+ * Decode a previously-serialized payload back into an application
20
+ * object. `eventType` is the class name recorded alongside the
21
+ * payload — the implementation may use it to look up a class
22
+ * prototype via an {@link EventTypeRegistry} or similar.
23
+ */
24
+ deserialize(serialized: string, eventType: string): unknown;
25
+ }
26
+ /** DI token for the active {@link EventSerializer} implementation. */
27
+ export declare const EVENT_SERIALIZER: unique symbol;
28
+ //# sourceMappingURL=event-serializer.d.ts.map
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EVENT_SERIALIZER = void 0;
4
+ /** DI token for the active {@link EventSerializer} implementation. */
5
+ exports.EVENT_SERIALIZER = Symbol('EVENT_SERIALIZER');
6
+ //# sourceMappingURL=event-serializer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-serializer.js","sourceRoot":"","sources":["../../src/serialization/event-serializer.ts"],"names":[],"mappings":";;;AA0BA,sEAAsE;AACzD,QAAA,gBAAgB,GAAG,MAAM,CAAC,kBAAkB,CAAC,CAAC"}