@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,39 @@
1
+ import { type EventPublicationRepository } from '../repository/event-publication-repository';
2
+ import type { StalenessConfig } from '../types/staleness-config';
3
+ /**
4
+ * Periodic watchdog that flips publications stuck in non-terminal
5
+ * states to `FAILED` once they have been in that state for longer
6
+ * than the configured threshold.
7
+ *
8
+ * Per-state thresholds live in {@link StalenessConfig} and a value of
9
+ * `0` disables the corresponding check. When every threshold is `0`,
10
+ * {@link start} is a no-op — we do not schedule the polling loop just
11
+ * to do nothing on every tick.
12
+ *
13
+ * Note: the `PROCESSING` / `RESUBMITTED` thresholds are evaluated
14
+ * against the publication's `publicationDate`, not against the moment
15
+ * it entered the current state. Real backends (outbox-typeorm) can
16
+ * refine this with per-state timestamps; the in-memory reference
17
+ * keeps it simple.
18
+ */
19
+ export declare class StalenessMonitor {
20
+ private readonly repository;
21
+ private readonly config;
22
+ private readonly logger;
23
+ private running;
24
+ private monitorLoop;
25
+ constructor(repository: EventPublicationRepository, config: StalenessConfig);
26
+ /** Start the watchdog. Idempotent. */
27
+ start(): void;
28
+ /** Stop the watchdog and cancel any pending scheduled tick. */
29
+ stop(): void;
30
+ /**
31
+ * Run a single staleness sweep — invoked both by the scheduled loop
32
+ * and directly by tests / one-shot tooling. Never rejects; infra
33
+ * failures are caught and logged.
34
+ */
35
+ checkStaleness(): Promise<void>;
36
+ private scheduleNext;
37
+ private markStale;
38
+ }
39
+ //# sourceMappingURL=staleness-monitor.d.ts.map
@@ -0,0 +1,118 @@
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 StalenessMonitor_1;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.StalenessMonitor = void 0;
17
+ const common_1 = require("@nestjs/common");
18
+ const event_publication_repository_1 = require("../repository/event-publication-repository");
19
+ const publication_status_1 = require("../types/publication-status");
20
+ /**
21
+ * Periodic watchdog that flips publications stuck in non-terminal
22
+ * states to `FAILED` once they have been in that state for longer
23
+ * than the configured threshold.
24
+ *
25
+ * Per-state thresholds live in {@link StalenessConfig} and a value of
26
+ * `0` disables the corresponding check. When every threshold is `0`,
27
+ * {@link start} is a no-op — we do not schedule the polling loop just
28
+ * to do nothing on every tick.
29
+ *
30
+ * Note: the `PROCESSING` / `RESUBMITTED` thresholds are evaluated
31
+ * against the publication's `publicationDate`, not against the moment
32
+ * it entered the current state. Real backends (outbox-typeorm) can
33
+ * refine this with per-state timestamps; the in-memory reference
34
+ * keeps it simple.
35
+ */
36
+ let StalenessMonitor = StalenessMonitor_1 = class StalenessMonitor {
37
+ repository;
38
+ config;
39
+ logger = new common_1.Logger(StalenessMonitor_1.name);
40
+ running = false;
41
+ monitorLoop = null;
42
+ constructor(repository, config) {
43
+ this.repository = repository;
44
+ this.config = config;
45
+ }
46
+ /** Start the watchdog. Idempotent. */
47
+ start() {
48
+ if (this.running) {
49
+ return;
50
+ }
51
+ if (this.config.published === 0 &&
52
+ this.config.processing === 0 &&
53
+ this.config.resubmitted === 0) {
54
+ this.logger.log('StalenessMonitor disabled (all thresholds = 0)');
55
+ return;
56
+ }
57
+ this.running = true;
58
+ this.scheduleNext();
59
+ this.logger.log(`StalenessMonitor started (interval: ${this.config.monitorInterval}ms)`);
60
+ }
61
+ /** Stop the watchdog and cancel any pending scheduled tick. */
62
+ stop() {
63
+ this.running = false;
64
+ if (this.monitorLoop !== null) {
65
+ clearTimeout(this.monitorLoop);
66
+ this.monitorLoop = null;
67
+ }
68
+ }
69
+ /**
70
+ * Run a single staleness sweep — invoked both by the scheduled loop
71
+ * and directly by tests / one-shot tooling. Never rejects; infra
72
+ * failures are caught and logged.
73
+ */
74
+ async checkStaleness() {
75
+ try {
76
+ const now = Date.now();
77
+ if (this.config.published > 0) {
78
+ await this.markStale(new Date(now - this.config.published), [publication_status_1.PublicationStatus.PUBLISHED], 'Publication stale in PUBLISHED state');
79
+ }
80
+ if (this.config.processing > 0) {
81
+ await this.markStale(new Date(now - this.config.processing), [publication_status_1.PublicationStatus.PROCESSING], 'Publication stale in PROCESSING state');
82
+ }
83
+ if (this.config.resubmitted > 0) {
84
+ await this.markStale(new Date(now - this.config.resubmitted), [publication_status_1.PublicationStatus.RESUBMITTED], 'Publication stale in RESUBMITTED state');
85
+ }
86
+ }
87
+ catch (err) {
88
+ this.logger.error('Staleness check failed', err instanceof Error ? err.stack : String(err));
89
+ }
90
+ }
91
+ scheduleNext() {
92
+ if (!this.running) {
93
+ return;
94
+ }
95
+ this.monitorLoop = setTimeout(() => {
96
+ void this.checkStaleness().finally(() => this.scheduleNext());
97
+ }, this.config.monitorInterval);
98
+ }
99
+ async markStale(beforeDate, statuses, reason) {
100
+ const stale = await this.repository.findStale(beforeDate, statuses);
101
+ if (stale.length === 0) {
102
+ return;
103
+ }
104
+ this.logger.warn(`Found ${stale.length} stale publication(s) in states [${statuses.join(',')}]`);
105
+ for (const pub of stale) {
106
+ await this.repository.updateStatus(pub.id, publication_status_1.PublicationStatus.FAILED, {
107
+ failureReason: reason,
108
+ });
109
+ }
110
+ }
111
+ };
112
+ exports.StalenessMonitor = StalenessMonitor;
113
+ exports.StalenessMonitor = StalenessMonitor = StalenessMonitor_1 = __decorate([
114
+ (0, common_1.Injectable)(),
115
+ __param(0, (0, common_1.Inject)(event_publication_repository_1.EVENT_PUBLICATION_REPOSITORY)),
116
+ __metadata("design:paramtypes", [Object, Object])
117
+ ], StalenessMonitor);
118
+ //# sourceMappingURL=staleness-monitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"staleness-monitor.js","sourceRoot":"","sources":["../../src/recovery/staleness-monitor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import { type OnApplicationBootstrap } from '@nestjs/common';
2
+ import { IncompleteEventPublications } from '../api/incomplete-event-publications';
3
+ /** DI token for {@link OutboxRecoveryOptions}. */
4
+ export declare const OUTBOX_RECOVERY_OPTIONS: unique symbol;
5
+ /**
6
+ * Runtime configuration for {@link StartupRecoveryService}. Wired by
7
+ * `OutboxModule.forRoot` from the module-level options.
8
+ */
9
+ export interface OutboxRecoveryOptions {
10
+ /**
11
+ * When `true`, every non-`COMPLETED` publication found at
12
+ * application bootstrap is transitioned to `RESUBMITTED` so the
13
+ * processor picks it up after the previous-run crash.
14
+ */
15
+ readonly republishOnStartup: boolean;
16
+ /**
17
+ * Maximum number of publications to resubmit in a single bootstrap
18
+ * pass. Defaults to 1000 when omitted.
19
+ */
20
+ readonly startupBatchSize?: number;
21
+ }
22
+ /**
23
+ * `OnApplicationBootstrap` hook that optionally resubmits every
24
+ * publication left in a non-terminal state when the previous process
25
+ * died — matches Spring Modulith's `Republishing on Startup`
26
+ * behaviour.
27
+ *
28
+ * Skips entirely when `republishOnStartup` is `false` (the default).
29
+ */
30
+ export declare class StartupRecoveryService implements OnApplicationBootstrap {
31
+ private readonly incomplete;
32
+ private readonly options;
33
+ private readonly logger;
34
+ constructor(incomplete: IncompleteEventPublications, options: OutboxRecoveryOptions);
35
+ onApplicationBootstrap(): Promise<void>;
36
+ }
37
+ //# sourceMappingURL=startup-recovery.d.ts.map
@@ -0,0 +1,55 @@
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 StartupRecoveryService_1;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.StartupRecoveryService = exports.OUTBOX_RECOVERY_OPTIONS = void 0;
17
+ const common_1 = require("@nestjs/common");
18
+ const incomplete_event_publications_1 = require("../api/incomplete-event-publications");
19
+ const resubmission_options_1 = require("../types/resubmission-options");
20
+ /** DI token for {@link OutboxRecoveryOptions}. */
21
+ exports.OUTBOX_RECOVERY_OPTIONS = Symbol('OUTBOX_RECOVERY_OPTIONS');
22
+ /**
23
+ * `OnApplicationBootstrap` hook that optionally resubmits every
24
+ * publication left in a non-terminal state when the previous process
25
+ * died — matches Spring Modulith's `Republishing on Startup`
26
+ * behaviour.
27
+ *
28
+ * Skips entirely when `republishOnStartup` is `false` (the default).
29
+ */
30
+ let StartupRecoveryService = StartupRecoveryService_1 = class StartupRecoveryService {
31
+ incomplete;
32
+ options;
33
+ logger = new common_1.Logger(StartupRecoveryService_1.name);
34
+ constructor(incomplete, options) {
35
+ this.incomplete = incomplete;
36
+ this.options = options;
37
+ }
38
+ async onApplicationBootstrap() {
39
+ if (!this.options.republishOnStartup) {
40
+ return;
41
+ }
42
+ this.logger.log('Checking for incomplete publications to republish...');
43
+ const count = await this.incomplete.resubmitIncompletePublications(resubmission_options_1.ResubmissionOptions.defaults().withBatchSize(this.options.startupBatchSize ?? 1000));
44
+ if (count > 0) {
45
+ this.logger.log(`Resubmitted ${count} incomplete publication(s) from previous run`);
46
+ }
47
+ }
48
+ };
49
+ exports.StartupRecoveryService = StartupRecoveryService;
50
+ exports.StartupRecoveryService = StartupRecoveryService = StartupRecoveryService_1 = __decorate([
51
+ (0, common_1.Injectable)(),
52
+ __param(1, (0, common_1.Inject)(exports.OUTBOX_RECOVERY_OPTIONS)),
53
+ __metadata("design:paramtypes", [incomplete_event_publications_1.IncompleteEventPublications, Object])
54
+ ], StartupRecoveryService);
55
+ //# sourceMappingURL=startup-recovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startup-recovery.js","sourceRoot":"","sources":["../../src/recovery/startup-recovery.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyF;AAEzF,wFAAmF;AACnF,wEAAoE;AAEpE,kDAAkD;AACrC,QAAA,uBAAuB,GAAG,MAAM,CAAC,yBAAyB,CAAC,CAAC;AAoBzE;;;;;;;GAOG;AAEI,IAAM,sBAAsB,8BAA5B,MAAM,sBAAsB;IAId;IAEA;IALF,MAAM,GAAG,IAAI,eAAM,CAAC,wBAAsB,CAAC,IAAI,CAAC,CAAC;IAElE,YACmB,UAAuC,EAEvC,OAA8B;QAF9B,eAAU,GAAV,UAAU,CAA6B;QAEvC,YAAO,GAAP,OAAO,CAAuB;IAC9C,CAAC;IAEJ,KAAK,CAAC,sBAAsB;QAC1B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC;YACrC,OAAO;QACT,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC;QAExE,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,8BAA8B,CAChE,0CAAmB,CAAC,QAAQ,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,IAAI,IAAI,CAAC,CACpF,CAAC;QAEF,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,eAAe,KAAK,8CAA8C,CAAC,CAAC;QACtF,CAAC;IACH,CAAC;CACF,CAAA;AAxBY,wDAAsB;iCAAtB,sBAAsB;IADlC,IAAA,mBAAU,GAAE;IAMR,WAAA,IAAA,eAAM,EAAC,+BAAuB,CAAC,CAAA;qCADH,2DAA2B;GAJ/C,sBAAsB,CAwBlC"}
@@ -0,0 +1,70 @@
1
+ import type { EventPublicationRepository } from '../repository/event-publication-repository';
2
+ import type { EventSerializer } from '../serialization/event-serializer';
3
+ import { CompletionMode } from '../types/completion-mode';
4
+ import type { EventPublication } from '../types/event-publication';
5
+ /**
6
+ * Central service that coordinates the event publication lifecycle.
7
+ *
8
+ * Responsibilities:
9
+ * - persist publication entries (one per listener) atomically with the
10
+ * business transaction via the injected {@link EventPublicationRepository};
11
+ * - transition lifecycle states (`PUBLISHED` → `PROCESSING` →
12
+ * `COMPLETED` / `FAILED` → `RESUBMITTED`);
13
+ * - hand out deserialized event payloads ready for listener invocation.
14
+ *
15
+ * Does NOT invoke listeners — that is the dispatcher's job (Phase 5.6).
16
+ * Inspired by the internals of Spring Modulith's Event Publication
17
+ * Registry.
18
+ */
19
+ export declare class EventPublicationRegistry {
20
+ private readonly repository;
21
+ private readonly serializer;
22
+ constructor(repository: EventPublicationRepository, serializer: EventSerializer);
23
+ /**
24
+ * Persist one publication entry per listener for a single event.
25
+ *
26
+ * Must be called inside an active transaction so the entries are
27
+ * committed atomically with the business write. Returns an empty
28
+ * array when there are no listeners — no repository call is made.
29
+ *
30
+ * The event's `constructor.name` is stored as `eventType` and used
31
+ * by the deserializer to look up the class at delivery time.
32
+ */
33
+ publish(event: unknown, listenerIds: readonly string[]): Promise<EventPublication[]>;
34
+ /**
35
+ * Atomically transition the publication from `PUBLISHED` or
36
+ * `RESUBMITTED` to `PROCESSING`. Returns `true` when the claim
37
+ * succeeded, `false` when another worker already holds it or the
38
+ * publication is in a terminal state.
39
+ */
40
+ tryClaim(publicationId: string): Promise<boolean>;
41
+ /**
42
+ * Mark the publication as completed using the requested
43
+ * {@link CompletionMode}:
44
+ * - `UPDATE` (default): set `completion_date` and keep the row for audit;
45
+ * - `DELETE`: delete the row outright;
46
+ * - `ARCHIVE`: move the row to the archive table (impl-specific).
47
+ */
48
+ markCompleted(publicationId: string, mode?: CompletionMode): Promise<void>;
49
+ /**
50
+ * Mark the publication as failed, storing the given reason so
51
+ * operators can triage.
52
+ */
53
+ markFailed(publicationId: string, reason: string): Promise<void>;
54
+ /**
55
+ * Mark a previously-failed publication as resubmitted for retry.
56
+ * Stamps `lastResubmissionDate` with the current time.
57
+ */
58
+ markResubmitted(publicationId: string): Promise<void>;
59
+ /**
60
+ * Decode the stored payload back into an application object via the
61
+ * configured {@link EventSerializer}. Delivered to the listener as-is.
62
+ */
63
+ deserialize(publication: EventPublication): unknown;
64
+ /**
65
+ * Pass-through to {@link EventPublicationRepository.findReadyForProcessing}
66
+ * — used by the (upcoming) async dispatcher to pull work.
67
+ */
68
+ findReadyForProcessing(limit: number): Promise<EventPublication[]>;
69
+ }
70
+ //# sourceMappingURL=event-publication-registry.d.ts.map
@@ -0,0 +1,138 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.EventPublicationRegistry = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const event_publication_repository_1 = require("../repository/event-publication-repository");
18
+ const event_serializer_1 = require("../serialization/event-serializer");
19
+ const completion_mode_1 = require("../types/completion-mode");
20
+ const publication_status_1 = require("../types/publication-status");
21
+ /**
22
+ * Central service that coordinates the event publication lifecycle.
23
+ *
24
+ * Responsibilities:
25
+ * - persist publication entries (one per listener) atomically with the
26
+ * business transaction via the injected {@link EventPublicationRepository};
27
+ * - transition lifecycle states (`PUBLISHED` → `PROCESSING` →
28
+ * `COMPLETED` / `FAILED` → `RESUBMITTED`);
29
+ * - hand out deserialized event payloads ready for listener invocation.
30
+ *
31
+ * Does NOT invoke listeners — that is the dispatcher's job (Phase 5.6).
32
+ * Inspired by the internals of Spring Modulith's Event Publication
33
+ * Registry.
34
+ */
35
+ let EventPublicationRegistry = class EventPublicationRegistry {
36
+ repository;
37
+ serializer;
38
+ constructor(repository, serializer) {
39
+ this.repository = repository;
40
+ this.serializer = serializer;
41
+ }
42
+ /**
43
+ * Persist one publication entry per listener for a single event.
44
+ *
45
+ * Must be called inside an active transaction so the entries are
46
+ * committed atomically with the business write. Returns an empty
47
+ * array when there are no listeners — no repository call is made.
48
+ *
49
+ * The event's `constructor.name` is stored as `eventType` and used
50
+ * by the deserializer to look up the class at delivery time.
51
+ */
52
+ async publish(event, listenerIds) {
53
+ if (listenerIds.length === 0) {
54
+ return [];
55
+ }
56
+ // Delegate validation of the event shape (must be an object) to the
57
+ // serializer — it throws SerializationError with a clear message.
58
+ const serialized = this.serializer.serialize(event);
59
+ const eventType = event.constructor.name;
60
+ const inputs = listenerIds.map((listenerId) => ({
61
+ listenerId,
62
+ eventType,
63
+ serializedEvent: serialized,
64
+ }));
65
+ return this.repository.createAll(inputs);
66
+ }
67
+ /**
68
+ * Atomically transition the publication from `PUBLISHED` or
69
+ * `RESUBMITTED` to `PROCESSING`. Returns `true` when the claim
70
+ * succeeded, `false` when another worker already holds it or the
71
+ * publication is in a terminal state.
72
+ */
73
+ async tryClaim(publicationId) {
74
+ return this.repository.tryClaim(publicationId);
75
+ }
76
+ /**
77
+ * Mark the publication as completed using the requested
78
+ * {@link CompletionMode}:
79
+ * - `UPDATE` (default): set `completion_date` and keep the row for audit;
80
+ * - `DELETE`: delete the row outright;
81
+ * - `ARCHIVE`: move the row to the archive table (impl-specific).
82
+ */
83
+ async markCompleted(publicationId, mode = completion_mode_1.CompletionMode.UPDATE) {
84
+ switch (mode) {
85
+ case completion_mode_1.CompletionMode.UPDATE:
86
+ await this.repository.updateStatus(publicationId, publication_status_1.PublicationStatus.COMPLETED, {
87
+ completionDate: new Date(),
88
+ });
89
+ return;
90
+ case completion_mode_1.CompletionMode.DELETE:
91
+ await this.repository.delete(publicationId);
92
+ return;
93
+ case completion_mode_1.CompletionMode.ARCHIVE:
94
+ await this.repository.archiveCompleted(publicationId);
95
+ return;
96
+ }
97
+ }
98
+ /**
99
+ * Mark the publication as failed, storing the given reason so
100
+ * operators can triage.
101
+ */
102
+ async markFailed(publicationId, reason) {
103
+ await this.repository.updateStatus(publicationId, publication_status_1.PublicationStatus.FAILED, {
104
+ failureReason: reason,
105
+ });
106
+ }
107
+ /**
108
+ * Mark a previously-failed publication as resubmitted for retry.
109
+ * Stamps `lastResubmissionDate` with the current time.
110
+ */
111
+ async markResubmitted(publicationId) {
112
+ await this.repository.updateStatus(publicationId, publication_status_1.PublicationStatus.RESUBMITTED, {
113
+ lastResubmissionDate: new Date(),
114
+ });
115
+ }
116
+ /**
117
+ * Decode the stored payload back into an application object via the
118
+ * configured {@link EventSerializer}. Delivered to the listener as-is.
119
+ */
120
+ deserialize(publication) {
121
+ return this.serializer.deserialize(publication.serializedEvent, publication.eventType);
122
+ }
123
+ /**
124
+ * Pass-through to {@link EventPublicationRepository.findReadyForProcessing}
125
+ * — used by the (upcoming) async dispatcher to pull work.
126
+ */
127
+ async findReadyForProcessing(limit) {
128
+ return this.repository.findReadyForProcessing(limit);
129
+ }
130
+ };
131
+ exports.EventPublicationRegistry = EventPublicationRegistry;
132
+ exports.EventPublicationRegistry = EventPublicationRegistry = __decorate([
133
+ (0, common_1.Injectable)(),
134
+ __param(0, (0, common_1.Inject)(event_publication_repository_1.EVENT_PUBLICATION_REPOSITORY)),
135
+ __param(1, (0, common_1.Inject)(event_serializer_1.EVENT_SERIALIZER)),
136
+ __metadata("design:paramtypes", [Object, Object])
137
+ ], EventPublicationRegistry);
138
+ //# sourceMappingURL=event-publication-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-publication-registry.js","sourceRoot":"","sources":["../../src/registry/event-publication-registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * A listener registered with the {@link OutboxListenerRegistry}.
3
+ *
4
+ * `id` is stable across restarts — it is persisted on every
5
+ * {@link EventPublication} row, so renaming the method breaks resume /
6
+ * retry of stored publications. Consumers are expected to either use
7
+ * the default `"ClassName.methodName"` shape or supply an explicit id
8
+ * in the decorator's options (see ADR-009 — planned).
9
+ */
10
+ export interface RegisteredOutboxListener {
11
+ /** Globally-unique listener id (e.g. `"InventoryModule.onOrderPlaced"`). */
12
+ readonly id: string;
13
+ /** Event class name this listener subscribes to. */
14
+ readonly eventType: string;
15
+ /** Invoke the listener with a decoded event payload. */
16
+ readonly invoke: (event: unknown) => Promise<void>;
17
+ }
18
+ /**
19
+ * Registry of `@OutboxEventsHandler`-annotated classes (and other
20
+ * programmatically-registered listeners such as those routed from
21
+ * `@IntegrationEventsHandler` via the structural registrar port),
22
+ * keyed by both the listener id (for targeted dispatch / resume) and
23
+ * the event class name (for fan-out when a new event is published).
24
+ *
25
+ * Populated at application bootstrap by the scanner (upcoming Phase 5
26
+ * iteration). Stateless beyond the two index maps.
27
+ */
28
+ export declare class OutboxListenerRegistry {
29
+ private readonly listenersByEventType;
30
+ private readonly listenersById;
31
+ /**
32
+ * Index a listener under both its id and its event type.
33
+ *
34
+ * @throws {DuplicateListenerIdError} when a listener with the same
35
+ * id is already registered — ids must be unique to avoid
36
+ * non-deterministic dispatch.
37
+ */
38
+ register(listener: RegisteredOutboxListener): void;
39
+ /**
40
+ * Return every listener subscribed to the given event class name, in
41
+ * registration order. Returns `[]` (never `undefined`) for unknown
42
+ * event types.
43
+ */
44
+ getByEventType(eventType: string): RegisteredOutboxListener[];
45
+ /** Return the listener with the given id, or `undefined` when not registered. */
46
+ getById(id: string): RegisteredOutboxListener | undefined;
47
+ /** Return every registered listener, in registration order. */
48
+ getAll(): RegisteredOutboxListener[];
49
+ /** Drop every registered listener. Primarily a testing convenience. */
50
+ clear(): void;
51
+ }
52
+ //# sourceMappingURL=listener-registry.d.ts.map
@@ -0,0 +1,67 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.OutboxListenerRegistry = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ const errors_1 = require("../types/errors");
12
+ /**
13
+ * Registry of `@OutboxEventsHandler`-annotated classes (and other
14
+ * programmatically-registered listeners such as those routed from
15
+ * `@IntegrationEventsHandler` via the structural registrar port),
16
+ * keyed by both the listener id (for targeted dispatch / resume) and
17
+ * the event class name (for fan-out when a new event is published).
18
+ *
19
+ * Populated at application bootstrap by the scanner (upcoming Phase 5
20
+ * iteration). Stateless beyond the two index maps.
21
+ */
22
+ let OutboxListenerRegistry = class OutboxListenerRegistry {
23
+ listenersByEventType = new Map();
24
+ listenersById = new Map();
25
+ /**
26
+ * Index a listener under both its id and its event type.
27
+ *
28
+ * @throws {DuplicateListenerIdError} when a listener with the same
29
+ * id is already registered — ids must be unique to avoid
30
+ * non-deterministic dispatch.
31
+ */
32
+ register(listener) {
33
+ if (this.listenersById.has(listener.id)) {
34
+ throw new errors_1.DuplicateListenerIdError(listener.id);
35
+ }
36
+ const listeners = this.listenersByEventType.get(listener.eventType) ?? [];
37
+ listeners.push(listener);
38
+ this.listenersByEventType.set(listener.eventType, listeners);
39
+ this.listenersById.set(listener.id, listener);
40
+ }
41
+ /**
42
+ * Return every listener subscribed to the given event class name, in
43
+ * registration order. Returns `[]` (never `undefined`) for unknown
44
+ * event types.
45
+ */
46
+ getByEventType(eventType) {
47
+ return this.listenersByEventType.get(eventType) ?? [];
48
+ }
49
+ /** Return the listener with the given id, or `undefined` when not registered. */
50
+ getById(id) {
51
+ return this.listenersById.get(id);
52
+ }
53
+ /** Return every registered listener, in registration order. */
54
+ getAll() {
55
+ return Array.from(this.listenersById.values());
56
+ }
57
+ /** Drop every registered listener. Primarily a testing convenience. */
58
+ clear() {
59
+ this.listenersByEventType.clear();
60
+ this.listenersById.clear();
61
+ }
62
+ };
63
+ exports.OutboxListenerRegistry = OutboxListenerRegistry;
64
+ exports.OutboxListenerRegistry = OutboxListenerRegistry = __decorate([
65
+ (0, common_1.Injectable)()
66
+ ], OutboxListenerRegistry);
67
+ //# sourceMappingURL=listener-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listener-registry.js","sourceRoot":"","sources":["../../src/registry/listener-registry.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA4C;AAE5C,4CAA2D;AAoB3D;;;;;;;;;GASG;AAEI,IAAM,sBAAsB,GAA5B,MAAM,sBAAsB;IAChB,oBAAoB,GAAG,IAAI,GAAG,EAAsC,CAAC;IACrE,aAAa,GAAG,IAAI,GAAG,EAAoC,CAAC;IAE7E;;;;;;OAMG;IACH,QAAQ,CAAC,QAAkC;QACzC,IAAI,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;YACxC,MAAM,IAAI,iCAAwB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAClD,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;QAC1E,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzB,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QAC7D,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IAChD,CAAC;IAED;;;;OAIG;IACH,cAAc,CAAC,SAAiB;QAC9B,OAAO,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;IACxD,CAAC;IAED,iFAAiF;IACjF,OAAO,CAAC,EAAU;QAChB,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACpC,CAAC;IAED,+DAA+D;IAC/D,MAAM;QACJ,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC;IACjD,CAAC;IAED,uEAAuE;IACvE,KAAK;QACH,IAAI,CAAC,oBAAoB,CAAC,KAAK,EAAE,CAAC;QAClC,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;IAC7B,CAAC;CACF,CAAA;AA9CY,wDAAsB;iCAAtB,sBAAsB;IADlC,IAAA,mBAAU,GAAE;GACA,sBAAsB,CA8ClC"}