@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.
- package/LICENSE +21 -0
- package/README.md +347 -0
- package/dist/entity/event-publication-archive.entity.d.ts +24 -0
- package/dist/entity/event-publication-archive.entity.js +84 -0
- package/dist/entity/event-publication-archive.entity.js.map +1 -0
- package/dist/entity/event-publication.entity.d.ts +33 -0
- package/dist/entity/event-publication.entity.js +98 -0
- package/dist/entity/event-publication.entity.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations/1700000000000-create-event-publication.d.ts +18 -0
- package/dist/migrations/1700000000000-create-event-publication.js +26 -0
- package/dist/migrations/1700000000000-create-event-publication.js.map +1 -0
- package/dist/module/outbox-typeorm.module.d.ts +272 -0
- package/dist/module/outbox-typeorm.module.js +335 -0
- package/dist/module/outbox-typeorm.module.js.map +1 -0
- package/dist/repository/typeorm-event-publication.repository.d.ts +50 -0
- package/dist/repository/typeorm-event-publication.repository.js +237 -0
- package/dist/repository/typeorm-event-publication.repository.js.map +1 -0
- package/dist/schema/event-publication-schema.d.ts +30 -0
- package/dist/schema/event-publication-schema.js +124 -0
- package/dist/schema/event-publication-schema.js.map +1 -0
- package/dist/schema/schema-initialization-options.d.ts +24 -0
- package/dist/schema/schema-initialization-options.js +10 -0
- package/dist/schema/schema-initialization-options.js.map +1 -0
- package/dist/schema/schema-initializer.d.ts +31 -0
- package/dist/schema/schema-initializer.js +72 -0
- package/dist/schema/schema-initializer.js.map +1 -0
- package/package.json +86 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { PublicationStatus, type EventPublication, type EventPublicationRepository, type FindCompletedOptions, type FindFailedOptions, type NewEventPublication, type UpdateStatusOptions } from '@nestjs-transactional/outbox';
|
|
2
|
+
import { type DataSource } from 'typeorm';
|
|
3
|
+
/**
|
|
4
|
+
* TypeORM-backed implementation of
|
|
5
|
+
* {@link EventPublicationRepository}. Reads and writes go through the
|
|
6
|
+
* {@link EntityManager} bound to the ambient transaction (via
|
|
7
|
+
* `@nestjs-transactional/core`'s `AsyncLocalStorage`), so publication
|
|
8
|
+
* rows commit atomically with the business data.
|
|
9
|
+
*
|
|
10
|
+
* `findReadyForProcessing` returns ready rows in publication-date
|
|
11
|
+
* order WITHOUT per-row locking. The earlier design used
|
|
12
|
+
* `SELECT ... FOR UPDATE SKIP LOCKED` to give concurrent workers
|
|
13
|
+
* disjoint row sets; that approach was dropped because pessimistic
|
|
14
|
+
* locks require an enclosing transaction whose lifetime did not fit
|
|
15
|
+
* the worker's `find → tryClaim → invoke → finalize` flow (a
|
|
16
|
+
* transaction wide enough to hold the lock would have to wrap the
|
|
17
|
+
* listener invocation, which is unsafe for long-running listeners).
|
|
18
|
+
* Concurrent workers may now SEE the same rows; correctness comes
|
|
19
|
+
* from {@link tryClaim}, whose conditional `UPDATE` transitions
|
|
20
|
+
* `PUBLISHED`/`RESUBMITTED` → `PROCESSING` atomically and returns
|
|
21
|
+
* whether the row was actually claimed. A losing worker simply moves
|
|
22
|
+
* on. With small worker counts (typical: 1–3) the duplicate-fetch
|
|
23
|
+
* cost is negligible.
|
|
24
|
+
*
|
|
25
|
+
* The repository is dataSource-aware: pass a non-default
|
|
26
|
+
* `dataSourceName` when the application uses multiple DataSources
|
|
27
|
+
* (`'billing'`, `'inventory'`, ...). The fallback {@link DataSource}
|
|
28
|
+
* passed to the constructor is used when `getCurrentEntityManager` is
|
|
29
|
+
* called outside any active transaction — typical for operator-facing
|
|
30
|
+
* read APIs.
|
|
31
|
+
*/
|
|
32
|
+
export declare class TypeOrmEventPublicationRepository implements EventPublicationRepository {
|
|
33
|
+
private readonly dataSource;
|
|
34
|
+
private readonly dataSourceName;
|
|
35
|
+
constructor(dataSource: DataSource, dataSourceName?: string);
|
|
36
|
+
private get em();
|
|
37
|
+
createAll(inputs: NewEventPublication[]): Promise<EventPublication[]>;
|
|
38
|
+
findById(id: string): Promise<EventPublication | null>;
|
|
39
|
+
updateStatus(id: string, status: PublicationStatus, options?: UpdateStatusOptions): Promise<void>;
|
|
40
|
+
tryClaim(id: string): Promise<boolean>;
|
|
41
|
+
findReadyForProcessing(limit: number): Promise<EventPublication[]>;
|
|
42
|
+
findStale(beforeDate: Date, statuses: PublicationStatus[]): Promise<EventPublication[]>;
|
|
43
|
+
findCompleted(options?: FindCompletedOptions): Promise<EventPublication[]>;
|
|
44
|
+
findIncomplete(): Promise<EventPublication[]>;
|
|
45
|
+
findFailed(options?: FindFailedOptions): Promise<EventPublication[]>;
|
|
46
|
+
deleteCompleted(olderThan?: Date): Promise<number>;
|
|
47
|
+
archiveCompleted(id: string): Promise<void>;
|
|
48
|
+
delete(id: string): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=typeorm-event-publication.repository.d.ts.map
|
|
@@ -0,0 +1,237 @@
|
|
|
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.TypeOrmEventPublicationRepository = void 0;
|
|
13
|
+
const node_crypto_1 = require("node:crypto");
|
|
14
|
+
const common_1 = require("@nestjs/common");
|
|
15
|
+
const outbox_1 = require("@nestjs-transactional/outbox");
|
|
16
|
+
const typeorm_1 = require("@nestjs-transactional/typeorm");
|
|
17
|
+
const typeorm_2 = require("typeorm");
|
|
18
|
+
const event_publication_archive_entity_1 = require("../entity/event-publication-archive.entity");
|
|
19
|
+
const event_publication_entity_1 = require("../entity/event-publication.entity");
|
|
20
|
+
/**
|
|
21
|
+
* TypeORM-backed implementation of
|
|
22
|
+
* {@link EventPublicationRepository}. Reads and writes go through the
|
|
23
|
+
* {@link EntityManager} bound to the ambient transaction (via
|
|
24
|
+
* `@nestjs-transactional/core`'s `AsyncLocalStorage`), so publication
|
|
25
|
+
* rows commit atomically with the business data.
|
|
26
|
+
*
|
|
27
|
+
* `findReadyForProcessing` returns ready rows in publication-date
|
|
28
|
+
* order WITHOUT per-row locking. The earlier design used
|
|
29
|
+
* `SELECT ... FOR UPDATE SKIP LOCKED` to give concurrent workers
|
|
30
|
+
* disjoint row sets; that approach was dropped because pessimistic
|
|
31
|
+
* locks require an enclosing transaction whose lifetime did not fit
|
|
32
|
+
* the worker's `find → tryClaim → invoke → finalize` flow (a
|
|
33
|
+
* transaction wide enough to hold the lock would have to wrap the
|
|
34
|
+
* listener invocation, which is unsafe for long-running listeners).
|
|
35
|
+
* Concurrent workers may now SEE the same rows; correctness comes
|
|
36
|
+
* from {@link tryClaim}, whose conditional `UPDATE` transitions
|
|
37
|
+
* `PUBLISHED`/`RESUBMITTED` → `PROCESSING` atomically and returns
|
|
38
|
+
* whether the row was actually claimed. A losing worker simply moves
|
|
39
|
+
* on. With small worker counts (typical: 1–3) the duplicate-fetch
|
|
40
|
+
* cost is negligible.
|
|
41
|
+
*
|
|
42
|
+
* The repository is dataSource-aware: pass a non-default
|
|
43
|
+
* `dataSourceName` when the application uses multiple DataSources
|
|
44
|
+
* (`'billing'`, `'inventory'`, ...). The fallback {@link DataSource}
|
|
45
|
+
* passed to the constructor is used when `getCurrentEntityManager` is
|
|
46
|
+
* called outside any active transaction — typical for operator-facing
|
|
47
|
+
* read APIs.
|
|
48
|
+
*/
|
|
49
|
+
let TypeOrmEventPublicationRepository = class TypeOrmEventPublicationRepository {
|
|
50
|
+
dataSource;
|
|
51
|
+
dataSourceName;
|
|
52
|
+
constructor(dataSource, dataSourceName = 'default') {
|
|
53
|
+
this.dataSource = dataSource;
|
|
54
|
+
this.dataSourceName = dataSourceName;
|
|
55
|
+
}
|
|
56
|
+
get em() {
|
|
57
|
+
return (0, typeorm_1.getCurrentEntityManager)(this.dataSourceName, this.dataSource);
|
|
58
|
+
}
|
|
59
|
+
async createAll(inputs) {
|
|
60
|
+
const entities = inputs.map((input) => {
|
|
61
|
+
const entity = new event_publication_entity_1.EventPublicationEntity();
|
|
62
|
+
entity.id = (0, node_crypto_1.randomUUID)();
|
|
63
|
+
entity.listenerId = input.listenerId;
|
|
64
|
+
entity.eventType = input.eventType;
|
|
65
|
+
entity.serializedEvent = input.serializedEvent;
|
|
66
|
+
entity.publicationDate = input.publicationDate ?? new Date();
|
|
67
|
+
entity.status = outbox_1.PublicationStatus.PUBLISHED;
|
|
68
|
+
entity.completionDate = null;
|
|
69
|
+
entity.lastResubmissionDate = null;
|
|
70
|
+
entity.completionAttempts = 0;
|
|
71
|
+
entity.failureReason = null;
|
|
72
|
+
return entity;
|
|
73
|
+
});
|
|
74
|
+
await this.em.save(event_publication_entity_1.EventPublicationEntity, entities);
|
|
75
|
+
return entities.map(toDomain);
|
|
76
|
+
}
|
|
77
|
+
async findById(id) {
|
|
78
|
+
const entity = await this.em.findOne(event_publication_entity_1.EventPublicationEntity, { where: { id } });
|
|
79
|
+
return entity ? toDomain(entity) : null;
|
|
80
|
+
}
|
|
81
|
+
async updateStatus(id, status, options = {}) {
|
|
82
|
+
// Build the SET clause inline: TypeORM's `.set()` accepts raw SQL
|
|
83
|
+
// fragments as `() => string`, so we can bump `completionAttempts`
|
|
84
|
+
// atomically without a separate `em.increment()` call.
|
|
85
|
+
await this.em
|
|
86
|
+
.createQueryBuilder()
|
|
87
|
+
.update(event_publication_entity_1.EventPublicationEntity)
|
|
88
|
+
.set({
|
|
89
|
+
status,
|
|
90
|
+
...(options.completionDate !== undefined
|
|
91
|
+
? { completionDate: options.completionDate }
|
|
92
|
+
: {}),
|
|
93
|
+
...(options.failureReason !== undefined ? { failureReason: options.failureReason } : {}),
|
|
94
|
+
...(options.lastResubmissionDate !== undefined
|
|
95
|
+
? { lastResubmissionDate: options.lastResubmissionDate }
|
|
96
|
+
: {}),
|
|
97
|
+
...(options.incrementAttempts
|
|
98
|
+
? { completionAttempts: () => 'completion_attempts + 1' }
|
|
99
|
+
: {}),
|
|
100
|
+
})
|
|
101
|
+
.where('id = :id', { id })
|
|
102
|
+
.execute();
|
|
103
|
+
}
|
|
104
|
+
async tryClaim(id) {
|
|
105
|
+
// Atomic conditional update: only transitions the row when the
|
|
106
|
+
// current status is one the worker is allowed to claim. `affected`
|
|
107
|
+
// tells us whether we won the race — losers get 0 and back off.
|
|
108
|
+
const result = await this.em
|
|
109
|
+
.createQueryBuilder()
|
|
110
|
+
.update(event_publication_entity_1.EventPublicationEntity)
|
|
111
|
+
.set({
|
|
112
|
+
status: outbox_1.PublicationStatus.PROCESSING,
|
|
113
|
+
completionAttempts: () => 'completion_attempts + 1',
|
|
114
|
+
})
|
|
115
|
+
.where('id = :id AND status IN (:...statuses)', {
|
|
116
|
+
id,
|
|
117
|
+
statuses: [outbox_1.PublicationStatus.PUBLISHED, outbox_1.PublicationStatus.RESUBMITTED],
|
|
118
|
+
})
|
|
119
|
+
.execute();
|
|
120
|
+
return (result.affected ?? 0) > 0;
|
|
121
|
+
}
|
|
122
|
+
async findReadyForProcessing(limit) {
|
|
123
|
+
const entities = await this.em
|
|
124
|
+
.createQueryBuilder(event_publication_entity_1.EventPublicationEntity, 'p')
|
|
125
|
+
.where('p.status IN (:...statuses)', {
|
|
126
|
+
statuses: [outbox_1.PublicationStatus.PUBLISHED, outbox_1.PublicationStatus.RESUBMITTED],
|
|
127
|
+
})
|
|
128
|
+
.orderBy('p.publication_date', 'ASC')
|
|
129
|
+
.limit(limit)
|
|
130
|
+
.getMany();
|
|
131
|
+
return entities.map(toDomain);
|
|
132
|
+
}
|
|
133
|
+
async findStale(beforeDate, statuses) {
|
|
134
|
+
if (statuses.length === 0) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
const entities = await this.em.find(event_publication_entity_1.EventPublicationEntity, {
|
|
138
|
+
where: {
|
|
139
|
+
status: (0, typeorm_2.In)(statuses),
|
|
140
|
+
publicationDate: (0, typeorm_2.LessThan)(beforeDate),
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
return entities.map(toDomain);
|
|
144
|
+
}
|
|
145
|
+
async findCompleted(options) {
|
|
146
|
+
const where = {
|
|
147
|
+
status: outbox_1.PublicationStatus.COMPLETED,
|
|
148
|
+
};
|
|
149
|
+
if (options?.olderThan !== undefined) {
|
|
150
|
+
where.completionDate = (0, typeorm_2.LessThan)(options.olderThan);
|
|
151
|
+
}
|
|
152
|
+
const entities = await this.em.find(event_publication_entity_1.EventPublicationEntity, {
|
|
153
|
+
where,
|
|
154
|
+
...(options?.limit !== undefined ? { take: options.limit } : {}),
|
|
155
|
+
order: { completionDate: 'DESC' },
|
|
156
|
+
});
|
|
157
|
+
return entities.map(toDomain);
|
|
158
|
+
}
|
|
159
|
+
async findIncomplete() {
|
|
160
|
+
const entities = await this.em.find(event_publication_entity_1.EventPublicationEntity, {
|
|
161
|
+
where: { status: (0, typeorm_2.Not)(outbox_1.PublicationStatus.COMPLETED) },
|
|
162
|
+
});
|
|
163
|
+
return entities.map(toDomain);
|
|
164
|
+
}
|
|
165
|
+
async findFailed(options) {
|
|
166
|
+
const where = {
|
|
167
|
+
status: outbox_1.PublicationStatus.FAILED,
|
|
168
|
+
};
|
|
169
|
+
if (options?.minAge !== undefined) {
|
|
170
|
+
where.publicationDate = (0, typeorm_2.LessThan)(new Date(Date.now() - options.minAge));
|
|
171
|
+
}
|
|
172
|
+
if (options?.maxAttempts !== undefined) {
|
|
173
|
+
where.completionAttempts = (0, typeorm_2.LessThanOrEqual)(options.maxAttempts);
|
|
174
|
+
}
|
|
175
|
+
const entities = await this.em.find(event_publication_entity_1.EventPublicationEntity, { where });
|
|
176
|
+
return entities.map(toDomain);
|
|
177
|
+
}
|
|
178
|
+
async deleteCompleted(olderThan) {
|
|
179
|
+
const where = {
|
|
180
|
+
status: outbox_1.PublicationStatus.COMPLETED,
|
|
181
|
+
};
|
|
182
|
+
if (olderThan !== undefined) {
|
|
183
|
+
where.completionDate = (0, typeorm_2.LessThan)(olderThan);
|
|
184
|
+
}
|
|
185
|
+
const result = await this.em.delete(event_publication_entity_1.EventPublicationEntity, where);
|
|
186
|
+
return result.affected ?? 0;
|
|
187
|
+
}
|
|
188
|
+
async archiveCompleted(id) {
|
|
189
|
+
// Deliberately does not open a nested TypeORM transaction — the
|
|
190
|
+
// ambient `@Transactional` scope (the processor always wraps the
|
|
191
|
+
// listener invocation in one) gives us atomicity between the
|
|
192
|
+
// archive insert and the hot-queue delete. If called outside a
|
|
193
|
+
// transaction, TypeORM executes both statements autocommit; the
|
|
194
|
+
// window of inconsistency is the round-trip between the two
|
|
195
|
+
// statements, which is acceptable for an archive operation.
|
|
196
|
+
const entity = await this.em.findOne(event_publication_entity_1.EventPublicationEntity, { where: { id } });
|
|
197
|
+
if (entity === null) {
|
|
198
|
+
throw new outbox_1.PublicationNotFoundError(id);
|
|
199
|
+
}
|
|
200
|
+
const archive = new event_publication_archive_entity_1.EventPublicationArchiveEntity();
|
|
201
|
+
archive.id = entity.id;
|
|
202
|
+
archive.listenerId = entity.listenerId;
|
|
203
|
+
archive.eventType = entity.eventType;
|
|
204
|
+
archive.serializedEvent = entity.serializedEvent;
|
|
205
|
+
archive.publicationDate = entity.publicationDate;
|
|
206
|
+
archive.status = entity.status;
|
|
207
|
+
archive.completionDate = entity.completionDate ?? new Date();
|
|
208
|
+
archive.lastResubmissionDate = entity.lastResubmissionDate;
|
|
209
|
+
archive.completionAttempts = entity.completionAttempts;
|
|
210
|
+
archive.failureReason = entity.failureReason;
|
|
211
|
+
await this.em.save(event_publication_archive_entity_1.EventPublicationArchiveEntity, archive);
|
|
212
|
+
await this.em.delete(event_publication_entity_1.EventPublicationEntity, { id });
|
|
213
|
+
}
|
|
214
|
+
async delete(id) {
|
|
215
|
+
await this.em.delete(event_publication_entity_1.EventPublicationEntity, { id });
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
exports.TypeOrmEventPublicationRepository = TypeOrmEventPublicationRepository;
|
|
219
|
+
exports.TypeOrmEventPublicationRepository = TypeOrmEventPublicationRepository = __decorate([
|
|
220
|
+
(0, common_1.Injectable)(),
|
|
221
|
+
__metadata("design:paramtypes", [Function, Object])
|
|
222
|
+
], TypeOrmEventPublicationRepository);
|
|
223
|
+
function toDomain(entity) {
|
|
224
|
+
return {
|
|
225
|
+
id: entity.id,
|
|
226
|
+
listenerId: entity.listenerId,
|
|
227
|
+
eventType: entity.eventType,
|
|
228
|
+
serializedEvent: entity.serializedEvent,
|
|
229
|
+
publicationDate: entity.publicationDate,
|
|
230
|
+
status: entity.status,
|
|
231
|
+
completionDate: entity.completionDate,
|
|
232
|
+
lastResubmissionDate: entity.lastResubmissionDate,
|
|
233
|
+
completionAttempts: entity.completionAttempts,
|
|
234
|
+
failureReason: entity.failureReason,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=typeorm-event-publication.repository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typeorm-event-publication.repository.js","sourceRoot":"","sources":["../../src/repository/typeorm-event-publication.repository.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type QueryRunner } from 'typeorm';
|
|
2
|
+
/**
|
|
3
|
+
* Canonical table name for the hot queue. Exposed so downstream tools
|
|
4
|
+
* (e.g. custom queries, observability) can avoid hardcoding the string.
|
|
5
|
+
*/
|
|
6
|
+
export declare const EVENT_PUBLICATION_TABLE = "event_publication";
|
|
7
|
+
/**
|
|
8
|
+
* Canonical table name for the archive trail. Used only by the
|
|
9
|
+
* `ARCHIVE` completion mode.
|
|
10
|
+
*/
|
|
11
|
+
export declare const EVENT_PUBLICATION_ARCHIVE_TABLE = "event_publication_archive";
|
|
12
|
+
/**
|
|
13
|
+
* Create the hot and archive tables plus every index. Shared by
|
|
14
|
+
* {@link CreateEventPublication1700000000000} (the TypeORM migration)
|
|
15
|
+
* and `SchemaInitializer` (development-only auto-init) so the two paths
|
|
16
|
+
* cannot drift.
|
|
17
|
+
*
|
|
18
|
+
* Strict mode: fails if the tables already exist — callers are
|
|
19
|
+
* responsible for checking existence and skipping this call when
|
|
20
|
+
* needed.
|
|
21
|
+
*/
|
|
22
|
+
export declare function applyEventPublicationSchema(qr: QueryRunner): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Drop the archive first (to avoid referential surprises if a future
|
|
25
|
+
* revision adds a FK between the two) and then the hot table. Both
|
|
26
|
+
* drops are tolerant — `dropTable(..., true)` adds `IF EXISTS`, so
|
|
27
|
+
* running `down()` on a partially-applied schema still succeeds.
|
|
28
|
+
*/
|
|
29
|
+
export declare function revertEventPublicationSchema(qr: QueryRunner): Promise<void>;
|
|
30
|
+
//# sourceMappingURL=event-publication-schema.d.ts.map
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EVENT_PUBLICATION_ARCHIVE_TABLE = exports.EVENT_PUBLICATION_TABLE = void 0;
|
|
4
|
+
exports.applyEventPublicationSchema = applyEventPublicationSchema;
|
|
5
|
+
exports.revertEventPublicationSchema = revertEventPublicationSchema;
|
|
6
|
+
const outbox_1 = require("@nestjs-transactional/outbox");
|
|
7
|
+
const typeorm_1 = require("typeorm");
|
|
8
|
+
/**
|
|
9
|
+
* Canonical table name for the hot queue. Exposed so downstream tools
|
|
10
|
+
* (e.g. custom queries, observability) can avoid hardcoding the string.
|
|
11
|
+
*/
|
|
12
|
+
exports.EVENT_PUBLICATION_TABLE = 'event_publication';
|
|
13
|
+
/**
|
|
14
|
+
* Canonical table name for the archive trail. Used only by the
|
|
15
|
+
* `ARCHIVE` completion mode.
|
|
16
|
+
*/
|
|
17
|
+
exports.EVENT_PUBLICATION_ARCHIVE_TABLE = 'event_publication_archive';
|
|
18
|
+
function buildHotTable() {
|
|
19
|
+
return new typeorm_1.Table({
|
|
20
|
+
name: exports.EVENT_PUBLICATION_TABLE,
|
|
21
|
+
columns: [
|
|
22
|
+
{ name: 'id', type: 'uuid', isPrimary: true },
|
|
23
|
+
{ name: 'listener_id', type: 'varchar', length: '512', isNullable: false },
|
|
24
|
+
{ name: 'event_type', type: 'varchar', length: '256', isNullable: false },
|
|
25
|
+
{ name: 'serialized_event', type: 'text', isNullable: false },
|
|
26
|
+
{ name: 'publication_date', type: 'timestamptz', isNullable: false },
|
|
27
|
+
{
|
|
28
|
+
name: 'status',
|
|
29
|
+
type: 'varchar',
|
|
30
|
+
length: '32',
|
|
31
|
+
default: `'${outbox_1.PublicationStatus.PUBLISHED}'`,
|
|
32
|
+
isNullable: false,
|
|
33
|
+
},
|
|
34
|
+
{ name: 'completion_date', type: 'timestamptz', isNullable: true },
|
|
35
|
+
{ name: 'last_resubmission_date', type: 'timestamptz', isNullable: true },
|
|
36
|
+
{ name: 'completion_attempts', type: 'int', default: 0, isNullable: false },
|
|
37
|
+
{ name: 'failure_reason', type: 'text', isNullable: true },
|
|
38
|
+
],
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function buildArchiveTable() {
|
|
42
|
+
return new typeorm_1.Table({
|
|
43
|
+
name: exports.EVENT_PUBLICATION_ARCHIVE_TABLE,
|
|
44
|
+
columns: [
|
|
45
|
+
{ name: 'id', type: 'uuid', isPrimary: true },
|
|
46
|
+
{ name: 'listener_id', type: 'varchar', length: '512', isNullable: false },
|
|
47
|
+
{ name: 'event_type', type: 'varchar', length: '256', isNullable: false },
|
|
48
|
+
{ name: 'serialized_event', type: 'text', isNullable: false },
|
|
49
|
+
{ name: 'publication_date', type: 'timestamptz', isNullable: false },
|
|
50
|
+
{ name: 'status', type: 'varchar', length: '32', isNullable: false },
|
|
51
|
+
{ name: 'completion_date', type: 'timestamptz', isNullable: false },
|
|
52
|
+
{ name: 'last_resubmission_date', type: 'timestamptz', isNullable: true },
|
|
53
|
+
{ name: 'completion_attempts', type: 'int', isNullable: false },
|
|
54
|
+
{ name: 'failure_reason', type: 'text', isNullable: true },
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function buildHotIndexes() {
|
|
59
|
+
return [
|
|
60
|
+
new typeorm_1.TableIndex({
|
|
61
|
+
name: 'idx_event_publication_status_date',
|
|
62
|
+
columnNames: ['status', 'publication_date'],
|
|
63
|
+
}),
|
|
64
|
+
new typeorm_1.TableIndex({
|
|
65
|
+
name: 'idx_event_publication_status_listener',
|
|
66
|
+
columnNames: ['status', 'listener_id'],
|
|
67
|
+
}),
|
|
68
|
+
new typeorm_1.TableIndex({
|
|
69
|
+
name: 'idx_event_publication_event_type',
|
|
70
|
+
columnNames: ['event_type'],
|
|
71
|
+
}),
|
|
72
|
+
new typeorm_1.TableIndex({
|
|
73
|
+
name: 'idx_event_publication_completion_date',
|
|
74
|
+
columnNames: ['completion_date'],
|
|
75
|
+
}),
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
function buildArchiveIndexes() {
|
|
79
|
+
return [
|
|
80
|
+
new typeorm_1.TableIndex({
|
|
81
|
+
name: 'idx_event_publication_archive_completion_date',
|
|
82
|
+
columnNames: ['completion_date'],
|
|
83
|
+
}),
|
|
84
|
+
new typeorm_1.TableIndex({
|
|
85
|
+
name: 'idx_event_publication_archive_listener',
|
|
86
|
+
columnNames: ['listener_id'],
|
|
87
|
+
}),
|
|
88
|
+
new typeorm_1.TableIndex({
|
|
89
|
+
name: 'idx_event_publication_archive_event_type',
|
|
90
|
+
columnNames: ['event_type'],
|
|
91
|
+
}),
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Create the hot and archive tables plus every index. Shared by
|
|
96
|
+
* {@link CreateEventPublication1700000000000} (the TypeORM migration)
|
|
97
|
+
* and `SchemaInitializer` (development-only auto-init) so the two paths
|
|
98
|
+
* cannot drift.
|
|
99
|
+
*
|
|
100
|
+
* Strict mode: fails if the tables already exist — callers are
|
|
101
|
+
* responsible for checking existence and skipping this call when
|
|
102
|
+
* needed.
|
|
103
|
+
*/
|
|
104
|
+
async function applyEventPublicationSchema(qr) {
|
|
105
|
+
await qr.createTable(buildHotTable());
|
|
106
|
+
for (const index of buildHotIndexes()) {
|
|
107
|
+
await qr.createIndex(exports.EVENT_PUBLICATION_TABLE, index);
|
|
108
|
+
}
|
|
109
|
+
await qr.createTable(buildArchiveTable());
|
|
110
|
+
for (const index of buildArchiveIndexes()) {
|
|
111
|
+
await qr.createIndex(exports.EVENT_PUBLICATION_ARCHIVE_TABLE, index);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Drop the archive first (to avoid referential surprises if a future
|
|
116
|
+
* revision adds a FK between the two) and then the hot table. Both
|
|
117
|
+
* drops are tolerant — `dropTable(..., true)` adds `IF EXISTS`, so
|
|
118
|
+
* running `down()` on a partially-applied schema still succeeds.
|
|
119
|
+
*/
|
|
120
|
+
async function revertEventPublicationSchema(qr) {
|
|
121
|
+
await qr.dropTable(exports.EVENT_PUBLICATION_ARCHIVE_TABLE, true);
|
|
122
|
+
await qr.dropTable(exports.EVENT_PUBLICATION_TABLE, true);
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=event-publication-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-publication-schema.js","sourceRoot":"","sources":["../../src/schema/event-publication-schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for {@link SchemaInitializer}. Deliberately minimal —
|
|
3
|
+
* the initializer's sole job is to decide whether to create the
|
|
4
|
+
* `event_publication` schema at bootstrap time.
|
|
5
|
+
*/
|
|
6
|
+
export interface SchemaInitializationOptions {
|
|
7
|
+
/**
|
|
8
|
+
* When `true`, the initializer creates the `event_publication` and
|
|
9
|
+
* `event_publication_archive` tables on application bootstrap if
|
|
10
|
+
* they are missing. When `false` (the default), bootstrap is a
|
|
11
|
+
* no-op and the user is expected to have applied the TypeORM
|
|
12
|
+
* migration shipped by this package (or their own equivalent).
|
|
13
|
+
*
|
|
14
|
+
* **Development only.** Production systems should apply schema
|
|
15
|
+
* changes through a reviewed migration step, never at process
|
|
16
|
+
* startup.
|
|
17
|
+
*/
|
|
18
|
+
readonly enabled: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** DI token for {@link SchemaInitializationOptions}. */
|
|
21
|
+
export declare const SCHEMA_INITIALIZATION_OPTIONS: unique symbol;
|
|
22
|
+
/** Safe defaults — auto-init off, migrations preferred. */
|
|
23
|
+
export declare const DEFAULT_SCHEMA_INITIALIZATION_OPTIONS: SchemaInitializationOptions;
|
|
24
|
+
//# sourceMappingURL=schema-initialization-options.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_SCHEMA_INITIALIZATION_OPTIONS = exports.SCHEMA_INITIALIZATION_OPTIONS = void 0;
|
|
4
|
+
/** DI token for {@link SchemaInitializationOptions}. */
|
|
5
|
+
exports.SCHEMA_INITIALIZATION_OPTIONS = Symbol('SCHEMA_INITIALIZATION_OPTIONS');
|
|
6
|
+
/** Safe defaults — auto-init off, migrations preferred. */
|
|
7
|
+
exports.DEFAULT_SCHEMA_INITIALIZATION_OPTIONS = {
|
|
8
|
+
enabled: false,
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=schema-initialization-options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-initialization-options.js","sourceRoot":"","sources":["../../src/schema/schema-initialization-options.ts"],"names":[],"mappings":";;;AAoBA,wDAAwD;AAC3C,QAAA,6BAA6B,GAAG,MAAM,CAAC,+BAA+B,CAAC,CAAC;AAErF,2DAA2D;AAC9C,QAAA,qCAAqC,GAAgC;IAChF,OAAO,EAAE,KAAK;CACf,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type OnApplicationBootstrap } from '@nestjs/common';
|
|
2
|
+
import type { DataSource } from 'typeorm';
|
|
3
|
+
import type { SchemaInitializationOptions } from './schema-initialization-options';
|
|
4
|
+
/**
|
|
5
|
+
* Development-time helper that creates the `event_publication` schema
|
|
6
|
+
* at application bootstrap, so developers can spin up a fresh app
|
|
7
|
+
* against an empty database without a separate migration step.
|
|
8
|
+
*
|
|
9
|
+
* **Not intended for production.** Production deployments should apply
|
|
10
|
+
* the schema via the TypeORM migration shipped by this package
|
|
11
|
+
* (`CreateEventPublication1700000000000`) or their own equivalent,
|
|
12
|
+
* and leave `enabled: false`.
|
|
13
|
+
*
|
|
14
|
+
* The equivalent Spring Modulith switch is
|
|
15
|
+
* `spring.modulith.events.jdbc.schema-initialization.enabled`.
|
|
16
|
+
*/
|
|
17
|
+
export declare class SchemaInitializer implements OnApplicationBootstrap {
|
|
18
|
+
private readonly dataSource;
|
|
19
|
+
private readonly options;
|
|
20
|
+
private readonly logger;
|
|
21
|
+
constructor(dataSource: DataSource, options: SchemaInitializationOptions);
|
|
22
|
+
onApplicationBootstrap(): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Check for the hot table via `to_regclass` — Postgres-specific and
|
|
25
|
+
* respects the current `search_path`. Returns `null` when the table
|
|
26
|
+
* is missing, a regclass name (as text after the `::text` cast) when
|
|
27
|
+
* it exists.
|
|
28
|
+
*/
|
|
29
|
+
private hotTableExists;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=schema-initializer.d.ts.map
|
|
@@ -0,0 +1,72 @@
|
|
|
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 SchemaInitializer_1;
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.SchemaInitializer = void 0;
|
|
14
|
+
const common_1 = require("@nestjs/common");
|
|
15
|
+
const event_publication_schema_1 = require("./event-publication-schema");
|
|
16
|
+
/**
|
|
17
|
+
* Development-time helper that creates the `event_publication` schema
|
|
18
|
+
* at application bootstrap, so developers can spin up a fresh app
|
|
19
|
+
* against an empty database without a separate migration step.
|
|
20
|
+
*
|
|
21
|
+
* **Not intended for production.** Production deployments should apply
|
|
22
|
+
* the schema via the TypeORM migration shipped by this package
|
|
23
|
+
* (`CreateEventPublication1700000000000`) or their own equivalent,
|
|
24
|
+
* and leave `enabled: false`.
|
|
25
|
+
*
|
|
26
|
+
* The equivalent Spring Modulith switch is
|
|
27
|
+
* `spring.modulith.events.jdbc.schema-initialization.enabled`.
|
|
28
|
+
*/
|
|
29
|
+
let SchemaInitializer = SchemaInitializer_1 = class SchemaInitializer {
|
|
30
|
+
dataSource;
|
|
31
|
+
options;
|
|
32
|
+
logger = new common_1.Logger(SchemaInitializer_1.name);
|
|
33
|
+
constructor(dataSource, options) {
|
|
34
|
+
this.dataSource = dataSource;
|
|
35
|
+
this.options = options;
|
|
36
|
+
}
|
|
37
|
+
async onApplicationBootstrap() {
|
|
38
|
+
if (!this.options.enabled) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (await this.hotTableExists()) {
|
|
42
|
+
this.logger.debug(`Table '${event_publication_schema_1.EVENT_PUBLICATION_TABLE}' already exists — skipping auto schema init`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this.logger.log(`Initialising '${event_publication_schema_1.EVENT_PUBLICATION_TABLE}' schema (development auto-init)`);
|
|
46
|
+
const queryRunner = this.dataSource.createQueryRunner();
|
|
47
|
+
try {
|
|
48
|
+
await queryRunner.connect();
|
|
49
|
+
await (0, event_publication_schema_1.applyEventPublicationSchema)(queryRunner);
|
|
50
|
+
this.logger.log(`'${event_publication_schema_1.EVENT_PUBLICATION_TABLE}' schema initialised`);
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
await queryRunner.release();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check for the hot table via `to_regclass` — Postgres-specific and
|
|
58
|
+
* respects the current `search_path`. Returns `null` when the table
|
|
59
|
+
* is missing, a regclass name (as text after the `::text` cast) when
|
|
60
|
+
* it exists.
|
|
61
|
+
*/
|
|
62
|
+
async hotTableExists() {
|
|
63
|
+
const rows = await this.dataSource.query(`SELECT to_regclass($1)::text AS exists`, [event_publication_schema_1.EVENT_PUBLICATION_TABLE]);
|
|
64
|
+
return rows[0]?.exists != null;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
exports.SchemaInitializer = SchemaInitializer;
|
|
68
|
+
exports.SchemaInitializer = SchemaInitializer = SchemaInitializer_1 = __decorate([
|
|
69
|
+
(0, common_1.Injectable)(),
|
|
70
|
+
__metadata("design:paramtypes", [Function, Object])
|
|
71
|
+
], SchemaInitializer);
|
|
72
|
+
//# sourceMappingURL=schema-initializer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-initializer.js","sourceRoot":"","sources":["../../src/schema/schema-initializer.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAAiF;AAGjF,yEAGoC;AAOpC;;;;;;;;;;;;GAYG;AAEI,IAAM,iBAAiB,yBAAvB,MAAM,iBAAiB;IAIT;IACA;IAJF,MAAM,GAAG,IAAI,eAAM,CAAC,mBAAiB,CAAC,IAAI,CAAC,CAAC;IAE7D,YACmB,UAAsB,EACtB,OAAoC;QADpC,eAAU,GAAV,UAAU,CAAY;QACtB,YAAO,GAAP,OAAO,CAA6B;IACpD,CAAC;IAEJ,KAAK,CAAC,sBAAsB;QAC1B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YAC1B,OAAO;QACT,CAAC;QAED,IAAI,MAAM,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;YAChC,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,UAAU,kDAAuB,8CAA8C,CAChF,CAAC;YACF,OAAO;QACT,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,GAAG,CACb,iBAAiB,kDAAuB,kCAAkC,CAC3E,CAAC;QAEF,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC;QACxD,IAAI,CAAC;YACH,MAAM,WAAW,CAAC,OAAO,EAAE,CAAC;YAC5B,MAAM,IAAA,sDAA2B,EAAC,WAAW,CAAC,CAAC;YAC/C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,kDAAuB,sBAAsB,CAAC,CAAC;QACrE,CAAC;gBAAS,CAAC;YACT,MAAM,WAAW,CAAC,OAAO,EAAE,CAAC;QAC9B,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACK,KAAK,CAAC,cAAc;QAC1B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CACtC,wCAAwC,EACxC,CAAC,kDAAuB,CAAC,CAC1B,CAAC;QACF,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,IAAI,CAAC;IACjC,CAAC;CACF,CAAA;AA/CY,8CAAiB;4BAAjB,iBAAiB;IAD7B,IAAA,mBAAU,GAAE;;GACA,iBAAiB,CA+C7B"}
|