@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,41 @@
1
+ import { Type } from '@nestjs/common';
2
+ /**
3
+ * Registry that maps event class names to their constructors so the
4
+ * serializer can reconstruct class instances from stored JSON payloads.
5
+ *
6
+ * Event classes are registered at application startup, typically via
7
+ * `OutboxModule.forFeature([...])` in the feature module that owns the
8
+ * event class. Each event type can only be registered once — duplicate
9
+ * registrations throw at bootstrap, so a misconfigured module fails
10
+ * fast rather than silently shadowing an earlier registration.
11
+ */
12
+ export declare class EventTypeRegistry {
13
+ private readonly registry;
14
+ /**
15
+ * Register a single event class, keyed by its constructor name.
16
+ *
17
+ * @throws `Error` when an event class with the same constructor name
18
+ * has already been registered. Duplicate registration almost always
19
+ * indicates a configuration bug (the same event type appearing in
20
+ * two `forFeature` calls) — fail fast rather than silently shadow.
21
+ */
22
+ register(eventType: Type<object>): void;
23
+ /** Register many event classes at once. */
24
+ registerAll(eventTypes: Type<object>[]): void;
25
+ /** Return the registered class for the given name, or `undefined`. */
26
+ get(typeName: string): Type<object> | undefined;
27
+ /**
28
+ * Return the registered class for the given name.
29
+ *
30
+ * @throws `Error` with an actionable message when the type is
31
+ * unregistered — tells the caller how to register it.
32
+ */
33
+ getOrThrow(typeName: string): Type<object>;
34
+ /** Whether the given type name is known to the registry. */
35
+ has(typeName: string): boolean;
36
+ /** Return a snapshot of all registered entries. Mutations are not observed. */
37
+ getAll(): Map<string, Type<object>>;
38
+ }
39
+ /** DI token for the {@link EventTypeRegistry}. */
40
+ export declare const EVENT_TYPE_REGISTRY: unique symbol;
41
+ //# sourceMappingURL=event-type-registry.d.ts.map
@@ -0,0 +1,79 @@
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.EVENT_TYPE_REGISTRY = exports.EventTypeRegistry = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ /**
12
+ * Registry that maps event class names to their constructors so the
13
+ * serializer can reconstruct class instances from stored JSON payloads.
14
+ *
15
+ * Event classes are registered at application startup, typically via
16
+ * `OutboxModule.forFeature([...])` in the feature module that owns the
17
+ * event class. Each event type can only be registered once — duplicate
18
+ * registrations throw at bootstrap, so a misconfigured module fails
19
+ * fast rather than silently shadowing an earlier registration.
20
+ */
21
+ let EventTypeRegistry = class EventTypeRegistry {
22
+ registry = new Map();
23
+ /**
24
+ * Register a single event class, keyed by its constructor name.
25
+ *
26
+ * @throws `Error` when an event class with the same constructor name
27
+ * has already been registered. Duplicate registration almost always
28
+ * indicates a configuration bug (the same event type appearing in
29
+ * two `forFeature` calls) — fail fast rather than silently shadow.
30
+ */
31
+ register(eventType) {
32
+ if (this.registry.has(eventType.name)) {
33
+ throw new Error(`Event type '${eventType.name}' already registered. ` +
34
+ `Each event type can only be registered once — check for ` +
35
+ `duplicate entries across OutboxModule.forFeature() calls.`);
36
+ }
37
+ this.registry.set(eventType.name, eventType);
38
+ }
39
+ /** Register many event classes at once. */
40
+ registerAll(eventTypes) {
41
+ for (const type of eventTypes) {
42
+ this.register(type);
43
+ }
44
+ }
45
+ /** Return the registered class for the given name, or `undefined`. */
46
+ get(typeName) {
47
+ return this.registry.get(typeName);
48
+ }
49
+ /**
50
+ * Return the registered class for the given name.
51
+ *
52
+ * @throws `Error` with an actionable message when the type is
53
+ * unregistered — tells the caller how to register it.
54
+ */
55
+ getOrThrow(typeName) {
56
+ const type = this.registry.get(typeName);
57
+ if (!type) {
58
+ throw new Error(`Event type '${typeName}' not registered. ` +
59
+ `Ensure it's registered via OutboxModule.forFeature([...]) in ` +
60
+ `the feature module that owns this event class.`);
61
+ }
62
+ return type;
63
+ }
64
+ /** Whether the given type name is known to the registry. */
65
+ has(typeName) {
66
+ return this.registry.has(typeName);
67
+ }
68
+ /** Return a snapshot of all registered entries. Mutations are not observed. */
69
+ getAll() {
70
+ return new Map(this.registry);
71
+ }
72
+ };
73
+ exports.EventTypeRegistry = EventTypeRegistry;
74
+ exports.EventTypeRegistry = EventTypeRegistry = __decorate([
75
+ (0, common_1.Injectable)()
76
+ ], EventTypeRegistry);
77
+ /** DI token for the {@link EventTypeRegistry}. */
78
+ exports.EVENT_TYPE_REGISTRY = Symbol('EVENT_TYPE_REGISTRY');
79
+ //# sourceMappingURL=event-type-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-type-registry.js","sourceRoot":"","sources":["../../src/serialization/event-type-registry.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAkD;AAElD;;;;;;;;;GASG;AAEI,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IACX,QAAQ,GAAG,IAAI,GAAG,EAAwB,CAAC;IAE5D;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAuB;QAC9B,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,MAAM,IAAI,KAAK,CACb,eAAe,SAAS,CAAC,IAAI,wBAAwB;gBACnD,0DAA0D;gBAC1D,2DAA2D,CAC9D,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAC/C,CAAC;IAED,2CAA2C;IAC3C,WAAW,CAAC,UAA0B;QACpC,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;YAC9B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAED,sEAAsE;IACtE,GAAG,CAAC,QAAgB;QAClB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC;IAED;;;;;OAKG;IACH,UAAU,CAAC,QAAgB;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CACb,eAAe,QAAQ,oBAAoB;gBACzC,+DAA+D;gBAC/D,gDAAgD,CACnD,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,4DAA4D;IAC5D,GAAG,CAAC,QAAgB;QAClB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC;IAED,+EAA+E;IAC/E,MAAM;QACJ,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;CACF,CAAA;AA7DY,8CAAiB;4BAAjB,iBAAiB;IAD7B,IAAA,mBAAU,GAAE;GACA,iBAAiB,CA6D7B;AAED,kDAAkD;AACrC,QAAA,mBAAmB,GAAG,MAAM,CAAC,qBAAqB,CAAC,CAAC"}
@@ -0,0 +1,43 @@
1
+ import type { EventTypeRegistry } from './event-type-registry';
2
+ /**
3
+ * Resolve the dataSource that owns a given event-type name by walking
4
+ * a Map of per-dataSource {@link EventTypeRegistry} instances. Returns
5
+ * the dataSource name as a string when exactly one registry contains
6
+ * the event-type — otherwise throws {@link OutboxError} with an
7
+ * actionable message.
8
+ *
9
+ * Single source of truth shared by three consumers (Phase 14.3.1):
10
+ *
11
+ * - {@link OutboxEventPublisher.resolveDataSource} — smart-facade
12
+ * publisher routes events to the dataSource that registered them.
13
+ * - `OutboxListenerScanner` — decorator-driven `@OutboxEventsHandler`
14
+ * handlers register with the per-dataSource
15
+ * `OutboxListenerRegistry` whose dataSource owns the event class.
16
+ * - `MultiDsOutboxListenerRegistrar` — bridges
17
+ * `@IntegrationEventsHandler` from the cqrs package's structural
18
+ * port to the per-dataSource registry.
19
+ *
20
+ * Failure modes:
21
+ *
22
+ * - **Zero matches** (event not registered anywhere): the caller is
23
+ * almost always missing an `OutboxModule.forFeature([X], { dataSource })`
24
+ * call. The thrown message names the event and suggests the fix.
25
+ * - **Multiple matches** (event registered in more than one dataSource):
26
+ * ambiguous routing. The thrown message names the event, lists the
27
+ * candidate dataSources, and suggests either restricting the
28
+ * registration to one dataSource or using an explicit
29
+ * `{ dataSource }` option (in publisher contexts) / programmatic
30
+ * per-DS listener registration (in scanner contexts).
31
+ *
32
+ * The same throw policy is applied across all three consumers so the
33
+ * mental model is uniform: an event class belongs to exactly one
34
+ * dataSource by registration.
35
+ *
36
+ * @param eventTypeName The event class's `constructor.name`.
37
+ * @param registries Map of dataSource name → {@link EventTypeRegistry}.
38
+ * @returns The dataSource name.
39
+ * @throws {OutboxError} when zero or multiple registries contain the
40
+ * event-type.
41
+ */
42
+ export declare function resolveDataSourceByEventTypeName(eventTypeName: string, registries: ReadonlyMap<string, EventTypeRegistry>): string;
43
+ //# sourceMappingURL=event-type-resolver.d.ts.map
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveDataSourceByEventTypeName = resolveDataSourceByEventTypeName;
4
+ const errors_1 = require("../types/errors");
5
+ /**
6
+ * Resolve the dataSource that owns a given event-type name by walking
7
+ * a Map of per-dataSource {@link EventTypeRegistry} instances. Returns
8
+ * the dataSource name as a string when exactly one registry contains
9
+ * the event-type — otherwise throws {@link OutboxError} with an
10
+ * actionable message.
11
+ *
12
+ * Single source of truth shared by three consumers (Phase 14.3.1):
13
+ *
14
+ * - {@link OutboxEventPublisher.resolveDataSource} — smart-facade
15
+ * publisher routes events to the dataSource that registered them.
16
+ * - `OutboxListenerScanner` — decorator-driven `@OutboxEventsHandler`
17
+ * handlers register with the per-dataSource
18
+ * `OutboxListenerRegistry` whose dataSource owns the event class.
19
+ * - `MultiDsOutboxListenerRegistrar` — bridges
20
+ * `@IntegrationEventsHandler` from the cqrs package's structural
21
+ * port to the per-dataSource registry.
22
+ *
23
+ * Failure modes:
24
+ *
25
+ * - **Zero matches** (event not registered anywhere): the caller is
26
+ * almost always missing an `OutboxModule.forFeature([X], { dataSource })`
27
+ * call. The thrown message names the event and suggests the fix.
28
+ * - **Multiple matches** (event registered in more than one dataSource):
29
+ * ambiguous routing. The thrown message names the event, lists the
30
+ * candidate dataSources, and suggests either restricting the
31
+ * registration to one dataSource or using an explicit
32
+ * `{ dataSource }` option (in publisher contexts) / programmatic
33
+ * per-DS listener registration (in scanner contexts).
34
+ *
35
+ * The same throw policy is applied across all three consumers so the
36
+ * mental model is uniform: an event class belongs to exactly one
37
+ * dataSource by registration.
38
+ *
39
+ * @param eventTypeName The event class's `constructor.name`.
40
+ * @param registries Map of dataSource name → {@link EventTypeRegistry}.
41
+ * @returns The dataSource name.
42
+ * @throws {OutboxError} when zero or multiple registries contain the
43
+ * event-type.
44
+ */
45
+ function resolveDataSourceByEventTypeName(eventTypeName, registries) {
46
+ const matches = [];
47
+ for (const [dataSource, registry] of registries) {
48
+ if (registry.has(eventTypeName)) {
49
+ matches.push(dataSource);
50
+ }
51
+ }
52
+ if (matches.length === 0) {
53
+ throw new errors_1.OutboxError(`Event type '${eventTypeName}' is not registered in any dataSource. ` +
54
+ `Add it to OutboxModule.forFeature([...], { dataSource: '...' }) ` +
55
+ `in the feature module that owns the event class.`);
56
+ }
57
+ if (matches.length > 1) {
58
+ throw new errors_1.OutboxError(`Event type '${eventTypeName}' is registered in multiple dataSources ` +
59
+ `(${matches.join(', ')}). Pass an explicit { dataSource } option to ` +
60
+ `disambiguate, or register the event in only one dataSource.`);
61
+ }
62
+ // Exactly one match — narrow safe.
63
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
64
+ return matches[0];
65
+ }
66
+ //# sourceMappingURL=event-type-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-type-resolver.js","sourceRoot":"","sources":["../../src/serialization/event-type-resolver.ts"],"names":[],"mappings":";;AA4CA,4EA4BC;AAxED,4CAA8C;AAI9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,SAAgB,gCAAgC,CAC9C,aAAqB,EACrB,UAAkD;IAElD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,IAAI,UAAU,EAAE,CAAC;QAChD,IAAI,QAAQ,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC;YAChC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,oBAAW,CACnB,eAAe,aAAa,yCAAyC;YACnE,kEAAkE;YAClE,kDAAkD,CACrD,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,oBAAW,CACnB,eAAe,aAAa,0CAA0C;YACpE,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,+CAA+C;YACrE,6DAA6D,CAChE,CAAC;IACJ,CAAC;IACD,mCAAmC;IACnC,oEAAoE;IACpE,OAAO,OAAO,CAAC,CAAC,CAAE,CAAC;AACrB,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { EventSerializer } from './event-serializer';
2
+ import { EventTypeRegistry } from './event-type-registry';
3
+ /**
4
+ * Default JSON-based {@link EventSerializer}. Uses `JSON.stringify` for
5
+ * encoding and `JSON.parse` + `Object.create` for decoding, consulting
6
+ * an {@link EventTypeRegistry} to restore the class prototype when
7
+ * possible.
8
+ *
9
+ * This implementation is intentionally simple: it restores the
10
+ * prototype but does not call the constructor, so classes with private
11
+ * fields, Value-Object invariants, or custom deserialization must
12
+ * provide their own `EventSerializer`.
13
+ */
14
+ export declare class JsonEventSerializer implements EventSerializer {
15
+ private readonly registry;
16
+ constructor(registry: EventTypeRegistry);
17
+ serialize(event: unknown): string;
18
+ deserialize(serialized: string, eventType: string): unknown;
19
+ }
20
+ //# sourceMappingURL=json-event-serializer.d.ts.map
@@ -0,0 +1,64 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.JsonEventSerializer = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const errors_1 = require("../types/errors");
15
+ const event_type_registry_1 = require("./event-type-registry");
16
+ /**
17
+ * Default JSON-based {@link EventSerializer}. Uses `JSON.stringify` for
18
+ * encoding and `JSON.parse` + `Object.create` for decoding, consulting
19
+ * an {@link EventTypeRegistry} to restore the class prototype when
20
+ * possible.
21
+ *
22
+ * This implementation is intentionally simple: it restores the
23
+ * prototype but does not call the constructor, so classes with private
24
+ * fields, Value-Object invariants, or custom deserialization must
25
+ * provide their own `EventSerializer`.
26
+ */
27
+ let JsonEventSerializer = class JsonEventSerializer {
28
+ registry;
29
+ constructor(registry) {
30
+ this.registry = registry;
31
+ }
32
+ serialize(event) {
33
+ if (!event || typeof event !== 'object') {
34
+ throw new errors_1.SerializationError(`Cannot serialize non-object: ${typeof event}`);
35
+ }
36
+ try {
37
+ return JSON.stringify(event);
38
+ }
39
+ catch (err) {
40
+ throw new errors_1.SerializationError(`Failed to serialize event: ${err.message}`);
41
+ }
42
+ }
43
+ deserialize(serialized, eventType) {
44
+ const EventClass = this.registry.get(eventType);
45
+ let data;
46
+ try {
47
+ data = JSON.parse(serialized);
48
+ }
49
+ catch (err) {
50
+ throw new errors_1.DeserializationError(`Failed to deserialize event '${eventType}': ${err.message}`);
51
+ }
52
+ if (!EventClass) {
53
+ return data;
54
+ }
55
+ const prototype = EventClass.prototype;
56
+ return Object.assign(Object.create(prototype), data);
57
+ }
58
+ };
59
+ exports.JsonEventSerializer = JsonEventSerializer;
60
+ exports.JsonEventSerializer = JsonEventSerializer = __decorate([
61
+ (0, common_1.Injectable)(),
62
+ __metadata("design:paramtypes", [event_type_registry_1.EventTypeRegistry])
63
+ ], JsonEventSerializer);
64
+ //# sourceMappingURL=json-event-serializer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-event-serializer.js","sourceRoot":"","sources":["../../src/serialization/json-event-serializer.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAE5C,4CAA2E;AAG3E,+DAA0D;AAE1D;;;;;;;;;;GAUG;AAEI,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IACD;IAA7B,YAA6B,QAA2B;QAA3B,aAAQ,GAAR,QAAQ,CAAmB;IAAG,CAAC;IAE5D,SAAS,CAAC,KAAc;QACtB,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACxC,MAAM,IAAI,2BAAkB,CAAC,gCAAgC,OAAO,KAAK,EAAE,CAAC,CAAC;QAC/E,CAAC;QACD,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,2BAAkB,CAC1B,8BAA+B,GAAa,CAAC,OAAO,EAAE,CACvD,CAAC;QACJ,CAAC;IACH,CAAC;IAED,WAAW,CAAC,UAAkB,EAAE,SAAiB;QAC/C,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAEhD,IAAI,IAAa,CAAC;QAClB,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAChC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,6BAAoB,CAC5B,gCAAgC,SAAS,MAAO,GAAa,CAAC,OAAO,EAAE,CACxE,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,SAAS,GAAW,UAAU,CAAC,SAAmB,CAAC;QACzD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAW,EAAE,IAAI,CAAC,CAAC;IACjE,CAAC;CACF,CAAA;AAnCY,kDAAmB;8BAAnB,mBAAmB;IAD/B,IAAA,mBAAU,GAAE;qCAE4B,uCAAiB;GAD7C,mBAAmB,CAmC/B"}
@@ -0,0 +1,71 @@
1
+ import { type Type } from '@nestjs/common';
2
+ import { PublishedEvents, type PublishedEventsView } from './published-events';
3
+ /**
4
+ * Thrown when an assertion made against the published-events view does
5
+ * not hold. A separate class (instead of a plain `Error`) so tests can
6
+ * pattern-match with `rejects.toBeInstanceOf(PublishedEventsAssertionError)`
7
+ * when they want to assert on failure modes.
8
+ */
9
+ export declare class PublishedEventsAssertionError extends Error {
10
+ constructor(message: string);
11
+ }
12
+ /**
13
+ * Jest-friendly fluent assertions over {@link PublishedEvents}. Inspired
14
+ * by Spring Modulith's `AssertablePublishedEvents`. Designed to read
15
+ * naturally at the call site:
16
+ *
17
+ * ```ts
18
+ * const view = await assertablePublishedEvents.contains(OrderPlacedEvent);
19
+ * view.matching((e) => e.orderId, 'order-123').hasSize(1);
20
+ * ```
21
+ *
22
+ * Both assertions throw {@link PublishedEventsAssertionError} on
23
+ * failure, which Jest renders as a normal test failure.
24
+ */
25
+ export declare class AssertablePublishedEvents {
26
+ private readonly events;
27
+ constructor(events: PublishedEvents);
28
+ /**
29
+ * Assert that at least one event of the given class was published,
30
+ * then return a view over those events for further assertions.
31
+ *
32
+ * @throws {PublishedEventsAssertionError} when no matching event
33
+ * was published.
34
+ */
35
+ contains<T extends object>(eventType: Type<T>): Promise<AssertionView<T>>;
36
+ /**
37
+ * Assert that NO event of the given class was published.
38
+ *
39
+ * @throws {PublishedEventsAssertionError} when one or more matching
40
+ * events were published.
41
+ */
42
+ doesNotContain<T extends object>(eventType: Type<T>): Promise<void>;
43
+ }
44
+ /**
45
+ * Materialised, filtered view returned by
46
+ * {@link AssertablePublishedEvents.contains}. Further filtering /
47
+ * sizing runs in memory — no additional repository reads.
48
+ */
49
+ export declare class AssertionView<T extends object> {
50
+ private readonly view;
51
+ private readonly events;
52
+ private readonly eventType;
53
+ constructor(view: PublishedEventsView<T>, events: readonly T[], eventType: Type<T>);
54
+ /**
55
+ * Narrow the view to events whose derived value equals `expected`.
56
+ * Returns a new {@link AssertionView} so assertions can chain.
57
+ *
58
+ * @throws {PublishedEventsAssertionError} when no event in the
59
+ * current view matches.
60
+ */
61
+ matching<K>(getter: (event: T) => K, expected: K): AssertionView<T>;
62
+ /**
63
+ * Assert the view contains exactly `expected` events.
64
+ *
65
+ * @throws {PublishedEventsAssertionError} when the count differs.
66
+ */
67
+ hasSize(expected: number): this;
68
+ /** Read the current events. Useful for ad-hoc assertions with Jest's matchers. */
69
+ toArray(): T[];
70
+ }
71
+ //# sourceMappingURL=assertable-published-events.d.ts.map
@@ -0,0 +1,124 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.AssertionView = exports.AssertablePublishedEvents = exports.PublishedEventsAssertionError = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const published_events_1 = require("./published-events");
15
+ /**
16
+ * Thrown when an assertion made against the published-events view does
17
+ * not hold. A separate class (instead of a plain `Error`) so tests can
18
+ * pattern-match with `rejects.toBeInstanceOf(PublishedEventsAssertionError)`
19
+ * when they want to assert on failure modes.
20
+ */
21
+ class PublishedEventsAssertionError extends Error {
22
+ constructor(message) {
23
+ super(message);
24
+ this.name = 'PublishedEventsAssertionError';
25
+ }
26
+ }
27
+ exports.PublishedEventsAssertionError = PublishedEventsAssertionError;
28
+ /**
29
+ * Jest-friendly fluent assertions over {@link PublishedEvents}. Inspired
30
+ * by Spring Modulith's `AssertablePublishedEvents`. Designed to read
31
+ * naturally at the call site:
32
+ *
33
+ * ```ts
34
+ * const view = await assertablePublishedEvents.contains(OrderPlacedEvent);
35
+ * view.matching((e) => e.orderId, 'order-123').hasSize(1);
36
+ * ```
37
+ *
38
+ * Both assertions throw {@link PublishedEventsAssertionError} on
39
+ * failure, which Jest renders as a normal test failure.
40
+ */
41
+ let AssertablePublishedEvents = class AssertablePublishedEvents {
42
+ events;
43
+ constructor(events) {
44
+ this.events = events;
45
+ }
46
+ /**
47
+ * Assert that at least one event of the given class was published,
48
+ * then return a view over those events for further assertions.
49
+ *
50
+ * @throws {PublishedEventsAssertionError} when no matching event
51
+ * was published.
52
+ */
53
+ async contains(eventType) {
54
+ const view = this.events.ofType(eventType);
55
+ const events = await view.get();
56
+ if (events.length === 0) {
57
+ throw new PublishedEventsAssertionError(`Expected at least one event of type ${eventType.name}, but found none`);
58
+ }
59
+ return new AssertionView(view, events, eventType);
60
+ }
61
+ /**
62
+ * Assert that NO event of the given class was published.
63
+ *
64
+ * @throws {PublishedEventsAssertionError} when one or more matching
65
+ * events were published.
66
+ */
67
+ async doesNotContain(eventType) {
68
+ const events = await this.events.ofType(eventType).get();
69
+ if (events.length > 0) {
70
+ throw new PublishedEventsAssertionError(`Expected no events of type ${eventType.name}, but found ${events.length}`);
71
+ }
72
+ }
73
+ };
74
+ exports.AssertablePublishedEvents = AssertablePublishedEvents;
75
+ exports.AssertablePublishedEvents = AssertablePublishedEvents = __decorate([
76
+ (0, common_1.Injectable)(),
77
+ __metadata("design:paramtypes", [published_events_1.PublishedEvents])
78
+ ], AssertablePublishedEvents);
79
+ /**
80
+ * Materialised, filtered view returned by
81
+ * {@link AssertablePublishedEvents.contains}. Further filtering /
82
+ * sizing runs in memory — no additional repository reads.
83
+ */
84
+ class AssertionView {
85
+ view;
86
+ events;
87
+ eventType;
88
+ constructor(view, events, eventType) {
89
+ this.view = view;
90
+ this.events = events;
91
+ this.eventType = eventType;
92
+ }
93
+ /**
94
+ * Narrow the view to events whose derived value equals `expected`.
95
+ * Returns a new {@link AssertionView} so assertions can chain.
96
+ *
97
+ * @throws {PublishedEventsAssertionError} when no event in the
98
+ * current view matches.
99
+ */
100
+ matching(getter, expected) {
101
+ const filtered = this.events.filter((event) => getter(event) === expected);
102
+ if (filtered.length === 0) {
103
+ throw new PublishedEventsAssertionError(`Expected at least one event of type ${this.eventType.name} matching the predicate, but none found`);
104
+ }
105
+ return new AssertionView(this.view, filtered, this.eventType);
106
+ }
107
+ /**
108
+ * Assert the view contains exactly `expected` events.
109
+ *
110
+ * @throws {PublishedEventsAssertionError} when the count differs.
111
+ */
112
+ hasSize(expected) {
113
+ if (this.events.length !== expected) {
114
+ throw new PublishedEventsAssertionError(`Expected ${expected} events of type ${this.eventType.name}, but found ${this.events.length}`);
115
+ }
116
+ return this;
117
+ }
118
+ /** Read the current events. Useful for ad-hoc assertions with Jest's matchers. */
119
+ toArray() {
120
+ return [...this.events];
121
+ }
122
+ }
123
+ exports.AssertionView = AssertionView;
124
+ //# sourceMappingURL=assertable-published-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assertable-published-events.js","sourceRoot":"","sources":["../../src/testing/assertable-published-events.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAuD;AAEvD,yDAA+E;AAE/E;;;;;GAKG;AACH,MAAa,6BAA8B,SAAQ,KAAK;IACtD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,+BAA+B,CAAC;IAC9C,CAAC;CACF;AALD,sEAKC;AAED;;;;;;;;;;;;GAYG;AAEI,IAAM,yBAAyB,GAA/B,MAAM,yBAAyB;IACP;IAA7B,YAA6B,MAAuB;QAAvB,WAAM,GAAN,MAAM,CAAiB;IAAG,CAAC;IAExD;;;;;;OAMG;IACH,KAAK,CAAC,QAAQ,CAAmB,SAAkB;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC3C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;QAChC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,6BAA6B,CACrC,uCAAuC,SAAS,CAAC,IAAI,kBAAkB,CACxE,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,aAAa,CAAI,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;IACvD,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,cAAc,CAAmB,SAAkB;QACvD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,GAAG,EAAE,CAAC;QACzD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,IAAI,6BAA6B,CACrC,8BAA8B,SAAS,CAAC,IAAI,eAAe,MAAM,CAAC,MAAM,EAAE,CAC3E,CAAC;QACJ,CAAC;IACH,CAAC;CACF,CAAA;AAnCY,8DAAyB;oCAAzB,yBAAyB;IADrC,IAAA,mBAAU,GAAE;qCAE0B,kCAAe;GADzC,yBAAyB,CAmCrC;AAED;;;;GAIG;AACH,MAAa,aAAa;IAEL;IACA;IACA;IAHnB,YACmB,IAA4B,EAC5B,MAAoB,EACpB,SAAkB;QAFlB,SAAI,GAAJ,IAAI,CAAwB;QAC5B,WAAM,GAAN,MAAM,CAAc;QACpB,cAAS,GAAT,SAAS,CAAS;IAClC,CAAC;IAEJ;;;;;;OAMG;IACH,QAAQ,CAAI,MAAuB,EAAE,QAAW;QAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,QAAQ,CAAC,CAAC;QAC3E,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,6BAA6B,CACrC,uCAAuC,IAAI,CAAC,SAAS,CAAC,IAAI,yCAAyC,CACpG,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,aAAa,CAAI,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IACnE,CAAC;IAED;;;;OAIG;IACH,OAAO,CAAC,QAAgB;QACtB,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YACpC,MAAM,IAAI,6BAA6B,CACrC,YAAY,QAAQ,mBAAmB,IAAI,CAAC,SAAS,CAAC,IAAI,eAAe,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAC9F,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,kFAAkF;IAClF,OAAO;QACL,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1B,CAAC;CACF;AA1CD,sCA0CC"}
@@ -0,0 +1,52 @@
1
+ import { TransactionManager } from '@nestjs-transactional/core';
2
+ import type { EventPublicationRepository, FindCompletedOptions, FindFailedOptions, UpdateStatusOptions } from '../repository/event-publication-repository';
3
+ import type { EventPublication, NewEventPublication } from '../types/event-publication';
4
+ import { PublicationStatus } from '../types/publication-status';
5
+ /**
6
+ * In-memory reference implementation of {@link EventPublicationRepository}.
7
+ *
8
+ * Intended for unit and integration tests of outbox consumers that
9
+ * do not want to spin up a real database. Not thread-safe — do not use
10
+ * in production.
11
+ *
12
+ * Optional `TransactionManager` enables transaction-aware behaviour:
13
+ * when a mutating method is called inside an active transaction, the
14
+ * repository registers an `afterRollback` hook that undoes the change
15
+ * — so the visible state after rollback matches what a real
16
+ * transactional backend would show. When no `TransactionManager` is
17
+ * provided (or no transaction is active), mutations apply immediately
18
+ * and are not undone.
19
+ *
20
+ * Exposes a few extra methods beyond the SPI (`reset`, `getAll`,
21
+ * `count`) to help tests set up and assert on state.
22
+ */
23
+ export declare class InMemoryEventPublicationRepository implements EventPublicationRepository {
24
+ private readonly transactionManager?;
25
+ private readonly publications;
26
+ constructor(transactionManager?: TransactionManager | undefined);
27
+ createAll(inputs: NewEventPublication[]): Promise<EventPublication[]>;
28
+ findById(id: string): Promise<EventPublication | null>;
29
+ updateStatus(id: string, status: PublicationStatus, options?: UpdateStatusOptions): Promise<void>;
30
+ tryClaim(id: string): Promise<boolean>;
31
+ findReadyForProcessing(limit: number): Promise<EventPublication[]>;
32
+ findStale(beforeDate: Date, statuses: PublicationStatus[]): Promise<EventPublication[]>;
33
+ findCompleted(options?: FindCompletedOptions): Promise<EventPublication[]>;
34
+ findIncomplete(): Promise<EventPublication[]>;
35
+ findFailed(options?: FindFailedOptions): Promise<EventPublication[]>;
36
+ deleteCompleted(olderThan?: Date): Promise<number>;
37
+ archiveCompleted(id: string): Promise<void>;
38
+ delete(id: string): Promise<void>;
39
+ /** Drop every stored publication. Does not interact with transactions. */
40
+ reset(): void;
41
+ /** Snapshot of every stored publication, in insertion order. */
42
+ getAll(): EventPublication[];
43
+ /** Number of stored publications. */
44
+ count(): number;
45
+ /**
46
+ * If a `TransactionManager` was provided and a transaction is active,
47
+ * register the given `undo` closure to fire on rollback. Otherwise
48
+ * this is a no-op.
49
+ */
50
+ private trackRollback;
51
+ }
52
+ //# sourceMappingURL=in-memory-repository.d.ts.map