@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,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"}
|