@nestjs-transactional/outbox-typeorm 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 (30) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +347 -0
  3. package/dist/entity/event-publication-archive.entity.d.ts +24 -0
  4. package/dist/entity/event-publication-archive.entity.js +84 -0
  5. package/dist/entity/event-publication-archive.entity.js.map +1 -0
  6. package/dist/entity/event-publication.entity.d.ts +33 -0
  7. package/dist/entity/event-publication.entity.js +98 -0
  8. package/dist/entity/event-publication.entity.js.map +1 -0
  9. package/dist/index.d.ts +9 -0
  10. package/dist/index.js +25 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/migrations/1700000000000-create-event-publication.d.ts +18 -0
  13. package/dist/migrations/1700000000000-create-event-publication.js +26 -0
  14. package/dist/migrations/1700000000000-create-event-publication.js.map +1 -0
  15. package/dist/module/outbox-typeorm.module.d.ts +272 -0
  16. package/dist/module/outbox-typeorm.module.js +335 -0
  17. package/dist/module/outbox-typeorm.module.js.map +1 -0
  18. package/dist/repository/typeorm-event-publication.repository.d.ts +50 -0
  19. package/dist/repository/typeorm-event-publication.repository.js +237 -0
  20. package/dist/repository/typeorm-event-publication.repository.js.map +1 -0
  21. package/dist/schema/event-publication-schema.d.ts +30 -0
  22. package/dist/schema/event-publication-schema.js +124 -0
  23. package/dist/schema/event-publication-schema.js.map +1 -0
  24. package/dist/schema/schema-initialization-options.d.ts +24 -0
  25. package/dist/schema/schema-initialization-options.js +10 -0
  26. package/dist/schema/schema-initialization-options.js.map +1 -0
  27. package/dist/schema/schema-initializer.d.ts +31 -0
  28. package/dist/schema/schema-initializer.js +72 -0
  29. package/dist/schema/schema-initializer.js.map +1 -0
  30. package/package.json +86 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Igor Golovanov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,347 @@
1
+ # @nestjs-transactional/outbox-typeorm
2
+
3
+ [![npm version](https://img.shields.io/npm/v/%40nestjs-transactional%2Foutbox-typeorm/alpha?style=flat-square&label=npm)](https://www.npmjs.com/package/@nestjs-transactional/outbox-typeorm)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](https://github.com/igorgolovanov/nestjs-transactional/blob/main/LICENSE)
5
+
6
+ TypeORM persistence backend for
7
+ [`@nestjs-transactional/outbox`](../outbox). Ships the
8
+ `event_publication` table schema, a TypeORM-backed implementation of
9
+ the `EventPublicationRepository` SPI, and (in a later iteration) the
10
+ NestJS module wiring.
11
+
12
+ ## Status
13
+
14
+ Alpha. Public API may change between 0.x releases. Current shape:
15
+
16
+ - `EventPublicationEntity` / `EventPublicationArchiveEntity` schema
17
+ with all four indexes for worker / operator / cleanup paths.
18
+ - `TypeOrmEventPublicationRepository` integrates through the
19
+ transparent transactional repository patches in
20
+ [`@nestjs-transactional/typeorm`](../typeorm) — every read and
21
+ write commits atomically with the business transaction.
22
+ - `OutboxTypeOrmModule.forRoot({ dataSource?, isDefault? })` and
23
+ `forRootAsync({...})` — DataSource is resolved from DI via
24
+ `@nestjs/typeorm`'s `getDataSourceToken(name)`, mirroring
25
+ `TypeOrmTransactionalModule`.
26
+ - `SchemaInitializer` for development-time auto-init plus the
27
+ shipped TypeORM migration `CreateEventPublication1700000000000`.
28
+
29
+ Design notes: [`docs/roadmap/README.md`](../../docs/roadmap/README.md),
30
+ [ADR-006](../../docs/adr/006-outbox-pattern.md),
31
+ [ADR-019](../../docs/adr/019-outbox-multi-forroot-pattern.md).
32
+
33
+ ## What ships today
34
+
35
+ ### Entities
36
+
37
+ - `EventPublicationEntity` (`event_publication`): the hot queue. Four
38
+ indexes cover the worker, operator, and cleanup paths:
39
+ - `(status, publicationDate)` — `findReadyForProcessing`,
40
+ `findStale`.
41
+ - `(status, listenerId)` — per-listener retries.
42
+ - `(eventType)` — operator queries and event externalization.
43
+ - `(completionDate)` — `findCompleted(olderThan)` and
44
+ `deleteCompleted(olderThan)`.
45
+ `status` is `varchar(32)` rather than a Postgres `enum`, to keep new
46
+ lifecycle states from forcing a type migration.
47
+
48
+ - `EventPublicationArchiveEntity` (`event_publication_archive`): the
49
+ cold audit trail used by the `ARCHIVE` completion mode. Same fields
50
+ as `EventPublicationEntity` except `completionDate` is non-nullable
51
+ — rows only arrive here after having completed.
52
+
53
+ ### Repository
54
+
55
+ `TypeOrmEventPublicationRepository` implements
56
+ `EventPublicationRepository` from `outbox`. Highlights:
57
+
58
+ - Every read and write goes through the ambient
59
+ `EntityManager` resolved by
60
+ `@nestjs-transactional/typeorm`'s `getCurrentEntityManager`, so
61
+ publication rows commit atomically with the business data when the
62
+ caller is inside a `@Transactional()` scope.
63
+ - `tryClaim` issues a single conditional `UPDATE`
64
+ (`WHERE id = :id AND status IN (PUBLISHED, RESUBMITTED)`) and
65
+ returns whether the row was actually transitioned — atomic under
66
+ concurrent workers.
67
+ - `findReadyForProcessing` uses
68
+ `SELECT ... FOR UPDATE SKIP LOCKED` so multiple workers can poll
69
+ without fighting for the same rows.
70
+ - `archiveCompleted` copies the row into
71
+ `event_publication_archive` and then deletes it from the hot queue
72
+ — atomicity comes from the ambient transaction the processor wraps
73
+ the listener invocation in.
74
+
75
+ ## Installation (once published)
76
+
77
+ ```bash
78
+ pnpm add @nestjs-transactional/core \
79
+ @nestjs-transactional/typeorm \
80
+ @nestjs-transactional/outbox \
81
+ @nestjs-transactional/outbox-typeorm
82
+ ```
83
+
84
+ Peer dependencies: `@nestjs/common`, `@nestjs/core`, `@nestjs/typeorm`,
85
+ `reflect-metadata`, `rxjs`, `typeorm`.
86
+
87
+ ## Usage
88
+
89
+ Full wiring for an application that publishes, processes, and
90
+ recovers events against a TypeORM-backed registry:
91
+
92
+ ```typescript
93
+ import { Module } from '@nestjs/common';
94
+ import { DataSource } from 'typeorm';
95
+ import { TransactionalModule } from '@nestjs-transactional/core';
96
+ import { TypeOrmTransactionalModule } from '@nestjs-transactional/typeorm';
97
+ import {
98
+ OutboxModule,
99
+ OutboxProcessingModule,
100
+ } from '@nestjs-transactional/outbox';
101
+ import {
102
+ EventPublicationEntity,
103
+ EventPublicationArchiveEntity,
104
+ OutboxTypeOrmModule,
105
+ typeOrmEventPublicationRepositoryProvider,
106
+ } from '@nestjs-transactional/outbox-typeorm';
107
+
108
+ import { OrderPlacedEvent } from './events';
109
+
110
+ const dataSource = new DataSource({
111
+ type: 'postgres',
112
+ // ...
113
+ entities: [
114
+ EventPublicationEntity,
115
+ EventPublicationArchiveEntity,
116
+ // ...your domain entities
117
+ ],
118
+ });
119
+
120
+ @Module({
121
+ imports: [
122
+ // 1. Core transaction infrastructure — must be global so
123
+ // downstream modules can see TransactionManager.
124
+ TransactionalModule.forRoot({ isGlobal: true }),
125
+
126
+ // 2. TypeORM adapter registration. `forRoot` resolves the
127
+ // actual DataSource via @nestjs/typeorm's
128
+ // `getDataSourceToken(name)` — so `TypeOrmModule.forRoot(...)`
129
+ // must be imported above this. Activates transparent
130
+ // transactional Repository dispatch.
131
+ TypeOrmTransactionalModule.forRoot({ isDefault: true }),
132
+
133
+ // 3. Outbox-typeorm registration. `forRoot` resolves the
134
+ // DataSource from DI (same pattern as
135
+ // TypeOrmTransactionalModule). Registers the
136
+ // `TypeOrmEventPublicationRepository` under a private per-DS
137
+ // token; the cross-module bridge
138
+ // `typeOrmEventPublicationRepositoryProvider()` (passed to
139
+ // `OutboxModule.forRoot` below) aliases the official outbox
140
+ // token to that private one. The `SchemaInitializer` is
141
+ // instantiated per-DS — production should disable it and
142
+ // apply the shipped TypeORM migration instead.
143
+ OutboxTypeOrmModule.forRoot({
144
+ schemaInitialization: { enabled: process.env.NODE_ENV !== 'production' },
145
+ }),
146
+
147
+ // 4. Outbox-core wiring. Forward the TypeORM repository via the
148
+ // aliasing Provider so outbox does NOT install its
149
+ // InMemory default.
150
+ OutboxModule.forRoot({
151
+ repository: typeOrmEventPublicationRepositoryProvider(),
152
+ republishOnStartup: true,
153
+ processor: { pollingInterval: 1000, batchSize: 100 },
154
+ staleness: { processing: 60_000, monitorInterval: 30_000 },
155
+ }),
156
+
157
+ // 5. Register the event classes the outbox should know about.
158
+ // Each feature module would normally call forFeature() for the
159
+ // events it owns; this single-module example collapses them.
160
+ OutboxModule.forFeature([OrderPlacedEvent]),
161
+
162
+ // 6. Only in worker processes — starts the processor and
163
+ // staleness monitor on bootstrap. API-only apps that just
164
+ // publish events should NOT import this.
165
+ OutboxProcessingModule,
166
+ ],
167
+ })
168
+ export class AppModule {}
169
+ ```
170
+
171
+ ### Why the `repository` forwarding provider
172
+
173
+ `OutboxModule.forRoot` defaults to
174
+ `InMemoryEventPublicationRepository` for the
175
+ `EVENT_PUBLICATION_REPOSITORY` token when `repository` is omitted.
176
+ Passing `typeOrmEventPublicationRepositoryProvider` replaces that
177
+ default with a `useExisting` alias pointing at the TypeORM
178
+ implementation registered by `OutboxTypeOrmModule.forFeature`. Leaving
179
+ the option out would install two providers for the same token —
180
+ the InMemory one would win and your publications would never reach
181
+ the database.
182
+
183
+ ### Publishing events
184
+
185
+ ```typescript
186
+ import { Injectable } from '@nestjs/common';
187
+ import { Transactional } from '@nestjs-transactional/core';
188
+ import { OutboxEventPublisher } from '@nestjs-transactional/outbox';
189
+
190
+ @Injectable()
191
+ export class PlaceOrderHandler {
192
+ constructor(private readonly outbox: OutboxEventPublisher) {}
193
+
194
+ @Transactional()
195
+ async handle(orderId: string): Promise<void> {
196
+ // ...persist business data in the same transaction...
197
+ await this.outbox.publish(new OrderPlacedEvent(orderId));
198
+ }
199
+ }
200
+ ```
201
+
202
+ The publication row commits atomically with the business data. If
203
+ the transaction rolls back, the publication row is rolled back too
204
+ — there is no "event published without the business change landing"
205
+ failure mode.
206
+
207
+ ### Declaring a handler
208
+
209
+ ```typescript
210
+ import { Injectable } from '@nestjs/common';
211
+ import {
212
+ type IOutboxEventHandler,
213
+ OutboxEventsHandler,
214
+ } from '@nestjs-transactional/outbox';
215
+
216
+ @Injectable()
217
+ @OutboxEventsHandler(OrderPlacedEvent)
218
+ export class InventoryReservationHandler
219
+ implements IOutboxEventHandler<OrderPlacedEvent>
220
+ {
221
+ async handle(event: OrderPlacedEvent): Promise<void> {
222
+ // Runs in a fresh REQUIRES_NEW transaction after the publishing
223
+ // transaction has committed, retried on exception, resumable
224
+ // across process restarts.
225
+ }
226
+ }
227
+ ```
228
+
229
+ ## Schema management
230
+
231
+ Two supported paths, matching Spring Modulith's split between
232
+ reviewed schema changes and the
233
+ `spring.modulith.events.jdbc.schema-initialization.enabled`
234
+ developer shortcut.
235
+
236
+ ### Production: run the TypeORM migration (preferred)
237
+
238
+ The package ships a ready-to-use migration,
239
+ `CreateEventPublication1700000000000`, that creates both
240
+ `event_publication` and `event_publication_archive` with every
241
+ index. Register it with your DataSource and run it through the
242
+ TypeORM CLI as part of your deploy:
243
+
244
+ ```typescript
245
+ // data-source.ts
246
+ import { DataSource } from 'typeorm';
247
+ import { CreateEventPublication1700000000000 } from '@nestjs-transactional/outbox-typeorm';
248
+
249
+ export const dataSource = new DataSource({
250
+ type: 'postgres',
251
+ // ...
252
+ migrations: [CreateEventPublication1700000000000, /* ...your own */],
253
+ });
254
+ ```
255
+
256
+ ```bash
257
+ pnpm typeorm migration:run -d ./dist/data-source.js
258
+ ```
259
+
260
+ The timestamp `1700000000000` is a placeholder chosen to sort
261
+ before most application-owned migrations. Feel free to copy the
262
+ migration file into your own `migrations/` directory and rename it
263
+ to match your team's timestamp convention — the migration body is
264
+ just a call to `applyEventPublicationSchema(queryRunner)` from this
265
+ package, so keeping a thin wrapper in your own tree is encouraged.
266
+
267
+ ### Development: auto-init at bootstrap
268
+
269
+ Useful for local development and integration suites that spin a
270
+ fresh database up per run. `SchemaInitializer` is a
271
+ NestJS-lifecycle-aware provider that creates both tables on
272
+ application bootstrap when its `enabled` option is set:
273
+
274
+ ```typescript
275
+ import { Module } from '@nestjs/common';
276
+ import { DataSource } from 'typeorm';
277
+ import { getDataSourceToken } from '@nestjs/typeorm';
278
+ import {
279
+ SchemaInitializer,
280
+ SCHEMA_INITIALIZATION_OPTIONS,
281
+ } from '@nestjs-transactional/outbox-typeorm';
282
+
283
+ @Module({
284
+ providers: [
285
+ {
286
+ provide: SCHEMA_INITIALIZATION_OPTIONS,
287
+ useValue: { enabled: process.env.NODE_ENV !== 'production' },
288
+ },
289
+ {
290
+ provide: SchemaInitializer,
291
+ useFactory: (ds: DataSource, opts) => new SchemaInitializer(ds, opts),
292
+ inject: [getDataSourceToken(), SCHEMA_INITIALIZATION_OPTIONS],
293
+ },
294
+ ],
295
+ })
296
+ export class OutboxSchemaModule {}
297
+ ```
298
+
299
+ The initializer is a no-op when `enabled: false`. When enabled and
300
+ the hot table already exists, it logs a debug line and bails out
301
+ before running any DDL — safe to leave on across restarts. **Do
302
+ not enable in production** — schema changes should always go
303
+ through a reviewed migration.
304
+
305
+ ## Using with `@nestjs-transactional/cqrs`
306
+
307
+ When the application uses `@nestjs/cqrs` aggregates, bind
308
+ `OutboxEventPublisher` under the cqrs package's
309
+ `OUTBOX_PUBLICATION_SCHEDULER` token AND bind
310
+ `OutboxListenerRegistry` under `OUTBOX_LISTENER_REGISTRAR`.
311
+ `HybridEventPublisher` (wired by `CqrsTransactionalModule.forRoot()`)
312
+ then routes every `aggregate.commit()` through both the in-memory
313
+ phase-aware dispatcher AND the outbox, and
314
+ `IntegrationEventsHandlerScanner` routes
315
+ `@IntegrationEventsHandler` classes through the outbox worker.
316
+ See [`../cqrs/README.md#outbox-integration`](../cqrs/README.md#outbox-integration)
317
+ for the full wiring recipe and the trade-offs between
318
+ `@TransactionalEventsHandler`, `@OutboxEventsHandler`, and
319
+ `@IntegrationEventsHandler`.
320
+
321
+ ## Testing
322
+
323
+ Integration tests live under `test/integration/` and rely on
324
+ [`testcontainers-node`](https://node.testcontainers.org/) to spin up
325
+ a real Postgres 16 container for every run. Requires Docker to be
326
+ running locally:
327
+
328
+ ```bash
329
+ pnpm --filter @nestjs-transactional/outbox-typeorm test:integration
330
+ ```
331
+
332
+ Unit-test-only runs (`pnpm test`) skip the integration suite per the
333
+ shared Jest base config.
334
+
335
+ ## Worked examples
336
+
337
+ - [`basic-typeorm-outbox`](../../examples/basic-typeorm-outbox) — single-DS outbox with Postgres, atomicity proven by testcontainers.
338
+ - [`multi-datasource-outbox`](../../examples/multi-datasource-outbox) — per-DS `event_publication` tables (ADR-019 multi-`forRoot`).
339
+ - [`shared-database-modular-monolith`](../../examples/shared-database-modular-monolith) — one Postgres, multi-schema, per-module outbox stacks.
340
+ - [`saga-pattern`](../../examples/saga-pattern), [`audit-logging`](../../examples/audit-logging) — outbox-driven business saga and asymmetric audit-DS sink.
341
+ - [`e-commerce-orders`](../../examples/e-commerce-orders) — three-DataSource flagship using `OutboxTypeOrmModule.forRoot` per DS.
342
+
343
+ Full catalogue: [examples/README.md](../../examples/README.md).
344
+
345
+ ## License
346
+
347
+ MIT
@@ -0,0 +1,24 @@
1
+ import { PublicationStatus } from '@nestjs-transactional/outbox';
2
+ /**
3
+ * Archive table for event publications that have been completed and
4
+ * moved out of the hot queue — used by the `ARCHIVE` completion mode.
5
+ * Rows never change after insertion; this table is intended for audit,
6
+ * debugging, and compliance review rather than worker-time queries.
7
+ *
8
+ * Schema mirrors {@link EventPublicationEntity} except that
9
+ * `completionDate` is non-nullable — a publication is only archived
10
+ * after it has completed, so the field is always populated.
11
+ */
12
+ export declare class EventPublicationArchiveEntity {
13
+ id: string;
14
+ listenerId: string;
15
+ eventType: string;
16
+ serializedEvent: string;
17
+ publicationDate: Date;
18
+ status: PublicationStatus;
19
+ completionDate: Date;
20
+ lastResubmissionDate: Date | null;
21
+ completionAttempts: number;
22
+ failureReason: string | null;
23
+ }
24
+ //# sourceMappingURL=event-publication-archive.entity.d.ts.map
@@ -0,0 +1,84 @@
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.EventPublicationArchiveEntity = void 0;
13
+ const outbox_1 = require("@nestjs-transactional/outbox");
14
+ const typeorm_1 = require("typeorm");
15
+ /**
16
+ * Archive table for event publications that have been completed and
17
+ * moved out of the hot queue — used by the `ARCHIVE` completion mode.
18
+ * Rows never change after insertion; this table is intended for audit,
19
+ * debugging, and compliance review rather than worker-time queries.
20
+ *
21
+ * Schema mirrors {@link EventPublicationEntity} except that
22
+ * `completionDate` is non-nullable — a publication is only archived
23
+ * after it has completed, so the field is always populated.
24
+ */
25
+ let EventPublicationArchiveEntity = class EventPublicationArchiveEntity {
26
+ id;
27
+ listenerId;
28
+ eventType;
29
+ serializedEvent;
30
+ publicationDate;
31
+ status;
32
+ completionDate;
33
+ lastResubmissionDate;
34
+ completionAttempts;
35
+ failureReason;
36
+ };
37
+ exports.EventPublicationArchiveEntity = EventPublicationArchiveEntity;
38
+ __decorate([
39
+ (0, typeorm_1.PrimaryColumn)('uuid'),
40
+ __metadata("design:type", String)
41
+ ], EventPublicationArchiveEntity.prototype, "id", void 0);
42
+ __decorate([
43
+ (0, typeorm_1.Column)({ name: 'listener_id', length: 512 }),
44
+ __metadata("design:type", String)
45
+ ], EventPublicationArchiveEntity.prototype, "listenerId", void 0);
46
+ __decorate([
47
+ (0, typeorm_1.Column)({ name: 'event_type', length: 256 }),
48
+ __metadata("design:type", String)
49
+ ], EventPublicationArchiveEntity.prototype, "eventType", void 0);
50
+ __decorate([
51
+ (0, typeorm_1.Column)({ name: 'serialized_event', type: 'text' }),
52
+ __metadata("design:type", String)
53
+ ], EventPublicationArchiveEntity.prototype, "serializedEvent", void 0);
54
+ __decorate([
55
+ (0, typeorm_1.Column)({ name: 'publication_date', type: 'timestamptz' }),
56
+ __metadata("design:type", Date)
57
+ ], EventPublicationArchiveEntity.prototype, "publicationDate", void 0);
58
+ __decorate([
59
+ (0, typeorm_1.Column)({ type: 'varchar', length: 32 }),
60
+ __metadata("design:type", String)
61
+ ], EventPublicationArchiveEntity.prototype, "status", void 0);
62
+ __decorate([
63
+ (0, typeorm_1.Column)({ name: 'completion_date', type: 'timestamptz' }),
64
+ __metadata("design:type", Date)
65
+ ], EventPublicationArchiveEntity.prototype, "completionDate", void 0);
66
+ __decorate([
67
+ (0, typeorm_1.Column)({ name: 'last_resubmission_date', type: 'timestamptz', nullable: true }),
68
+ __metadata("design:type", Object)
69
+ ], EventPublicationArchiveEntity.prototype, "lastResubmissionDate", void 0);
70
+ __decorate([
71
+ (0, typeorm_1.Column)({ name: 'completion_attempts', type: 'int' }),
72
+ __metadata("design:type", Number)
73
+ ], EventPublicationArchiveEntity.prototype, "completionAttempts", void 0);
74
+ __decorate([
75
+ (0, typeorm_1.Column)({ name: 'failure_reason', type: 'text', nullable: true }),
76
+ __metadata("design:type", Object)
77
+ ], EventPublicationArchiveEntity.prototype, "failureReason", void 0);
78
+ exports.EventPublicationArchiveEntity = EventPublicationArchiveEntity = __decorate([
79
+ (0, typeorm_1.Entity)('event_publication_archive'),
80
+ (0, typeorm_1.Index)(['completionDate']),
81
+ (0, typeorm_1.Index)(['listenerId']),
82
+ (0, typeorm_1.Index)(['eventType'])
83
+ ], EventPublicationArchiveEntity);
84
+ //# sourceMappingURL=event-publication-archive.entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-publication-archive.entity.js","sourceRoot":"","sources":["../../src/entity/event-publication-archive.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,yDAAiE;AACjE,qCAA+D;AAE/D;;;;;;;;;GASG;AAKI,IAAM,6BAA6B,GAAnC,MAAM,6BAA6B;IAExC,EAAE,CAAU;IAGZ,UAAU,CAAU;IAGpB,SAAS,CAAU;IAGnB,eAAe,CAAU;IAGzB,eAAe,CAAQ;IAGvB,MAAM,CAAqB;IAG3B,cAAc,CAAQ;IAGtB,oBAAoB,CAAe;IAGnC,kBAAkB,CAAU;IAG5B,aAAa,CAAiB;CAC/B,CAAA;AA9BY,sEAA6B;AAExC;IADC,IAAA,uBAAa,EAAC,MAAM,CAAC;;yDACV;AAGZ;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;iEACzB;AAGpB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;gEACzB;AAGnB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;sEAC1B;AAGzB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;8BACxC,IAAI;sEAAC;AAGvB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;;6DACb;AAG3B;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;8BACxC,IAAI;qEAAC;AAGtB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,wBAAwB,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;2EAC7C;AAGnC;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,qBAAqB,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;;yEACzB;AAG5B;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;oEACnC;wCA7BnB,6BAA6B;IAJzC,IAAA,gBAAM,EAAC,2BAA2B,CAAC;IACnC,IAAA,eAAK,EAAC,CAAC,gBAAgB,CAAC,CAAC;IACzB,IAAA,eAAK,EAAC,CAAC,YAAY,CAAC,CAAC;IACrB,IAAA,eAAK,EAAC,CAAC,WAAW,CAAC,CAAC;GACR,6BAA6B,CA8BzC"}
@@ -0,0 +1,33 @@
1
+ import { PublicationStatus } from '@nestjs-transactional/outbox';
2
+ /**
3
+ * Hot-queue TypeORM entity backing the Event Publication Registry.
4
+ *
5
+ * Schema follows the contract of `EventPublication` from
6
+ * `@nestjs-transactional/outbox`. Rows move through the lifecycle
7
+ * `PUBLISHED → PROCESSING → COMPLETED` (or `FAILED → RESUBMITTED → ...`
8
+ * on retry).
9
+ *
10
+ * Indexes:
11
+ * - `(status, publicationDate)` — primary index for `findReadyForProcessing`
12
+ * (worker poll) and `findStale` (staleness monitor).
13
+ * - `(status, listenerId)` — looking up retries scoped to a single listener.
14
+ * - `(eventType)` — operator queries and event externalization filters.
15
+ * - `(completionDate)` — `findCompleted(olderThan)` and
16
+ * `deleteCompleted(olderThan)` cleanup.
17
+ *
18
+ * `status` is stored as `varchar(32)` rather than a Postgres `enum` type
19
+ * to avoid schema churn whenever a new lifecycle state is introduced.
20
+ */
21
+ export declare class EventPublicationEntity {
22
+ id: string;
23
+ listenerId: string;
24
+ eventType: string;
25
+ serializedEvent: string;
26
+ publicationDate: Date;
27
+ status: PublicationStatus;
28
+ completionDate: Date | null;
29
+ lastResubmissionDate: Date | null;
30
+ completionAttempts: number;
31
+ failureReason: string | null;
32
+ }
33
+ //# sourceMappingURL=event-publication.entity.d.ts.map
@@ -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
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.EventPublicationEntity = void 0;
13
+ const outbox_1 = require("@nestjs-transactional/outbox");
14
+ const typeorm_1 = require("typeorm");
15
+ /**
16
+ * Hot-queue TypeORM entity backing the Event Publication Registry.
17
+ *
18
+ * Schema follows the contract of `EventPublication` from
19
+ * `@nestjs-transactional/outbox`. Rows move through the lifecycle
20
+ * `PUBLISHED → PROCESSING → COMPLETED` (or `FAILED → RESUBMITTED → ...`
21
+ * on retry).
22
+ *
23
+ * Indexes:
24
+ * - `(status, publicationDate)` — primary index for `findReadyForProcessing`
25
+ * (worker poll) and `findStale` (staleness monitor).
26
+ * - `(status, listenerId)` — looking up retries scoped to a single listener.
27
+ * - `(eventType)` — operator queries and event externalization filters.
28
+ * - `(completionDate)` — `findCompleted(olderThan)` and
29
+ * `deleteCompleted(olderThan)` cleanup.
30
+ *
31
+ * `status` is stored as `varchar(32)` rather than a Postgres `enum` type
32
+ * to avoid schema churn whenever a new lifecycle state is introduced.
33
+ */
34
+ let EventPublicationEntity = class EventPublicationEntity {
35
+ id;
36
+ listenerId;
37
+ eventType;
38
+ serializedEvent;
39
+ publicationDate;
40
+ status;
41
+ completionDate;
42
+ lastResubmissionDate;
43
+ completionAttempts;
44
+ failureReason;
45
+ };
46
+ exports.EventPublicationEntity = EventPublicationEntity;
47
+ __decorate([
48
+ (0, typeorm_1.PrimaryColumn)('uuid'),
49
+ __metadata("design:type", String)
50
+ ], EventPublicationEntity.prototype, "id", void 0);
51
+ __decorate([
52
+ (0, typeorm_1.Column)({ name: 'listener_id', length: 512 }),
53
+ __metadata("design:type", String)
54
+ ], EventPublicationEntity.prototype, "listenerId", void 0);
55
+ __decorate([
56
+ (0, typeorm_1.Column)({ name: 'event_type', length: 256 }),
57
+ __metadata("design:type", String)
58
+ ], EventPublicationEntity.prototype, "eventType", void 0);
59
+ __decorate([
60
+ (0, typeorm_1.Column)({ name: 'serialized_event', type: 'text' }),
61
+ __metadata("design:type", String)
62
+ ], EventPublicationEntity.prototype, "serializedEvent", void 0);
63
+ __decorate([
64
+ (0, typeorm_1.Column)({ name: 'publication_date', type: 'timestamptz' }),
65
+ __metadata("design:type", Date)
66
+ ], EventPublicationEntity.prototype, "publicationDate", void 0);
67
+ __decorate([
68
+ (0, typeorm_1.Column)({
69
+ type: 'varchar',
70
+ length: 32,
71
+ default: outbox_1.PublicationStatus.PUBLISHED,
72
+ }),
73
+ __metadata("design:type", String)
74
+ ], EventPublicationEntity.prototype, "status", void 0);
75
+ __decorate([
76
+ (0, typeorm_1.Column)({ name: 'completion_date', type: 'timestamptz', nullable: true }),
77
+ __metadata("design:type", Object)
78
+ ], EventPublicationEntity.prototype, "completionDate", void 0);
79
+ __decorate([
80
+ (0, typeorm_1.Column)({ name: 'last_resubmission_date', type: 'timestamptz', nullable: true }),
81
+ __metadata("design:type", Object)
82
+ ], EventPublicationEntity.prototype, "lastResubmissionDate", void 0);
83
+ __decorate([
84
+ (0, typeorm_1.Column)({ name: 'completion_attempts', type: 'int', default: 0 }),
85
+ __metadata("design:type", Number)
86
+ ], EventPublicationEntity.prototype, "completionAttempts", void 0);
87
+ __decorate([
88
+ (0, typeorm_1.Column)({ name: 'failure_reason', type: 'text', nullable: true }),
89
+ __metadata("design:type", Object)
90
+ ], EventPublicationEntity.prototype, "failureReason", void 0);
91
+ exports.EventPublicationEntity = EventPublicationEntity = __decorate([
92
+ (0, typeorm_1.Entity)('event_publication'),
93
+ (0, typeorm_1.Index)(['status', 'publicationDate']),
94
+ (0, typeorm_1.Index)(['status', 'listenerId']),
95
+ (0, typeorm_1.Index)(['eventType']),
96
+ (0, typeorm_1.Index)(['completionDate'])
97
+ ], EventPublicationEntity);
98
+ //# sourceMappingURL=event-publication.entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-publication.entity.js","sourceRoot":"","sources":["../../src/entity/event-publication.entity.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,yDAAiE;AACjE,qCAA+D;AAE/D;;;;;;;;;;;;;;;;;;GAkBG;AAMI,IAAM,sBAAsB,GAA5B,MAAM,sBAAsB;IAEjC,EAAE,CAAU;IAGZ,UAAU,CAAU;IAGpB,SAAS,CAAU;IAGnB,eAAe,CAAU;IAGzB,eAAe,CAAQ;IAOvB,MAAM,CAAqB;IAG3B,cAAc,CAAe;IAG7B,oBAAoB,CAAe;IAGnC,kBAAkB,CAAU;IAG5B,aAAa,CAAiB;CAC/B,CAAA;AAlCY,wDAAsB;AAEjC;IADC,IAAA,uBAAa,EAAC,MAAM,CAAC;;kDACV;AAGZ;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;0DACzB;AAGpB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;yDACzB;AAGnB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;+DAC1B;AAGzB;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;8BACxC,IAAI;+DAAC;AAOvB;IALC,IAAA,gBAAM,EAAC;QACN,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,EAAE;QACV,OAAO,EAAE,0BAAiB,CAAC,SAAS;KACrC,CAAC;;sDACyB;AAG3B;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;8DAC5C;AAG7B;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,wBAAwB,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;oEAC7C;AAGnC;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,qBAAqB,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;;kEACrC;AAG5B;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;6DACnC;iCAjCnB,sBAAsB;IALlC,IAAA,gBAAM,EAAC,mBAAmB,CAAC;IAC3B,IAAA,eAAK,EAAC,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;IACpC,IAAA,eAAK,EAAC,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IAC/B,IAAA,eAAK,EAAC,CAAC,WAAW,CAAC,CAAC;IACpB,IAAA,eAAK,EAAC,CAAC,gBAAgB,CAAC,CAAC;GACb,sBAAsB,CAkClC"}
@@ -0,0 +1,9 @@
1
+ export * from './entity/event-publication.entity';
2
+ export * from './entity/event-publication-archive.entity';
3
+ export * from './repository/typeorm-event-publication.repository';
4
+ export * from './schema/event-publication-schema';
5
+ export * from './schema/schema-initialization-options';
6
+ export * from './schema/schema-initializer';
7
+ export * from './migrations/1700000000000-create-event-publication';
8
+ export * from './module/outbox-typeorm.module';
9
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./entity/event-publication.entity"), exports);
18
+ __exportStar(require("./entity/event-publication-archive.entity"), exports);
19
+ __exportStar(require("./repository/typeorm-event-publication.repository"), exports);
20
+ __exportStar(require("./schema/event-publication-schema"), exports);
21
+ __exportStar(require("./schema/schema-initialization-options"), exports);
22
+ __exportStar(require("./schema/schema-initializer"), exports);
23
+ __exportStar(require("./migrations/1700000000000-create-event-publication"), exports);
24
+ __exportStar(require("./module/outbox-typeorm.module"), exports);
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oEAAkD;AAClD,4EAA0D;AAC1D,oFAAkE;AAClE,oEAAkD;AAClD,yEAAuD;AACvD,8DAA4C;AAC5C,sFAAoE;AACpE,iEAA+C"}