@apso/domain-events 0.1.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 (50) hide show
  1. package/README.md +68 -0
  2. package/dist/destinations/delivery-destination.d.ts +25 -0
  3. package/dist/destinations/delivery-destination.d.ts.map +1 -0
  4. package/dist/destinations/delivery-destination.js +10 -0
  5. package/dist/destinations/delivery-destination.js.map +1 -0
  6. package/dist/destinations/eventbridge.destination.d.ts +23 -0
  7. package/dist/destinations/eventbridge.destination.d.ts.map +1 -0
  8. package/dist/destinations/eventbridge.destination.js +55 -0
  9. package/dist/destinations/eventbridge.destination.js.map +1 -0
  10. package/dist/destinations/index.d.ts +22 -0
  11. package/dist/destinations/index.d.ts.map +1 -0
  12. package/dist/destinations/index.js +60 -0
  13. package/dist/destinations/index.js.map +1 -0
  14. package/dist/destinations/kafka.destination.d.ts +24 -0
  15. package/dist/destinations/kafka.destination.d.ts.map +1 -0
  16. package/dist/destinations/kafka.destination.js +66 -0
  17. package/dist/destinations/kafka.destination.js.map +1 -0
  18. package/dist/destinations/sqs.destination.d.ts +20 -0
  19. package/dist/destinations/sqs.destination.d.ts.map +1 -0
  20. package/dist/destinations/sqs.destination.js +46 -0
  21. package/dist/destinations/sqs.destination.js.map +1 -0
  22. package/dist/destinations/webhook.destination.d.ts +34 -0
  23. package/dist/destinations/webhook.destination.d.ts.map +1 -0
  24. package/dist/destinations/webhook.destination.js +73 -0
  25. package/dist/destinations/webhook.destination.js.map +1 -0
  26. package/dist/domain-event.entity.d.ts +28 -0
  27. package/dist/domain-event.entity.d.ts.map +1 -0
  28. package/dist/domain-event.entity.js +62 -0
  29. package/dist/domain-event.entity.js.map +1 -0
  30. package/dist/domain-event.mapper.d.ts +41 -0
  31. package/dist/domain-event.mapper.d.ts.map +1 -0
  32. package/dist/domain-event.mapper.js +42 -0
  33. package/dist/domain-event.mapper.js.map +1 -0
  34. package/dist/domain-event.relay.d.ts +80 -0
  35. package/dist/domain-event.relay.d.ts.map +1 -0
  36. package/dist/domain-event.relay.js +167 -0
  37. package/dist/domain-event.relay.js.map +1 -0
  38. package/dist/domain-event.subscriber.d.ts +33 -0
  39. package/dist/domain-event.subscriber.d.ts.map +1 -0
  40. package/dist/domain-event.subscriber.js +98 -0
  41. package/dist/domain-event.subscriber.js.map +1 -0
  42. package/dist/domain-events.module.d.ts +43 -0
  43. package/dist/domain-events.module.d.ts.map +1 -0
  44. package/dist/domain-events.module.js +74 -0
  45. package/dist/domain-events.module.js.map +1 -0
  46. package/dist/index.d.ts +7 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +23 -0
  49. package/dist/index.js.map +1 -0
  50. package/package.json +46 -0
@@ -0,0 +1,62 @@
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.DomainEvent = void 0;
13
+ const typeorm_1 = require("typeorm");
14
+ /**
15
+ * DomainEvent is a durable, append-only log of domain state changes.
16
+ *
17
+ * Rows are written in the SAME database transaction as the state change that
18
+ * produced them (see {@link DomainEventSubscriber}), which is what makes
19
+ * delivery durable. This implements the standard "transactional outbox"
20
+ * pattern, but is surfaced with generic domain-event naming — no public
21
+ * artifact is named "outbox".
22
+ *
23
+ * A separate {@link DomainEventRelay} polls pending rows and delivers them.
24
+ * Because the stable `id` is preserved across delivery attempts, consumers can
25
+ * dedupe on it for at-least-once delivery semantics.
26
+ */
27
+ let DomainEvent = class DomainEvent {
28
+ };
29
+ exports.DomainEvent = DomainEvent;
30
+ __decorate([
31
+ (0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
32
+ __metadata("design:type", String)
33
+ ], DomainEvent.prototype, "id", void 0);
34
+ __decorate([
35
+ (0, typeorm_1.Column)({ type: 'varchar' }),
36
+ __metadata("design:type", String)
37
+ ], DomainEvent.prototype, "type", void 0);
38
+ __decorate([
39
+ (0, typeorm_1.Column)({ type: 'jsonb' }),
40
+ __metadata("design:type", Object)
41
+ ], DomainEvent.prototype, "payload", void 0);
42
+ __decorate([
43
+ (0, typeorm_1.Column)({ type: 'varchar', default: 'pending' }),
44
+ __metadata("design:type", String)
45
+ ], DomainEvent.prototype, "status", void 0);
46
+ __decorate([
47
+ (0, typeorm_1.Column)({ type: 'int', default: 0 }),
48
+ __metadata("design:type", Number)
49
+ ], DomainEvent.prototype, "attempts", void 0);
50
+ __decorate([
51
+ (0, typeorm_1.CreateDateColumn)({ type: 'timestamptz' }),
52
+ __metadata("design:type", Date)
53
+ ], DomainEvent.prototype, "created_at", void 0);
54
+ __decorate([
55
+ (0, typeorm_1.Column)({ type: 'timestamptz', nullable: true }),
56
+ __metadata("design:type", Object)
57
+ ], DomainEvent.prototype, "publishedAt", void 0);
58
+ exports.DomainEvent = DomainEvent = __decorate([
59
+ (0, typeorm_1.Index)(['status', 'created_at']),
60
+ (0, typeorm_1.Entity)('events')
61
+ ], DomainEvent);
62
+ //# sourceMappingURL=domain-event.entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-event.entity.js","sourceRoot":"","sources":["../src/domain-event.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qCAMiB;AAEjB;;;;;;;;;;;;GAYG;AAGI,IAAM,WAAW,GAAjB,MAAM,WAAW;CA0BvB,CAAA;AA1BY,kCAAW;AAEtB;IADC,IAAA,gCAAsB,EAAC,MAAM,CAAC;;uCACnB;AAIZ;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;yCACd;AAId;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;;4CACQ;AAIlC;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;;2CACJ;AAI5C;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;6CAClB;AAGlB;IADC,IAAA,0BAAgB,EAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;8BAC7B,IAAI;+CAAC;AAIlB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;gDACtB;sBAzBf,WAAW;IAFvB,IAAA,eAAK,EAAC,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IAC/B,IAAA,gBAAM,EAAC,QAAQ,CAAC;GACJ,WAAW,CA0BvB"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Action that produced a domain event.
3
+ */
4
+ export type DomainEventAction = 'created' | 'updated' | 'removed';
5
+ /**
6
+ * DI token for the {@link DomainEventMapper}.
7
+ *
8
+ * {@link DomainEventsModule} binds this token to {@link DefaultDomainEventMapper}.
9
+ * Your application can override it by providing its own implementation:
10
+ *
11
+ * { provide: DOMAIN_EVENT_MAPPER, useClass: MyDomainEventMapper }
12
+ */
13
+ export declare const DOMAIN_EVENT_MAPPER = "DOMAIN_EVENT_MAPPER";
14
+ /**
15
+ * DomainEventMapper is the extension point that keeps application semantics
16
+ * (event-type taxonomy, payload shape) OUT of the library. Provide your own
17
+ * implementation under {@link DOMAIN_EVENT_MAPPER} to customize.
18
+ */
19
+ export interface DomainEventMapper {
20
+ /**
21
+ * Maps an entity name + action to an event type string, e.g.
22
+ * ("Product", "created") -> "product.created".
23
+ */
24
+ eventType(entityName: string, action: DomainEventAction): string;
25
+ /**
26
+ * Builds the serializable payload for an event from the changed entity.
27
+ */
28
+ toPayload(entity: unknown, action: DomainEventAction): Record<string, unknown>;
29
+ }
30
+ /**
31
+ * Default mapper. Uses an `entity.action` type taxonomy (entity name
32
+ * lower-camel-cased) and returns the entity as-is (shallow) as the payload.
33
+ *
34
+ * Override by providing your own {@link DomainEventMapper} under
35
+ * {@link DOMAIN_EVENT_MAPPER}.
36
+ */
37
+ export declare class DefaultDomainEventMapper implements DomainEventMapper {
38
+ eventType(entityName: string, action: DomainEventAction): string;
39
+ toPayload(entity: unknown, _action: DomainEventAction): Record<string, unknown>;
40
+ }
41
+ //# sourceMappingURL=domain-event.mapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-event.mapper.d.ts","sourceRoot":"","sources":["../src/domain-event.mapper.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAElE;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,wBAAwB,CAAC;AAEzD;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAAC;IAEjE;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChF;AAED;;;;;;GAMG;AACH,qBACa,wBAAyB,YAAW,iBAAiB;IAChE,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,MAAM;IAOhE,SAAS,CACP,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAG3B"}
@@ -0,0 +1,42 @@
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.DefaultDomainEventMapper = exports.DOMAIN_EVENT_MAPPER = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ /**
12
+ * DI token for the {@link DomainEventMapper}.
13
+ *
14
+ * {@link DomainEventsModule} binds this token to {@link DefaultDomainEventMapper}.
15
+ * Your application can override it by providing its own implementation:
16
+ *
17
+ * { provide: DOMAIN_EVENT_MAPPER, useClass: MyDomainEventMapper }
18
+ */
19
+ exports.DOMAIN_EVENT_MAPPER = 'DOMAIN_EVENT_MAPPER';
20
+ /**
21
+ * Default mapper. Uses an `entity.action` type taxonomy (entity name
22
+ * lower-camel-cased) and returns the entity as-is (shallow) as the payload.
23
+ *
24
+ * Override by providing your own {@link DomainEventMapper} under
25
+ * {@link DOMAIN_EVENT_MAPPER}.
26
+ */
27
+ let DefaultDomainEventMapper = class DefaultDomainEventMapper {
28
+ eventType(entityName, action) {
29
+ const normalized = entityName
30
+ ? entityName.charAt(0).toLowerCase() + entityName.slice(1)
31
+ : entityName;
32
+ return `${normalized}.${action}`;
33
+ }
34
+ toPayload(entity, _action) {
35
+ return { ...entity };
36
+ }
37
+ };
38
+ exports.DefaultDomainEventMapper = DefaultDomainEventMapper;
39
+ exports.DefaultDomainEventMapper = DefaultDomainEventMapper = __decorate([
40
+ (0, common_1.Injectable)()
41
+ ], DefaultDomainEventMapper);
42
+ //# sourceMappingURL=domain-event.mapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-event.mapper.js","sourceRoot":"","sources":["../src/domain-event.mapper.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA4C;AAO5C;;;;;;;GAOG;AACU,QAAA,mBAAmB,GAAG,qBAAqB,CAAC;AAoBzD;;;;;;GAMG;AAEI,IAAM,wBAAwB,GAA9B,MAAM,wBAAwB;IACnC,SAAS,CAAC,UAAkB,EAAE,MAAyB;QACrD,MAAM,UAAU,GAAG,UAAU;YAC3B,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;YAC1D,CAAC,CAAC,UAAU,CAAC;QACf,OAAO,GAAG,UAAU,IAAI,MAAM,EAAE,CAAC;IACnC,CAAC;IAED,SAAS,CACP,MAAe,EACf,OAA0B;QAE1B,OAAO,EAAE,GAAI,MAAkC,EAAE,CAAC;IACpD,CAAC;CACF,CAAA;AAdY,4DAAwB;mCAAxB,wBAAwB;IADpC,IAAA,mBAAU,GAAE;GACA,wBAAwB,CAcpC"}
@@ -0,0 +1,80 @@
1
+ import { OnApplicationBootstrap, OnModuleDestroy } from '@nestjs/common';
2
+ import { Repository } from 'typeorm';
3
+ import { DomainEvent } from './domain-event.entity';
4
+ import { DeliveryDestination } from './destinations';
5
+ /**
6
+ * Maximum number of delivery attempts before an event is marked 'failed'.
7
+ */
8
+ export declare const MAX_ATTEMPTS = 5;
9
+ /**
10
+ * Default poll interval (ms) for the self-contained drain loop.
11
+ */
12
+ export declare const DEFAULT_POLL_INTERVAL_MS = 5000;
13
+ /**
14
+ * DI token carrying the poll interval (ms) for the relay's self-contained
15
+ * poller. Provided by {@link DomainEventsModule.forRoot}.
16
+ */
17
+ export declare const DOMAIN_EVENT_POLL_INTERVAL = "DOMAIN_EVENT_POLL_INTERVAL";
18
+ /**
19
+ * DomainEventRelay drains the durable `events` table and delivers pending
20
+ * events. It is the consumer side of the transactional-outbox pattern: the
21
+ * subscriber writes events transactionally, the relay publishes them
22
+ * asynchronously with at-least-once semantics.
23
+ *
24
+ * SELF-CONTAINED POLLER: on application bootstrap the relay starts its own
25
+ * `setInterval` drain loop and clears it on shutdown. It depends on NO external
26
+ * scheduler package (no `@nestjs/schedule`). The interval defaults to
27
+ * {@link DEFAULT_POLL_INTERVAL_MS} and is configurable via `forRoot`.
28
+ *
29
+ * DELIVERY: `publish()` fans each event out to every ACTIVE
30
+ * {@link DeliveryDestination} (chosen at runtime via the `EVENTS_DESTINATION`
31
+ * env var). Any thrown error bubbles so the relay retries.
32
+ *
33
+ * DUPLICATES — IMPORTANT: delivery is tracked at the event grain (a single
34
+ * `events.status`), NOT per (event × destination). With MORE THAN ONE active
35
+ * destination, a failure in any one destination retries the WHOLE event, so the
36
+ * healthy destinations receive the event AGAIN on every retry. Consumer-side
37
+ * dedupe on `event.id` is therefore MANDATORY, not optional, whenever you run
38
+ * multiple destinations. (A single destination — the common case — never
39
+ * duplicates beyond ordinary at-least-once.) You can override `publish()` to
40
+ * change this behavior.
41
+ */
42
+ export declare class DomainEventRelay implements OnApplicationBootstrap, OnModuleDestroy {
43
+ private readonly events;
44
+ private readonly destinations;
45
+ private readonly pollIntervalMs;
46
+ private readonly logger;
47
+ private timer?;
48
+ private draining;
49
+ constructor(events: Repository<DomainEvent>, destinations?: DeliveryDestination[], pollIntervalMs?: number);
50
+ /**
51
+ * Starts the self-contained periodic drain. Called by Nest on app bootstrap.
52
+ */
53
+ onApplicationBootstrap(): void;
54
+ /**
55
+ * Stops the periodic drain. Called by Nest on shutdown.
56
+ */
57
+ onModuleDestroy(): void;
58
+ /**
59
+ * One non-overlapping drain pass. Errors are swallowed (logged) so a single
60
+ * bad tick never kills the interval.
61
+ */
62
+ private tick;
63
+ /**
64
+ * Load pending events oldest-first and deliver each. On success the event is
65
+ * marked `published` with `publishedAt`; on failure `attempts` increments and
66
+ * the event is marked `failed` once `attempts >= MAX_ATTEMPTS`.
67
+ */
68
+ processPending(limit?: number): Promise<void>;
69
+ /**
70
+ * Deliver a single domain event.
71
+ *
72
+ * Fans the event out to every ACTIVE {@link DeliveryDestination}. Any rejection
73
+ * bubbles so the relay retries (and, with multiple destinations, re-sends to
74
+ * ALL of them — see the class doc on mandatory consumer-side dedupe). Override
75
+ * to change delivery behavior. When no destination is active (empty
76
+ * EVENTS_DESTINATION), this throws so the misconfiguration surfaces.
77
+ */
78
+ publish(event: DomainEvent): Promise<void>;
79
+ }
80
+ //# sourceMappingURL=domain-event.relay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-event.relay.d.ts","sourceRoot":"","sources":["../src/domain-event.relay.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,sBAAsB,EACtB,eAAe,EAChB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EACL,mBAAmB,EAEpB,MAAM,gBAAgB,CAAC;AAExB;;GAEG;AACH,eAAO,MAAM,YAAY,IAAI,CAAC;AAE9B;;GAEG;AACH,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C;;;GAGG;AACH,eAAO,MAAM,0BAA0B,+BAA+B,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBACa,gBACX,YAAW,sBAAsB,EAAE,eAAe;IAQhD,OAAO,CAAC,QAAQ,CAAC,MAAM;IAGvB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAG7B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAZjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqC;IAC5D,OAAO,CAAC,KAAK,CAAC,CAAiC;IAC/C,OAAO,CAAC,QAAQ,CAAS;gBAIN,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,EAG/B,YAAY,GAAE,mBAAmB,EAAO,EAGxC,cAAc,GAAE,MAAiC;IAGpE;;OAEG;IACH,sBAAsB,IAAI,IAAI;IAW9B;;OAEG;IACH,eAAe,IAAI,IAAI;IAOvB;;;OAGG;YACW,IAAI;IAelB;;;;OAIG;IACG,cAAc,CAAC,KAAK,SAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IA8B/C;;;;;;;;OAQG;IACG,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;CAUjD"}
@@ -0,0 +1,167 @@
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 DomainEventRelay_1;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.DomainEventRelay = exports.DOMAIN_EVENT_POLL_INTERVAL = exports.DEFAULT_POLL_INTERVAL_MS = exports.MAX_ATTEMPTS = void 0;
17
+ const common_1 = require("@nestjs/common");
18
+ const typeorm_1 = require("@nestjs/typeorm");
19
+ const typeorm_2 = require("typeorm");
20
+ const domain_event_entity_1 = require("./domain-event.entity");
21
+ const destinations_1 = require("./destinations");
22
+ /**
23
+ * Maximum number of delivery attempts before an event is marked 'failed'.
24
+ */
25
+ exports.MAX_ATTEMPTS = 5;
26
+ /**
27
+ * Default poll interval (ms) for the self-contained drain loop.
28
+ */
29
+ exports.DEFAULT_POLL_INTERVAL_MS = 5000;
30
+ /**
31
+ * DI token carrying the poll interval (ms) for the relay's self-contained
32
+ * poller. Provided by {@link DomainEventsModule.forRoot}.
33
+ */
34
+ exports.DOMAIN_EVENT_POLL_INTERVAL = 'DOMAIN_EVENT_POLL_INTERVAL';
35
+ /**
36
+ * DomainEventRelay drains the durable `events` table and delivers pending
37
+ * events. It is the consumer side of the transactional-outbox pattern: the
38
+ * subscriber writes events transactionally, the relay publishes them
39
+ * asynchronously with at-least-once semantics.
40
+ *
41
+ * SELF-CONTAINED POLLER: on application bootstrap the relay starts its own
42
+ * `setInterval` drain loop and clears it on shutdown. It depends on NO external
43
+ * scheduler package (no `@nestjs/schedule`). The interval defaults to
44
+ * {@link DEFAULT_POLL_INTERVAL_MS} and is configurable via `forRoot`.
45
+ *
46
+ * DELIVERY: `publish()` fans each event out to every ACTIVE
47
+ * {@link DeliveryDestination} (chosen at runtime via the `EVENTS_DESTINATION`
48
+ * env var). Any thrown error bubbles so the relay retries.
49
+ *
50
+ * DUPLICATES — IMPORTANT: delivery is tracked at the event grain (a single
51
+ * `events.status`), NOT per (event × destination). With MORE THAN ONE active
52
+ * destination, a failure in any one destination retries the WHOLE event, so the
53
+ * healthy destinations receive the event AGAIN on every retry. Consumer-side
54
+ * dedupe on `event.id` is therefore MANDATORY, not optional, whenever you run
55
+ * multiple destinations. (A single destination — the common case — never
56
+ * duplicates beyond ordinary at-least-once.) You can override `publish()` to
57
+ * change this behavior.
58
+ */
59
+ let DomainEventRelay = DomainEventRelay_1 = class DomainEventRelay {
60
+ constructor(events, destinations = [], pollIntervalMs = exports.DEFAULT_POLL_INTERVAL_MS) {
61
+ this.events = events;
62
+ this.destinations = destinations;
63
+ this.pollIntervalMs = pollIntervalMs;
64
+ this.logger = new common_1.Logger(DomainEventRelay_1.name);
65
+ this.draining = false;
66
+ }
67
+ /**
68
+ * Starts the self-contained periodic drain. Called by Nest on app bootstrap.
69
+ */
70
+ onApplicationBootstrap() {
71
+ if (this.timer)
72
+ return;
73
+ this.timer = setInterval(() => {
74
+ void this.tick();
75
+ }, this.pollIntervalMs);
76
+ // Don't keep the event loop alive solely for the poller.
77
+ if (typeof this.timer.unref === 'function') {
78
+ this.timer.unref();
79
+ }
80
+ }
81
+ /**
82
+ * Stops the periodic drain. Called by Nest on shutdown.
83
+ */
84
+ onModuleDestroy() {
85
+ if (this.timer) {
86
+ clearInterval(this.timer);
87
+ this.timer = undefined;
88
+ }
89
+ }
90
+ /**
91
+ * One non-overlapping drain pass. Errors are swallowed (logged) so a single
92
+ * bad tick never kills the interval.
93
+ */
94
+ async tick() {
95
+ if (this.draining)
96
+ return;
97
+ this.draining = true;
98
+ try {
99
+ await this.processPending();
100
+ }
101
+ catch (error) {
102
+ this.logger.error('DomainEventRelay drain tick failed', error instanceof Error ? error.stack : undefined);
103
+ }
104
+ finally {
105
+ this.draining = false;
106
+ }
107
+ }
108
+ /**
109
+ * Load pending events oldest-first and deliver each. On success the event is
110
+ * marked `published` with `publishedAt`; on failure `attempts` increments and
111
+ * the event is marked `failed` once `attempts >= MAX_ATTEMPTS`.
112
+ */
113
+ async processPending(limit = 50) {
114
+ const pending = await this.events.find({
115
+ where: { status: 'pending' },
116
+ order: { created_at: 'ASC' },
117
+ take: limit,
118
+ });
119
+ for (const event of pending) {
120
+ try {
121
+ // eslint-disable-next-line no-await-in-loop
122
+ await this.publish(event);
123
+ event.status = 'published';
124
+ event.publishedAt = new Date();
125
+ // eslint-disable-next-line no-await-in-loop
126
+ await this.events.save(event);
127
+ }
128
+ catch (error) {
129
+ event.attempts += 1;
130
+ if (event.attempts >= exports.MAX_ATTEMPTS) {
131
+ event.status = 'failed';
132
+ this.logger.error(`DomainEvent ${event.id} (${event.type}) failed after ${event.attempts} attempts`, error instanceof Error ? error.stack : undefined);
133
+ }
134
+ // eslint-disable-next-line no-await-in-loop
135
+ await this.events.save(event);
136
+ }
137
+ }
138
+ }
139
+ /**
140
+ * Deliver a single domain event.
141
+ *
142
+ * Fans the event out to every ACTIVE {@link DeliveryDestination}. Any rejection
143
+ * bubbles so the relay retries (and, with multiple destinations, re-sends to
144
+ * ALL of them — see the class doc on mandatory consumer-side dedupe). Override
145
+ * to change delivery behavior. When no destination is active (empty
146
+ * EVENTS_DESTINATION), this throws so the misconfiguration surfaces.
147
+ */
148
+ async publish(event) {
149
+ if (this.destinations?.length) {
150
+ await Promise.all(this.destinations.map((d) => d.send(event)));
151
+ return;
152
+ }
153
+ throw new Error('DomainEventRelay.publish() has no active destinations — set ' +
154
+ 'EVENTS_DESTINATION (comma-separated) or override publish()');
155
+ }
156
+ };
157
+ exports.DomainEventRelay = DomainEventRelay;
158
+ exports.DomainEventRelay = DomainEventRelay = DomainEventRelay_1 = __decorate([
159
+ (0, common_1.Injectable)(),
160
+ __param(0, (0, typeorm_1.InjectRepository)(domain_event_entity_1.DomainEvent)),
161
+ __param(1, (0, common_1.Optional)()),
162
+ __param(1, (0, common_1.Inject)(destinations_1.DOMAIN_EVENT_DESTINATIONS)),
163
+ __param(2, (0, common_1.Optional)()),
164
+ __param(2, (0, common_1.Inject)(exports.DOMAIN_EVENT_POLL_INTERVAL)),
165
+ __metadata("design:paramtypes", [typeorm_2.Repository, Array, Number])
166
+ ], DomainEventRelay);
167
+ //# sourceMappingURL=domain-event.relay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-event.relay.js","sourceRoot":"","sources":["../src/domain-event.relay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { DataSource, EntitySubscriberInterface, InsertEvent, UpdateEvent, RemoveEvent } from 'typeorm';
2
+ import { DomainEventMapper } from './domain-event.mapper';
3
+ /**
4
+ * DI token carrying the set of entity classes that have opted in to
5
+ * domain-event emission. Provided by {@link DomainEventsModule.forRoot} from the
6
+ * `entities` it is given. The library NEVER hardcodes this set — it is the
7
+ * CLI-emitted manifest of opted-in entities.
8
+ */
9
+ export declare const DOMAIN_EVENT_ENTITIES = "DOMAIN_EVENT_ENTITIES";
10
+ /**
11
+ * DomainEventSubscriber writes a {@link DomainEvent} row for every
12
+ * insert/update/remove of an opted-in entity.
13
+ *
14
+ * DURABILITY: each row is written via `event.manager` (the EntityManager of the
15
+ * in-flight transaction), so the event is committed atomically WITH the state
16
+ * change. This is the durability lever behind the transactional-outbox pattern.
17
+ *
18
+ * RECURSION GUARD: we never emit events for {@link DomainEvent} itself, otherwise
19
+ * each emitted row would itself trigger another emission. Only entities in the
20
+ * opted-in set (passed to {@link DomainEventsModule.forRoot}) emit; everything
21
+ * else is skipped.
22
+ */
23
+ export declare class DomainEventSubscriber implements EntitySubscriberInterface {
24
+ private readonly mapper;
25
+ private readonly emittingEntities;
26
+ constructor(mapper: DomainEventMapper, emittingEntities: Function[], dataSource?: DataSource);
27
+ private isEmitting;
28
+ private emit;
29
+ afterInsert(event: InsertEvent<unknown>): Promise<void>;
30
+ afterUpdate(event: UpdateEvent<unknown>): Promise<void>;
31
+ afterRemove(event: RemoveEvent<unknown>): Promise<void>;
32
+ }
33
+ //# sourceMappingURL=domain-event.subscriber.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-event.subscriber.d.ts","sourceRoot":"","sources":["../src/domain-event.subscriber.ts"],"names":[],"mappings":"AACA,OAAO,EACL,UAAU,EAEV,yBAAyB,EAEzB,WAAW,EACX,WAAW,EACX,WAAW,EACZ,MAAM,SAAS,CAAC;AAEjB,OAAO,EAGL,iBAAiB,EAClB,MAAM,uBAAuB,CAAC;AAE/B;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,0BAA0B,CAAC;AAE7D;;;;;;;;;;;;GAYG;AACH,qBAEa,qBAAsB,YAAW,yBAAyB;IAEtC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAErB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;gBAFlB,MAAM,EAAE,iBAAiB,EAEvB,gBAAgB,EAAE,QAAQ,EAAE,EAKhE,UAAU,CAAC,EAAE,UAAU;IAMrC,OAAO,CAAC,UAAU;YAUJ,IAAI;IAoBZ,WAAW,CAAC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvD,WAAW,CAAC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvD,WAAW,CAAC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAU9D"}
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.DomainEventSubscriber = exports.DOMAIN_EVENT_ENTITIES = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const typeorm_1 = require("typeorm");
18
+ const domain_event_entity_1 = require("./domain-event.entity");
19
+ const domain_event_mapper_1 = require("./domain-event.mapper");
20
+ /**
21
+ * DI token carrying the set of entity classes that have opted in to
22
+ * domain-event emission. Provided by {@link DomainEventsModule.forRoot} from the
23
+ * `entities` it is given. The library NEVER hardcodes this set — it is the
24
+ * CLI-emitted manifest of opted-in entities.
25
+ */
26
+ exports.DOMAIN_EVENT_ENTITIES = 'DOMAIN_EVENT_ENTITIES';
27
+ /**
28
+ * DomainEventSubscriber writes a {@link DomainEvent} row for every
29
+ * insert/update/remove of an opted-in entity.
30
+ *
31
+ * DURABILITY: each row is written via `event.manager` (the EntityManager of the
32
+ * in-flight transaction), so the event is committed atomically WITH the state
33
+ * change. This is the durability lever behind the transactional-outbox pattern.
34
+ *
35
+ * RECURSION GUARD: we never emit events for {@link DomainEvent} itself, otherwise
36
+ * each emitted row would itself trigger another emission. Only entities in the
37
+ * opted-in set (passed to {@link DomainEventsModule.forRoot}) emit; everything
38
+ * else is skipped.
39
+ */
40
+ let DomainEventSubscriber = class DomainEventSubscriber {
41
+ constructor(mapper, emittingEntities, dataSource) {
42
+ this.mapper = mapper;
43
+ this.emittingEntities = emittingEntities;
44
+ dataSource?.subscribers?.push(this);
45
+ }
46
+ // eslint-disable-next-line @typescript-eslint/ban-types
47
+ isEmitting(target) {
48
+ // RECURSION GUARD: never emit for DomainEvent itself.
49
+ if (target === domain_event_entity_1.DomainEvent || target === 'DomainEvent') {
50
+ return false;
51
+ }
52
+ return this.emittingEntities.some((cls) => target === cls || target === cls.name);
53
+ }
54
+ async emit(manager,
55
+ // eslint-disable-next-line @typescript-eslint/ban-types
56
+ entityClass, entity, action) {
57
+ const entityName = entityClass.name;
58
+ const repo = manager.getRepository(domain_event_entity_1.DomainEvent);
59
+ // manager keeps us inside the active transaction.
60
+ await repo.save(repo.create({
61
+ type: this.mapper.eventType(entityName, action),
62
+ payload: this.mapper.toPayload(entity, action),
63
+ status: 'pending',
64
+ attempts: 0,
65
+ }));
66
+ }
67
+ async afterInsert(event) {
68
+ if (!this.isEmitting(event.metadata.target))
69
+ return;
70
+ await this.emit(event.manager,
71
+ // eslint-disable-next-line @typescript-eslint/ban-types
72
+ event.metadata.target, event.entity, 'created');
73
+ }
74
+ async afterUpdate(event) {
75
+ if (!this.isEmitting(event.metadata.target))
76
+ return;
77
+ await this.emit(event.manager,
78
+ // eslint-disable-next-line @typescript-eslint/ban-types
79
+ event.metadata.target, event.entity ?? event.databaseEntity, 'updated');
80
+ }
81
+ async afterRemove(event) {
82
+ if (!this.isEmitting(event.metadata.target))
83
+ return;
84
+ await this.emit(event.manager,
85
+ // eslint-disable-next-line @typescript-eslint/ban-types
86
+ event.metadata.target, event.entity ?? event.databaseEntity, 'removed');
87
+ }
88
+ };
89
+ exports.DomainEventSubscriber = DomainEventSubscriber;
90
+ exports.DomainEventSubscriber = DomainEventSubscriber = __decorate([
91
+ (0, common_1.Injectable)(),
92
+ (0, typeorm_1.EventSubscriber)(),
93
+ __param(0, (0, common_1.Inject)(domain_event_mapper_1.DOMAIN_EVENT_MAPPER)),
94
+ __param(1, (0, common_1.Inject)(exports.DOMAIN_EVENT_ENTITIES)),
95
+ __param(2, (0, common_1.Optional)()),
96
+ __metadata("design:paramtypes", [Object, Array, typeorm_1.DataSource])
97
+ ], DomainEventSubscriber);
98
+ //# sourceMappingURL=domain-event.subscriber.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-event.subscriber.js","sourceRoot":"","sources":["../src/domain-event.subscriber.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAA8D;AAC9D,qCAQiB;AACjB,+DAAoD;AACpD,+DAI+B;AAE/B;;;;;GAKG;AACU,QAAA,qBAAqB,GAAG,uBAAuB,CAAC;AAE7D;;;;;;;;;;;;GAYG;AAGI,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IAChC,YACgD,MAAyB,EAEvB,gBAA4B,EAKhE,UAAuB;QAPW,WAAM,GAAN,MAAM,CAAmB;QAEvB,qBAAgB,GAAhB,gBAAgB,CAAY;QAO5E,UAAU,EAAE,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,wDAAwD;IAChD,UAAU,CAAC,MAAyB;QAC1C,sDAAsD;QACtD,IAAI,MAAM,KAAK,iCAAW,IAAI,MAAM,KAAK,aAAa,EAAE,CAAC;YACvD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAC/B,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,CAAC,IAAI,CAC/C,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,IAAI,CAChB,OAAsB;IACtB,wDAAwD;IACxD,WAAqB,EACrB,MAAe,EACf,MAAyB;QAEzB,MAAM,UAAU,GAAG,WAAW,CAAC,IAAI,CAAC;QACpC,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,iCAAW,CAAC,CAAC;QAChD,kDAAkD;QAClD,MAAM,IAAI,CAAC,IAAI,CACb,IAAI,CAAC,MAAM,CAAC;YACV,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC;YAC/C,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC;YAC9C,MAAM,EAAE,SAAS;YACjB,QAAQ,EAAE,CAAC;SACZ,CAAC,CACH,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,KAA2B;QAC3C,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO;QACpD,MAAM,IAAI,CAAC,IAAI,CACb,KAAK,CAAC,OAAO;QACb,wDAAwD;QACxD,KAAK,CAAC,QAAQ,CAAC,MAAkB,EACjC,KAAK,CAAC,MAAM,EACZ,SAAS,CACV,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,KAA2B;QAC3C,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO;QACpD,MAAM,IAAI,CAAC,IAAI,CACb,KAAK,CAAC,OAAO;QACb,wDAAwD;QACxD,KAAK,CAAC,QAAQ,CAAC,MAAkB,EACjC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,cAAc,EACpC,SAAS,CACV,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,KAA2B;QAC3C,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO;QACpD,MAAM,IAAI,CAAC,IAAI,CACb,KAAK,CAAC,OAAO;QACb,wDAAwD;QACxD,KAAK,CAAC,QAAQ,CAAC,MAAkB,EACjC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,cAAc,EACpC,SAAS,CACV,CAAC;IACJ,CAAC;CACF,CAAA;AA7EY,sDAAqB;gCAArB,qBAAqB;IAFjC,IAAA,mBAAU,GAAE;IACZ,IAAA,yBAAe,GAAE;IAGb,WAAA,IAAA,eAAM,EAAC,yCAAmB,CAAC,CAAA;IAE3B,WAAA,IAAA,eAAM,EAAC,6BAAqB,CAAC,CAAA;IAK7B,WAAA,IAAA,iBAAQ,GAAE,CAAA;oDAAc,oBAAU;GAT1B,qBAAqB,CA6EjC"}
@@ -0,0 +1,43 @@
1
+ import { DynamicModule, Type } from '@nestjs/common';
2
+ import { DomainEventMapper } from './domain-event.mapper';
3
+ /**
4
+ * Options for {@link DomainEventsModule.forRoot}.
5
+ */
6
+ export interface DomainEventsModuleOptions {
7
+ /**
8
+ * Entity classes opted in to domain-event emission (the CLI-emitted
9
+ * manifest). Only state changes to these emit events; the library never
10
+ * hardcodes this set.
11
+ */
12
+ entities: Function[];
13
+ /**
14
+ * Optional custom mapper class to bind under {@link DOMAIN_EVENT_MAPPER}.
15
+ * Defaults to {@link DefaultDomainEventMapper}.
16
+ */
17
+ mapper?: Type<DomainEventMapper>;
18
+ /**
19
+ * Poll interval (ms) for the relay's self-contained drain loop. Defaults to
20
+ * {@link DEFAULT_POLL_INTERVAL_MS} (5000).
21
+ */
22
+ pollIntervalMs?: number;
23
+ }
24
+ /**
25
+ * DomainEventsModule wires the durable domain-event spine (transactional-outbox
26
+ * pattern, surfaced as generic domain events).
27
+ *
28
+ * - {@link DomainEventSubscriber} writes events in-transaction with state changes.
29
+ * - {@link DomainEventRelay} drains and delivers pending events, with its own
30
+ * self-contained poller (no external scheduler dependency).
31
+ * - {@link DOMAIN_EVENT_MAPPER} controls event-type/payload semantics; pass a
32
+ * `mapper` to override.
33
+ * - {@link DOMAIN_EVENT_DESTINATIONS} is the set of ACTIVE delivery adapters,
34
+ * built from the `EVENTS_DESTINATION` env var at runtime.
35
+ *
36
+ * MULTI-DATASOURCE: `@EventSubscriber()` auto-registers on the default
37
+ * DataSource; for additional named DataSources, register this module (or the
38
+ * subscriber) per DataSource.
39
+ */
40
+ export declare class DomainEventsModule {
41
+ static forRoot(options: DomainEventsModuleOptions): DynamicModule;
42
+ }
43
+ //# sourceMappingURL=domain-events.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-events.module.d.ts","sourceRoot":"","sources":["../src/domain-events.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAA4B,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAY/E,OAAO,EAGL,iBAAiB,EAClB,MAAM,uBAAuB,CAAC;AAG/B;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IAEH,QAAQ,EAAE,QAAQ,EAAE,CAAC;IAErB;;;OAGG;IACH,MAAM,CAAC,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAEjC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAEa,kBAAkB;IAC7B,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,yBAAyB,GAAG,aAAa;CAmClE"}