@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.
- package/LICENSE +21 -0
- package/README.md +396 -0
- package/dist/api/completed-event-publications.d.ts +31 -0
- package/dist/api/completed-event-publications.js +58 -0
- package/dist/api/completed-event-publications.js.map +1 -0
- package/dist/api/failed-event-publications.d.ts +36 -0
- package/dist/api/failed-event-publications.js +75 -0
- package/dist/api/failed-event-publications.js.map +1 -0
- package/dist/api/incomplete-event-publications.d.ts +28 -0
- package/dist/api/incomplete-event-publications.js +72 -0
- package/dist/api/incomplete-event-publications.js.map +1 -0
- package/dist/decorators/inject-decorators.d.ts +85 -0
- package/dist/decorators/inject-decorators.js +100 -0
- package/dist/decorators/inject-decorators.js.map +1 -0
- package/dist/decorators/outbox-events-handler.decorator.d.ts +97 -0
- package/dist/decorators/outbox-events-handler.decorator.js +58 -0
- package/dist/decorators/outbox-events-handler.decorator.js.map +1 -0
- package/dist/dispatcher/data-source-outbox-publisher.d.ts +69 -0
- package/dist/dispatcher/data-source-outbox-publisher.js +127 -0
- package/dist/dispatcher/data-source-outbox-publisher.js.map +1 -0
- package/dist/dispatcher/event-publication-processor.d.ts +69 -0
- package/dist/dispatcher/event-publication-processor.js +192 -0
- package/dist/dispatcher/event-publication-processor.js.map +1 -0
- package/dist/dispatcher/outbox-event-publisher.d.ts +129 -0
- package/dist/dispatcher/outbox-event-publisher.js +172 -0
- package/dist/dispatcher/outbox-event-publisher.js.map +1 -0
- package/dist/dispatcher/processor-options.d.ts +24 -0
- package/dist/dispatcher/processor-options.js +15 -0
- package/dist/dispatcher/processor-options.js.map +1 -0
- package/dist/externalization/errors.d.ts +21 -0
- package/dist/externalization/errors.js +30 -0
- package/dist/externalization/errors.js.map +1 -0
- package/dist/externalization/event-externalizer.d.ts +49 -0
- package/dist/externalization/event-externalizer.js +14 -0
- package/dist/externalization/event-externalizer.js.map +1 -0
- package/dist/externalization/externalization-registry.d.ts +56 -0
- package/dist/externalization/externalization-registry.js +103 -0
- package/dist/externalization/externalization-registry.js.map +1 -0
- package/dist/externalization/externalized.decorator.d.ts +121 -0
- package/dist/externalization/externalized.decorator.js +87 -0
- package/dist/externalization/externalized.decorator.js.map +1 -0
- package/dist/externalization/types.d.ts +39 -0
- package/dist/externalization/types.js +3 -0
- package/dist/externalization/types.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/outbox-event-handler.interface.d.ts +32 -0
- package/dist/interfaces/outbox-event-handler.interface.js +3 -0
- package/dist/interfaces/outbox-event-handler.interface.js.map +1 -0
- package/dist/module/outbox-processing.module.d.ts +29 -0
- package/dist/module/outbox-processing.module.js +66 -0
- package/dist/module/outbox-processing.module.js.map +1 -0
- package/dist/module/outbox.module.d.ts +196 -0
- package/dist/module/outbox.module.js +604 -0
- package/dist/module/outbox.module.js.map +1 -0
- package/dist/recovery/staleness-monitor.d.ts +39 -0
- package/dist/recovery/staleness-monitor.js +118 -0
- package/dist/recovery/staleness-monitor.js.map +1 -0
- package/dist/recovery/startup-recovery.d.ts +37 -0
- package/dist/recovery/startup-recovery.js +55 -0
- package/dist/recovery/startup-recovery.js.map +1 -0
- package/dist/registry/event-publication-registry.d.ts +70 -0
- package/dist/registry/event-publication-registry.js +138 -0
- package/dist/registry/event-publication-registry.js.map +1 -0
- package/dist/registry/listener-registry.d.ts +52 -0
- package/dist/registry/listener-registry.js +67 -0
- package/dist/registry/listener-registry.js.map +1 -0
- package/dist/registry/multi-ds-listener-registrar.d.ts +78 -0
- package/dist/registry/multi-ds-listener-registrar.js +112 -0
- package/dist/registry/multi-ds-listener-registrar.js.map +1 -0
- package/dist/registry/outbox-listener-scanner.d.ts +66 -0
- package/dist/registry/outbox-listener-scanner.js +192 -0
- package/dist/registry/outbox-listener-scanner.js.map +1 -0
- package/dist/repository/event-publication-repository.d.ts +112 -0
- package/dist/repository/event-publication-repository.js +6 -0
- package/dist/repository/event-publication-repository.js.map +1 -0
- package/dist/serialization/event-serializer.d.ts +28 -0
- package/dist/serialization/event-serializer.js +6 -0
- package/dist/serialization/event-serializer.js.map +1 -0
- package/dist/serialization/event-type-registry.d.ts +41 -0
- package/dist/serialization/event-type-registry.js +79 -0
- package/dist/serialization/event-type-registry.js.map +1 -0
- package/dist/serialization/event-type-resolver.d.ts +43 -0
- package/dist/serialization/event-type-resolver.js +66 -0
- package/dist/serialization/event-type-resolver.js.map +1 -0
- package/dist/serialization/json-event-serializer.d.ts +20 -0
- package/dist/serialization/json-event-serializer.js +64 -0
- package/dist/serialization/json-event-serializer.js.map +1 -0
- package/dist/testing/assertable-published-events.d.ts +71 -0
- package/dist/testing/assertable-published-events.js +124 -0
- package/dist/testing/assertable-published-events.js.map +1 -0
- package/dist/testing/in-memory-repository.d.ts +52 -0
- package/dist/testing/in-memory-repository.js +230 -0
- package/dist/testing/in-memory-repository.js.map +1 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +20 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/published-events.d.ts +62 -0
- package/dist/testing/published-events.js +110 -0
- package/dist/testing/published-events.js.map +1 -0
- package/dist/tokens/index.d.ts +2 -0
- package/dist/tokens/index.js +14 -0
- package/dist/tokens/index.js.map +1 -0
- package/dist/tokens/token-utils.d.ts +68 -0
- package/dist/tokens/token-utils.js +98 -0
- package/dist/tokens/token-utils.js.map +1 -0
- package/dist/types/completion-mode.d.ts +16 -0
- package/dist/types/completion-mode.js +20 -0
- package/dist/types/completion-mode.js.map +1 -0
- package/dist/types/errors.d.ts +50 -0
- package/dist/types/errors.js +64 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/event-publication.d.ts +47 -0
- package/dist/types/event-publication.js +3 -0
- package/dist/types/event-publication.js.map +1 -0
- package/dist/types/publication-status.d.ts +26 -0
- package/dist/types/publication-status.js +30 -0
- package/dist/types/publication-status.js.map +1 -0
- package/dist/types/resubmission-options.d.ts +37 -0
- package/dist/types/resubmission-options.js +62 -0
- package/dist/types/resubmission-options.js.map +1 -0
- package/dist/types/staleness-config.d.ts +18 -0
- package/dist/types/staleness-config.js +10 -0
- package/dist/types/staleness-config.js.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type EventExternalizer } from '../externalization/event-externalizer';
|
|
2
|
+
import { ExternalizationRegistry } from '../externalization/externalization-registry';
|
|
3
|
+
import { EventPublicationRegistry } from '../registry/event-publication-registry';
|
|
4
|
+
import { OutboxListenerRegistry } from '../registry/listener-registry';
|
|
5
|
+
import type { EventPublicationProcessorOptions } from './processor-options';
|
|
6
|
+
/**
|
|
7
|
+
* Async worker that drains the event publication queue:
|
|
8
|
+
*
|
|
9
|
+
* 1. Pulls up to `batchSize` publications in `PUBLISHED` / `RESUBMITTED`
|
|
10
|
+
* state from the {@link EventPublicationRegistry};
|
|
11
|
+
* 2. atomically claims each one (`PUBLISHED/RESUBMITTED → PROCESSING`)
|
|
12
|
+
* so concurrent workers cannot double-dispatch;
|
|
13
|
+
* 3. looks up the corresponding listener in the
|
|
14
|
+
* {@link OutboxListenerRegistry} and invokes it with the
|
|
15
|
+
* deserialized event;
|
|
16
|
+
* 4. finalizes the publication — `COMPLETED` with the configured
|
|
17
|
+
* completion mode on success, `FAILED` with the error message
|
|
18
|
+
* otherwise.
|
|
19
|
+
*
|
|
20
|
+
* Up to `maxConcurrent` invocations run in parallel inside one batch.
|
|
21
|
+
* Listener errors never bubble out: they are caught, logged, and
|
|
22
|
+
* recorded on the publication row.
|
|
23
|
+
*/
|
|
24
|
+
export declare class EventPublicationProcessor {
|
|
25
|
+
private readonly registry;
|
|
26
|
+
private readonly listenerRegistry;
|
|
27
|
+
private readonly options;
|
|
28
|
+
private readonly externalizer?;
|
|
29
|
+
private readonly externalizationRegistry?;
|
|
30
|
+
private readonly logger;
|
|
31
|
+
private running;
|
|
32
|
+
private processingLoop;
|
|
33
|
+
constructor(registry: EventPublicationRegistry, listenerRegistry: OutboxListenerRegistry, options: EventPublicationProcessorOptions, externalizer?: EventExternalizer | undefined, externalizationRegistry?: ExternalizationRegistry | undefined);
|
|
34
|
+
/** Start the periodic polling loop. Idempotent. */
|
|
35
|
+
start(): void;
|
|
36
|
+
/** Stop the polling loop and cancel any pending scheduled tick. */
|
|
37
|
+
stop(): void;
|
|
38
|
+
/**
|
|
39
|
+
* Process a single batch of pending publications. Safe to call
|
|
40
|
+
* directly from tests and one-shot tooling; `start()` triggers it
|
|
41
|
+
* periodically.
|
|
42
|
+
*
|
|
43
|
+
* Internal errors (e.g. DB unreachable) are caught and logged —
|
|
44
|
+
* this method never rejects.
|
|
45
|
+
*/
|
|
46
|
+
processBatch(): Promise<void>;
|
|
47
|
+
private scheduleNext;
|
|
48
|
+
private processOne;
|
|
49
|
+
/**
|
|
50
|
+
* Externalize the event after the local listener has succeeded.
|
|
51
|
+
*
|
|
52
|
+
* Returns immediately (no-op) when:
|
|
53
|
+
* - no {@link EventExternalizer} is bound (DD-018 — externalization
|
|
54
|
+
* is optional);
|
|
55
|
+
* - no {@link ExternalizationRegistry} is bound (defensive — the
|
|
56
|
+
* `OutboxModule` always provides one, but direct `new`
|
|
57
|
+
* instantiation in tests may omit it);
|
|
58
|
+
* - the event type carries no `@Externalized` mapping.
|
|
59
|
+
*
|
|
60
|
+
* Otherwise resolves the per-publication {@link ExternalizationMetadata}
|
|
61
|
+
* from the registry and invokes the externalizer. Any rejection is
|
|
62
|
+
* wrapped in {@link ExternalizationError} and re-thrown so the outer
|
|
63
|
+
* try / catch in `processOne` records the publication as `FAILED`,
|
|
64
|
+
* which preserves the single-unit atomicity contract from DD-019.
|
|
65
|
+
*/
|
|
66
|
+
private tryExternalize;
|
|
67
|
+
private chunk;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=event-publication-processor.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 EventPublicationProcessor_1;
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.EventPublicationProcessor = void 0;
|
|
17
|
+
const common_1 = require("@nestjs/common");
|
|
18
|
+
const errors_1 = require("../externalization/errors");
|
|
19
|
+
const event_externalizer_1 = require("../externalization/event-externalizer");
|
|
20
|
+
const externalization_registry_1 = require("../externalization/externalization-registry");
|
|
21
|
+
const event_publication_registry_1 = require("../registry/event-publication-registry");
|
|
22
|
+
const listener_registry_1 = require("../registry/listener-registry");
|
|
23
|
+
/**
|
|
24
|
+
* Async worker that drains the event publication queue:
|
|
25
|
+
*
|
|
26
|
+
* 1. Pulls up to `batchSize` publications in `PUBLISHED` / `RESUBMITTED`
|
|
27
|
+
* state from the {@link EventPublicationRegistry};
|
|
28
|
+
* 2. atomically claims each one (`PUBLISHED/RESUBMITTED → PROCESSING`)
|
|
29
|
+
* so concurrent workers cannot double-dispatch;
|
|
30
|
+
* 3. looks up the corresponding listener in the
|
|
31
|
+
* {@link OutboxListenerRegistry} and invokes it with the
|
|
32
|
+
* deserialized event;
|
|
33
|
+
* 4. finalizes the publication — `COMPLETED` with the configured
|
|
34
|
+
* completion mode on success, `FAILED` with the error message
|
|
35
|
+
* otherwise.
|
|
36
|
+
*
|
|
37
|
+
* Up to `maxConcurrent` invocations run in parallel inside one batch.
|
|
38
|
+
* Listener errors never bubble out: they are caught, logged, and
|
|
39
|
+
* recorded on the publication row.
|
|
40
|
+
*/
|
|
41
|
+
let EventPublicationProcessor = EventPublicationProcessor_1 = class EventPublicationProcessor {
|
|
42
|
+
registry;
|
|
43
|
+
listenerRegistry;
|
|
44
|
+
options;
|
|
45
|
+
externalizer;
|
|
46
|
+
externalizationRegistry;
|
|
47
|
+
logger = new common_1.Logger(EventPublicationProcessor_1.name);
|
|
48
|
+
running = false;
|
|
49
|
+
processingLoop = null;
|
|
50
|
+
constructor(registry, listenerRegistry, options, externalizer, externalizationRegistry) {
|
|
51
|
+
this.registry = registry;
|
|
52
|
+
this.listenerRegistry = listenerRegistry;
|
|
53
|
+
this.options = options;
|
|
54
|
+
this.externalizer = externalizer;
|
|
55
|
+
this.externalizationRegistry = externalizationRegistry;
|
|
56
|
+
}
|
|
57
|
+
/** Start the periodic polling loop. Idempotent. */
|
|
58
|
+
start() {
|
|
59
|
+
if (this.running) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
this.running = true;
|
|
63
|
+
this.scheduleNext();
|
|
64
|
+
this.logger.log('EventPublicationProcessor started');
|
|
65
|
+
}
|
|
66
|
+
/** Stop the polling loop and cancel any pending scheduled tick. */
|
|
67
|
+
stop() {
|
|
68
|
+
this.running = false;
|
|
69
|
+
if (this.processingLoop !== null) {
|
|
70
|
+
clearTimeout(this.processingLoop);
|
|
71
|
+
this.processingLoop = null;
|
|
72
|
+
}
|
|
73
|
+
this.logger.log('EventPublicationProcessor stopped');
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Process a single batch of pending publications. Safe to call
|
|
77
|
+
* directly from tests and one-shot tooling; `start()` triggers it
|
|
78
|
+
* periodically.
|
|
79
|
+
*
|
|
80
|
+
* Internal errors (e.g. DB unreachable) are caught and logged —
|
|
81
|
+
* this method never rejects.
|
|
82
|
+
*/
|
|
83
|
+
async processBatch() {
|
|
84
|
+
try {
|
|
85
|
+
const publications = await this.registry.findReadyForProcessing(this.options.batchSize);
|
|
86
|
+
if (publications.length === 0) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
this.logger.debug(`Processing batch of ${publications.length} publications`);
|
|
90
|
+
const chunks = this.chunk(publications, this.options.maxConcurrent);
|
|
91
|
+
for (const chunk of chunks) {
|
|
92
|
+
await Promise.all(chunk.map((pub) => this.processOne(pub)));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
this.logger.error('Batch processing failed', err instanceof Error ? err.stack : String(err));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
scheduleNext() {
|
|
100
|
+
if (!this.running) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
this.processingLoop = setTimeout(() => {
|
|
104
|
+
void this.processBatch().finally(() => this.scheduleNext());
|
|
105
|
+
}, this.options.pollingInterval);
|
|
106
|
+
}
|
|
107
|
+
async processOne(publication) {
|
|
108
|
+
try {
|
|
109
|
+
const claimed = await this.registry.tryClaim(publication.id);
|
|
110
|
+
if (!claimed) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const listener = this.listenerRegistry.getById(publication.listenerId);
|
|
114
|
+
if (listener === undefined) {
|
|
115
|
+
await this.registry.markFailed(publication.id, `Listener '${publication.listenerId}' is not registered`);
|
|
116
|
+
this.logger.warn(`Listener '${publication.listenerId}' not found for publication ${publication.id}`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const event = this.registry.deserialize(publication);
|
|
121
|
+
// Local listener invocation runs first so cheap, in-process
|
|
122
|
+
// failures fail fast before we touch a broker (DD-019).
|
|
123
|
+
await listener.invoke(event);
|
|
124
|
+
// Externalization is optional and gated by the registry
|
|
125
|
+
// resolution wired in 11.2 — `tryExternalize` is a no-op until
|
|
126
|
+
// ExternalizationRegistry lookup lands.
|
|
127
|
+
await this.tryExternalize(event, publication);
|
|
128
|
+
await this.registry.markCompleted(publication.id, this.options.completionMode);
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
132
|
+
await this.registry.markFailed(publication.id, reason);
|
|
133
|
+
this.logger.error(`Listener '${publication.listenerId}' failed for publication ${publication.id}: ${reason}`, err instanceof Error ? err.stack : undefined);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
this.logger.error(`Error processing publication ${publication.id}`, err instanceof Error ? err.stack : String(err));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Externalize the event after the local listener has succeeded.
|
|
142
|
+
*
|
|
143
|
+
* Returns immediately (no-op) when:
|
|
144
|
+
* - no {@link EventExternalizer} is bound (DD-018 — externalization
|
|
145
|
+
* is optional);
|
|
146
|
+
* - no {@link ExternalizationRegistry} is bound (defensive — the
|
|
147
|
+
* `OutboxModule` always provides one, but direct `new`
|
|
148
|
+
* instantiation in tests may omit it);
|
|
149
|
+
* - the event type carries no `@Externalized` mapping.
|
|
150
|
+
*
|
|
151
|
+
* Otherwise resolves the per-publication {@link ExternalizationMetadata}
|
|
152
|
+
* from the registry and invokes the externalizer. Any rejection is
|
|
153
|
+
* wrapped in {@link ExternalizationError} and re-thrown so the outer
|
|
154
|
+
* try / catch in `processOne` records the publication as `FAILED`,
|
|
155
|
+
* which preserves the single-unit atomicity contract from DD-019.
|
|
156
|
+
*/
|
|
157
|
+
async tryExternalize(event, publication) {
|
|
158
|
+
if (this.externalizer === undefined || this.externalizationRegistry === undefined) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const metadata = this.externalizationRegistry.buildMetadata(publication.eventType, event);
|
|
162
|
+
if (metadata === undefined) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
await this.externalizer.externalize(event, metadata);
|
|
167
|
+
this.logger.debug(`Externalized ${publication.eventType} → ${metadata.target}`);
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
const cause = err instanceof Error ? err : undefined;
|
|
171
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
172
|
+
throw new errors_1.ExternalizationError(`Externalization failed for ${publication.eventType}: ${message}`, publication.eventType, metadata.target, cause);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
chunk(arr, size) {
|
|
176
|
+
const chunks = [];
|
|
177
|
+
for (let i = 0; i < arr.length; i += size) {
|
|
178
|
+
chunks.push(arr.slice(i, i + size));
|
|
179
|
+
}
|
|
180
|
+
return chunks;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
exports.EventPublicationProcessor = EventPublicationProcessor;
|
|
184
|
+
exports.EventPublicationProcessor = EventPublicationProcessor = EventPublicationProcessor_1 = __decorate([
|
|
185
|
+
(0, common_1.Injectable)(),
|
|
186
|
+
__param(3, (0, common_1.Optional)()),
|
|
187
|
+
__param(3, (0, common_1.Inject)(event_externalizer_1.EVENT_EXTERNALIZER)),
|
|
188
|
+
__param(4, (0, common_1.Optional)()),
|
|
189
|
+
__metadata("design:paramtypes", [event_publication_registry_1.EventPublicationRegistry,
|
|
190
|
+
listener_registry_1.OutboxListenerRegistry, Object, Object, externalization_registry_1.ExternalizationRegistry])
|
|
191
|
+
], EventPublicationProcessor);
|
|
192
|
+
//# sourceMappingURL=event-publication-processor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-publication-processor.js","sourceRoot":"","sources":["../../src/dispatcher/event-publication-processor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { type OnModuleInit } from '@nestjs/common';
|
|
2
|
+
import { ModuleRef } from '@nestjs/core';
|
|
3
|
+
/**
|
|
4
|
+
* Optional override accepted by the facade `OutboxEventPublisher`'s
|
|
5
|
+
* publish methods to target a specific dataSource explicitly.
|
|
6
|
+
*
|
|
7
|
+
* When omitted, the facade routes by event-type registration: the
|
|
8
|
+
* event class is looked up in each per-dataSource
|
|
9
|
+
* {@link EventTypeRegistry}, and the dataSource that registered it
|
|
10
|
+
* owns the publication.
|
|
11
|
+
*/
|
|
12
|
+
export interface OutboxPublishOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Public dataSource name to publish to. Always wins over event-type
|
|
15
|
+
* routing when set. Useful for events that are registered in
|
|
16
|
+
* multiple dataSources, or for explicit cross-dataSource routing.
|
|
17
|
+
*/
|
|
18
|
+
readonly dataSource?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* High-level smart-facade publisher (DD-024). The single user-facing
|
|
22
|
+
* publisher class — application code injects `OutboxEventPublisher`
|
|
23
|
+
* and the facade routes publications to the per-dataSource
|
|
24
|
+
* {@link DataSourceOutboxPublisher} that owns the event class.
|
|
25
|
+
*
|
|
26
|
+
* **Routing algorithm** (in order):
|
|
27
|
+
* 1. If `options.dataSource` is provided, route to that dataSource.
|
|
28
|
+
* 2. Else, look up `event.constructor.name` in each registered
|
|
29
|
+
* per-dataSource {@link EventTypeRegistry}; the registry that
|
|
30
|
+
* contains the event determines the destination.
|
|
31
|
+
* 3. If no registry contains the event, throw {@link OutboxError}.
|
|
32
|
+
*
|
|
33
|
+
* **Cross-dataSource publishing** is allowed: the routing decision is
|
|
34
|
+
* about the event's *registered* dataSource, NOT about which
|
|
35
|
+
* transactions are currently active. With Phase 14.2's cross-DS
|
|
36
|
+
* simultaneous transactions, a billing event published from inside an
|
|
37
|
+
* outer-billing / inner-inventory async stack still routes to billing
|
|
38
|
+
* (because that's where the event class is registered) and validates
|
|
39
|
+
* its atomicity against the billing transaction.
|
|
40
|
+
*
|
|
41
|
+
* **Single-dataSource deployments** see no behavioural change: there
|
|
42
|
+
* is one per-DS publisher (named `'default'`), one event-type
|
|
43
|
+
* registry, and the facade transparently delegates to it.
|
|
44
|
+
*
|
|
45
|
+
* Replaces the pre-Phase-14.3 publisher whose internal
|
|
46
|
+
* `findCurrentTransaction()` returned an arbitrary first-active
|
|
47
|
+
* transaction — non-deterministic with multiple active transactions.
|
|
48
|
+
* No backwards-compatibility shim: callers always inject
|
|
49
|
+
* `OutboxEventPublisher` and receive routing semantics consistent
|
|
50
|
+
* with the registered event-type ownership.
|
|
51
|
+
*/
|
|
52
|
+
/** @internal — DI token carrying the live Map of registered dataSources. */
|
|
53
|
+
export declare const OUTBOX_DATA_SOURCE_NAMES: unique symbol;
|
|
54
|
+
/** @internal — Read-only view of the shared registration list. */
|
|
55
|
+
export type OutboxDataSourceNames = ReadonlySet<string> | {
|
|
56
|
+
keys(): IterableIterator<string>;
|
|
57
|
+
};
|
|
58
|
+
export declare class OutboxEventPublisher implements OnModuleInit {
|
|
59
|
+
private readonly moduleRef;
|
|
60
|
+
/**
|
|
61
|
+
* Live reference to {@link OutboxModule.registrations} (passed via
|
|
62
|
+
* `useValue` so its `keys()` enumeration sees every dataSource
|
|
63
|
+
* registered across all `forRoot` calls — `useValue` captures the
|
|
64
|
+
* Map by reference, so later forRoot pushes are visible at
|
|
65
|
+
* `OnModuleInit` time).
|
|
66
|
+
*/
|
|
67
|
+
private readonly dataSourceNames;
|
|
68
|
+
private readonly logger;
|
|
69
|
+
private readonly publishers;
|
|
70
|
+
private readonly eventTypeRegistries;
|
|
71
|
+
constructor(moduleRef: ModuleRef,
|
|
72
|
+
/**
|
|
73
|
+
* Live reference to {@link OutboxModule.registrations} (passed via
|
|
74
|
+
* `useValue` so its `keys()` enumeration sees every dataSource
|
|
75
|
+
* registered across all `forRoot` calls — `useValue` captures the
|
|
76
|
+
* Map by reference, so later forRoot pushes are visible at
|
|
77
|
+
* `OnModuleInit` time).
|
|
78
|
+
*/
|
|
79
|
+
dataSourceNames: OutboxDataSourceNames);
|
|
80
|
+
/**
|
|
81
|
+
* Late-bind per-DS publishers and event-type registries via
|
|
82
|
+
* `ModuleRef`. Lifecycle hook chosen because it fires AFTER every
|
|
83
|
+
* provider has been instantiated — by then every per-DS publisher
|
|
84
|
+
* exists and is resolvable, regardless of the order in which the
|
|
85
|
+
* facade's `forRoot` and per-DS `forRoot`s appear in the import
|
|
86
|
+
* tree.
|
|
87
|
+
*/
|
|
88
|
+
onModuleInit(): void;
|
|
89
|
+
/**
|
|
90
|
+
* Publish a single event. Routing per the class JSDoc:
|
|
91
|
+
* `options.dataSource` (explicit) > event-type registration
|
|
92
|
+
* (implicit) > {@link OutboxError}.
|
|
93
|
+
*
|
|
94
|
+
* Must be called inside an active transaction *for the resolved
|
|
95
|
+
* dataSource* — `DataSourceOutboxPublisher.publish` enforces this
|
|
96
|
+
* with a clear error message naming the dataSource.
|
|
97
|
+
*/
|
|
98
|
+
publish(event: unknown, options?: OutboxPublishOptions): Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* Publish a batch of events. Each event is routed independently, so
|
|
101
|
+
* a mixed-dataSource batch (e.g. one billing event and one
|
|
102
|
+
* inventory event in the same call) routes each to its own
|
|
103
|
+
* dataSource publisher.
|
|
104
|
+
*/
|
|
105
|
+
publishAll(events: readonly unknown[], options?: OutboxPublishOptions): Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* Synchronous scheduling sibling of {@link publish}. Designed for
|
|
108
|
+
* sync callers such as `@nestjs/cqrs`'s `AggregateRoot.commit()`
|
|
109
|
+
* pathway. Same routing rules apply.
|
|
110
|
+
*
|
|
111
|
+
* Inside the resolved dataSource's active transaction: events are
|
|
112
|
+
* buffered and flushed in a `beforeCommit` hook
|
|
113
|
+
* (see {@link DataSourceOutboxPublisher.scheduleForPublication}).
|
|
114
|
+
* Outside any transaction: fire-and-forget {@link publish}, errors
|
|
115
|
+
* logged because there is no caller to propagate to.
|
|
116
|
+
*
|
|
117
|
+
* If the event cannot be routed (no matching registration), the
|
|
118
|
+
* call is logged-and-dropped — sync paths cannot raise.
|
|
119
|
+
*/
|
|
120
|
+
scheduleForPublication(event: unknown, options?: OutboxPublishOptions): void;
|
|
121
|
+
/**
|
|
122
|
+
* Read-only snapshot of the dataSources this facade routes to.
|
|
123
|
+
* Useful for diagnostics and tests.
|
|
124
|
+
*/
|
|
125
|
+
getRegisteredDataSources(): readonly string[];
|
|
126
|
+
private resolveDataSource;
|
|
127
|
+
private requirePublisher;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=outbox-event-publisher.d.ts.map
|
|
@@ -0,0 +1,172 @@
|
|
|
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 OutboxEventPublisher_1;
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.OutboxEventPublisher = exports.OUTBOX_DATA_SOURCE_NAMES = void 0;
|
|
17
|
+
const common_1 = require("@nestjs/common");
|
|
18
|
+
const core_1 = require("@nestjs/core");
|
|
19
|
+
const event_type_resolver_1 = require("../serialization/event-type-resolver");
|
|
20
|
+
const token_utils_1 = require("../tokens/token-utils");
|
|
21
|
+
const errors_1 = require("../types/errors");
|
|
22
|
+
/**
|
|
23
|
+
* High-level smart-facade publisher (DD-024). The single user-facing
|
|
24
|
+
* publisher class — application code injects `OutboxEventPublisher`
|
|
25
|
+
* and the facade routes publications to the per-dataSource
|
|
26
|
+
* {@link DataSourceOutboxPublisher} that owns the event class.
|
|
27
|
+
*
|
|
28
|
+
* **Routing algorithm** (in order):
|
|
29
|
+
* 1. If `options.dataSource` is provided, route to that dataSource.
|
|
30
|
+
* 2. Else, look up `event.constructor.name` in each registered
|
|
31
|
+
* per-dataSource {@link EventTypeRegistry}; the registry that
|
|
32
|
+
* contains the event determines the destination.
|
|
33
|
+
* 3. If no registry contains the event, throw {@link OutboxError}.
|
|
34
|
+
*
|
|
35
|
+
* **Cross-dataSource publishing** is allowed: the routing decision is
|
|
36
|
+
* about the event's *registered* dataSource, NOT about which
|
|
37
|
+
* transactions are currently active. With Phase 14.2's cross-DS
|
|
38
|
+
* simultaneous transactions, a billing event published from inside an
|
|
39
|
+
* outer-billing / inner-inventory async stack still routes to billing
|
|
40
|
+
* (because that's where the event class is registered) and validates
|
|
41
|
+
* its atomicity against the billing transaction.
|
|
42
|
+
*
|
|
43
|
+
* **Single-dataSource deployments** see no behavioural change: there
|
|
44
|
+
* is one per-DS publisher (named `'default'`), one event-type
|
|
45
|
+
* registry, and the facade transparently delegates to it.
|
|
46
|
+
*
|
|
47
|
+
* Replaces the pre-Phase-14.3 publisher whose internal
|
|
48
|
+
* `findCurrentTransaction()` returned an arbitrary first-active
|
|
49
|
+
* transaction — non-deterministic with multiple active transactions.
|
|
50
|
+
* No backwards-compatibility shim: callers always inject
|
|
51
|
+
* `OutboxEventPublisher` and receive routing semantics consistent
|
|
52
|
+
* with the registered event-type ownership.
|
|
53
|
+
*/
|
|
54
|
+
/** @internal — DI token carrying the live Map of registered dataSources. */
|
|
55
|
+
exports.OUTBOX_DATA_SOURCE_NAMES = Symbol('OUTBOX_DATA_SOURCE_NAMES');
|
|
56
|
+
let OutboxEventPublisher = OutboxEventPublisher_1 = class OutboxEventPublisher {
|
|
57
|
+
moduleRef;
|
|
58
|
+
dataSourceNames;
|
|
59
|
+
logger = new common_1.Logger(OutboxEventPublisher_1.name);
|
|
60
|
+
publishers = new Map();
|
|
61
|
+
eventTypeRegistries = new Map();
|
|
62
|
+
constructor(moduleRef, dataSourceNames) {
|
|
63
|
+
this.moduleRef = moduleRef;
|
|
64
|
+
this.dataSourceNames = dataSourceNames;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Late-bind per-DS publishers and event-type registries via
|
|
68
|
+
* `ModuleRef`. Lifecycle hook chosen because it fires AFTER every
|
|
69
|
+
* provider has been instantiated — by then every per-DS publisher
|
|
70
|
+
* exists and is resolvable, regardless of the order in which the
|
|
71
|
+
* facade's `forRoot` and per-DS `forRoot`s appear in the import
|
|
72
|
+
* tree.
|
|
73
|
+
*/
|
|
74
|
+
onModuleInit() {
|
|
75
|
+
for (const ds of this.dataSourceNames.keys()) {
|
|
76
|
+
this.publishers.set(ds, this.moduleRef.get((0, token_utils_1.getOutboxPublisherToken)(ds), {
|
|
77
|
+
strict: false,
|
|
78
|
+
}));
|
|
79
|
+
this.eventTypeRegistries.set(ds, this.moduleRef.get((0, token_utils_1.getEventTypeRegistryToken)(ds), {
|
|
80
|
+
strict: false,
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Publish a single event. Routing per the class JSDoc:
|
|
86
|
+
* `options.dataSource` (explicit) > event-type registration
|
|
87
|
+
* (implicit) > {@link OutboxError}.
|
|
88
|
+
*
|
|
89
|
+
* Must be called inside an active transaction *for the resolved
|
|
90
|
+
* dataSource* — `DataSourceOutboxPublisher.publish` enforces this
|
|
91
|
+
* with a clear error message naming the dataSource.
|
|
92
|
+
*/
|
|
93
|
+
async publish(event, options = {}) {
|
|
94
|
+
const dataSource = this.resolveDataSource(event, options);
|
|
95
|
+
const publisher = this.requirePublisher(dataSource);
|
|
96
|
+
await publisher.publish(event);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Publish a batch of events. Each event is routed independently, so
|
|
100
|
+
* a mixed-dataSource batch (e.g. one billing event and one
|
|
101
|
+
* inventory event in the same call) routes each to its own
|
|
102
|
+
* dataSource publisher.
|
|
103
|
+
*/
|
|
104
|
+
async publishAll(events, options = {}) {
|
|
105
|
+
for (const event of events) {
|
|
106
|
+
await this.publish(event, options);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Synchronous scheduling sibling of {@link publish}. Designed for
|
|
111
|
+
* sync callers such as `@nestjs/cqrs`'s `AggregateRoot.commit()`
|
|
112
|
+
* pathway. Same routing rules apply.
|
|
113
|
+
*
|
|
114
|
+
* Inside the resolved dataSource's active transaction: events are
|
|
115
|
+
* buffered and flushed in a `beforeCommit` hook
|
|
116
|
+
* (see {@link DataSourceOutboxPublisher.scheduleForPublication}).
|
|
117
|
+
* Outside any transaction: fire-and-forget {@link publish}, errors
|
|
118
|
+
* logged because there is no caller to propagate to.
|
|
119
|
+
*
|
|
120
|
+
* If the event cannot be routed (no matching registration), the
|
|
121
|
+
* call is logged-and-dropped — sync paths cannot raise.
|
|
122
|
+
*/
|
|
123
|
+
scheduleForPublication(event, options = {}) {
|
|
124
|
+
let dataSource;
|
|
125
|
+
try {
|
|
126
|
+
dataSource = this.resolveDataSource(event, options);
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
this.logger.error(`scheduleForPublication: ${err instanceof Error ? err.message : String(err)}`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const publisher = this.publishers.get(dataSource);
|
|
133
|
+
if (publisher === undefined) {
|
|
134
|
+
this.logger.error(`scheduleForPublication: no outbox publisher registered for dataSource '${dataSource}'`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
publisher.scheduleForPublication(event);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Read-only snapshot of the dataSources this facade routes to.
|
|
141
|
+
* Useful for diagnostics and tests.
|
|
142
|
+
*/
|
|
143
|
+
getRegisteredDataSources() {
|
|
144
|
+
return Array.from(this.publishers.keys());
|
|
145
|
+
}
|
|
146
|
+
resolveDataSource(event, options) {
|
|
147
|
+
if (options.dataSource !== undefined) {
|
|
148
|
+
return options.dataSource;
|
|
149
|
+
}
|
|
150
|
+
// Phase 14.3.1 — same helper used by `OutboxListenerScanner` and
|
|
151
|
+
// `MultiDsOutboxListenerRegistrar` so the routing policy (one
|
|
152
|
+
// owning DS per event class, throw on 0 or many matches) stays
|
|
153
|
+
// consistent across publish and listener-registration paths.
|
|
154
|
+
const eventType = event.constructor.name;
|
|
155
|
+
return (0, event_type_resolver_1.resolveDataSourceByEventTypeName)(eventType, this.eventTypeRegistries);
|
|
156
|
+
}
|
|
157
|
+
requirePublisher(dataSource) {
|
|
158
|
+
const publisher = this.publishers.get(dataSource);
|
|
159
|
+
if (publisher === undefined) {
|
|
160
|
+
throw new errors_1.OutboxError(`No outbox configured for dataSource '${dataSource}'. ` +
|
|
161
|
+
`Add 'OutboxModule.forRoot({ dataSource: '${dataSource}' })' to the module imports.`);
|
|
162
|
+
}
|
|
163
|
+
return publisher;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
exports.OutboxEventPublisher = OutboxEventPublisher;
|
|
167
|
+
exports.OutboxEventPublisher = OutboxEventPublisher = OutboxEventPublisher_1 = __decorate([
|
|
168
|
+
(0, common_1.Injectable)(),
|
|
169
|
+
__param(1, (0, common_1.Inject)(exports.OUTBOX_DATA_SOURCE_NAMES)),
|
|
170
|
+
__metadata("design:paramtypes", [core_1.ModuleRef, Object])
|
|
171
|
+
], OutboxEventPublisher);
|
|
172
|
+
//# sourceMappingURL=outbox-event-publisher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbox-event-publisher.js","sourceRoot":"","sources":["../../src/dispatcher/outbox-event-publisher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { CompletionMode } from '../types/completion-mode';
|
|
2
|
+
/**
|
|
3
|
+
* Tunables for the {@link EventPublicationProcessor} polling loop.
|
|
4
|
+
*
|
|
5
|
+
* Values are enforced at injection time — provide a fully-populated
|
|
6
|
+
* object (merge with {@link DEFAULT_PROCESSOR_OPTIONS} if you only
|
|
7
|
+
* want to override a subset).
|
|
8
|
+
*/
|
|
9
|
+
export interface EventPublicationProcessorOptions {
|
|
10
|
+
/** Milliseconds to wait between polling cycles. */
|
|
11
|
+
readonly pollingInterval: number;
|
|
12
|
+
/** Maximum publications fetched per poll. */
|
|
13
|
+
readonly batchSize: number;
|
|
14
|
+
/** Maximum listener invocations that may run in parallel inside one batch. */
|
|
15
|
+
readonly maxConcurrent: number;
|
|
16
|
+
/** How a successfully-delivered publication is finalized. */
|
|
17
|
+
readonly completionMode: CompletionMode;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Reasonable defaults for local development — a 1-second poll, 100 rows
|
|
21
|
+
* per batch, 10 parallel listener invocations, and `UPDATE` completion.
|
|
22
|
+
*/
|
|
23
|
+
export declare const DEFAULT_PROCESSOR_OPTIONS: EventPublicationProcessorOptions;
|
|
24
|
+
//# sourceMappingURL=processor-options.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_PROCESSOR_OPTIONS = void 0;
|
|
4
|
+
const completion_mode_1 = require("../types/completion-mode");
|
|
5
|
+
/**
|
|
6
|
+
* Reasonable defaults for local development — a 1-second poll, 100 rows
|
|
7
|
+
* per batch, 10 parallel listener invocations, and `UPDATE` completion.
|
|
8
|
+
*/
|
|
9
|
+
exports.DEFAULT_PROCESSOR_OPTIONS = {
|
|
10
|
+
pollingInterval: 1000,
|
|
11
|
+
batchSize: 100,
|
|
12
|
+
maxConcurrent: 10,
|
|
13
|
+
completionMode: completion_mode_1.CompletionMode.UPDATE,
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=processor-options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"processor-options.js","sourceRoot":"","sources":["../../src/dispatcher/processor-options.ts"],"names":[],"mappings":";;;AAAA,8DAA0D;AAoB1D;;;GAGG;AACU,QAAA,yBAAyB,GAAqC;IACzE,eAAe,EAAE,IAAI;IACrB,SAAS,EAAE,GAAG;IACd,aAAa,EAAE,EAAE;IACjB,cAAc,EAAE,gCAAc,CAAC,MAAM;CACtC,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { OutboxError } from '../types/errors';
|
|
2
|
+
/**
|
|
3
|
+
* Raised when an {@link EventExternalizer} fails to deliver a
|
|
4
|
+
* publication to its external broker target. Carries diagnostic
|
|
5
|
+
* context (event type, broker target, optional underlying cause) so
|
|
6
|
+
* operators can correlate a `FAILED` publication row with the broker
|
|
7
|
+
* issue without parsing free-form messages.
|
|
8
|
+
*
|
|
9
|
+
* Implementations are encouraged to wrap transport-specific failures
|
|
10
|
+
* in this error, but the `EventPublicationProcessor` does not
|
|
11
|
+
* require it — any rejection from `externalize()` is recorded on the
|
|
12
|
+
* publication.
|
|
13
|
+
*/
|
|
14
|
+
export declare class ExternalizationError extends OutboxError {
|
|
15
|
+
readonly eventType: string;
|
|
16
|
+
readonly target: string;
|
|
17
|
+
readonly cause?: Error | undefined;
|
|
18
|
+
readonly code = "EXTERNALIZATION_ERROR";
|
|
19
|
+
constructor(message: string, eventType: string, target: string, cause?: Error | undefined);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=errors.d.ts.map
|