@nestjs-transactional/outbox 1.0.0-alpha.5 → 1.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.
- package/README.md +174 -335
- package/dist/api/failed-event-publications.js.map +1 -1
- package/dist/api/incomplete-event-publications.js +1 -2
- package/dist/api/incomplete-event-publications.js.map +1 -1
- package/dist/decorators/outbox-events-handler.decorator.d.ts +1 -1
- package/dist/decorators/outbox-events-handler.decorator.js.map +1 -1
- package/dist/dispatcher/data-source-outbox-publisher.d.ts +2 -3
- package/dist/dispatcher/data-source-outbox-publisher.js +2 -3
- package/dist/dispatcher/data-source-outbox-publisher.js.map +1 -1
- package/dist/dispatcher/event-publication-processor.d.ts +19 -2
- package/dist/dispatcher/event-publication-processor.js +40 -3
- package/dist/dispatcher/event-publication-processor.js.map +1 -1
- package/dist/dispatcher/outbox-event-publisher.d.ts +1 -1
- package/dist/dispatcher/outbox-event-publisher.js +2 -2
- package/dist/dispatcher/outbox-event-publisher.js.map +1 -1
- package/dist/dispatcher/processor-options.d.ts +13 -1
- package/dist/dispatcher/processor-options.js +4 -1
- package/dist/dispatcher/processor-options.js.map +1 -1
- package/dist/externalization/describe-thrown.d.ts +20 -0
- package/dist/externalization/describe-thrown.js +49 -0
- package/dist/externalization/describe-thrown.js.map +1 -0
- package/dist/externalization/event-externalizer.d.ts +2 -2
- package/dist/externalization/externalization-registry.js.map +1 -1
- package/dist/externalization/externalized.decorator.d.ts +3 -4
- package/dist/externalization/externalized.decorator.js +2 -3
- package/dist/externalization/externalized.decorator.js.map +1 -1
- package/dist/externalization/types.d.ts +2 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/module/outbox-processing.module.d.ts +18 -2
- package/dist/module/outbox-processing.module.js +33 -7
- package/dist/module/outbox-processing.module.js.map +1 -1
- package/dist/module/outbox.module.d.ts +15 -0
- package/dist/module/outbox.module.js +99 -8
- package/dist/module/outbox.module.js.map +1 -1
- package/dist/recovery/outbox-cleanup-scheduler.d.ts +56 -0
- package/dist/recovery/outbox-cleanup-scheduler.js +139 -0
- package/dist/recovery/outbox-cleanup-scheduler.js.map +1 -0
- package/dist/recovery/outbox-retry-scheduler.d.ts +68 -0
- package/dist/recovery/outbox-retry-scheduler.js +163 -0
- package/dist/recovery/outbox-retry-scheduler.js.map +1 -0
- package/dist/recovery/staleness-monitor.d.ts +10 -2
- package/dist/recovery/staleness-monitor.js +28 -3
- package/dist/recovery/staleness-monitor.js.map +1 -1
- package/dist/registry/event-publication-registry.d.ts +1 -1
- package/dist/registry/event-publication-registry.js +1 -1
- package/dist/registry/event-publication-registry.js.map +1 -1
- package/dist/registry/listener-registry.d.ts +2 -2
- package/dist/registry/listener-registry.js +2 -2
- package/dist/registry/multi-ds-listener-registrar.d.ts +10 -4
- package/dist/registry/multi-ds-listener-registrar.js +1 -1
- package/dist/registry/multi-ds-listener-registrar.js.map +1 -1
- package/dist/registry/outbox-listener-scanner.d.ts +1 -1
- package/dist/registry/outbox-listener-scanner.js +1 -1
- package/dist/registry/outbox-listener-scanner.js.map +1 -1
- package/dist/repository/event-publication-repository.d.ts +37 -6
- package/dist/repository/event-publication-repository.js.map +1 -1
- package/dist/serialization/event-type-resolver.d.ts +1 -1
- package/dist/serialization/event-type-resolver.js +1 -1
- package/dist/serialization/json-event-serializer.js.map +1 -1
- package/dist/shutdown/drain.d.ts +32 -0
- package/dist/shutdown/drain.js +56 -0
- package/dist/shutdown/drain.js.map +1 -0
- package/dist/testing/in-memory-repository.js +11 -2
- package/dist/testing/in-memory-repository.js.map +1 -1
- package/dist/testing/published-events.js +6 -2
- package/dist/testing/published-events.js.map +1 -1
- package/dist/tokens/token-utils.js.map +1 -1
- package/dist/types/cleanup-config.d.ts +59 -0
- package/dist/types/cleanup-config.js +17 -0
- package/dist/types/cleanup-config.js.map +1 -0
- package/dist/types/resubmission-options.d.ts +0 -4
- package/dist/types/resubmission-options.js +0 -9
- package/dist/types/resubmission-options.js.map +1 -1
- package/dist/types/retry-config.d.ts +51 -0
- package/dist/types/retry-config.js +16 -0
- package/dist/types/retry-config.js.map +1 -0
- package/dist/types/staleness-config.d.ts +8 -0
- package/dist/types/staleness-config.js +2 -0
- package/dist/types/staleness-config.js.map +1 -1
- package/package.json +25 -13
|
@@ -0,0 +1,163 @@
|
|
|
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 OutboxRetryScheduler_1;
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.OutboxRetryScheduler = void 0;
|
|
14
|
+
const common_1 = require("@nestjs/common");
|
|
15
|
+
const failed_event_publications_1 = require("../api/failed-event-publications");
|
|
16
|
+
const drain_1 = require("../shutdown/drain");
|
|
17
|
+
const resubmission_options_1 = require("../types/resubmission-options");
|
|
18
|
+
/**
|
|
19
|
+
* Periodically resubmits `FAILED` publications whose backoff window has
|
|
20
|
+
* elapsed, up to a per-publication attempt cap (DD-026).
|
|
21
|
+
*
|
|
22
|
+
* Deliberately a thin scheduler over
|
|
23
|
+
* {@link FailedEventPublications.resubmit} rather than a second
|
|
24
|
+
* resubmission path: automatic retry is the same operation an operator
|
|
25
|
+
* performs by hand, on a timer, with a backoff predicate as the filter.
|
|
26
|
+
* Spring Modulith stops at the manual API — this is where we go one step
|
|
27
|
+
* past parity, and keeping it layered is what makes that step cheap to
|
|
28
|
+
* reason about.
|
|
29
|
+
*
|
|
30
|
+
* Disabled unless `config.maxAttempts > 0`, in which case {@link start}
|
|
31
|
+
* never schedules the loop. A publication that exhausts its attempts is
|
|
32
|
+
* simply no longer selected; it stays `FAILED` and remains available to
|
|
33
|
+
* the operator APIs.
|
|
34
|
+
*/
|
|
35
|
+
let OutboxRetryScheduler = OutboxRetryScheduler_1 = class OutboxRetryScheduler {
|
|
36
|
+
failed;
|
|
37
|
+
config;
|
|
38
|
+
logger = new common_1.Logger(OutboxRetryScheduler_1.name);
|
|
39
|
+
running = false;
|
|
40
|
+
retryLoop = null;
|
|
41
|
+
/** The pass currently running, or `null` when idle. See {@link stop}. */
|
|
42
|
+
inFlightPass = null;
|
|
43
|
+
constructor(failed, config) {
|
|
44
|
+
this.failed = failed;
|
|
45
|
+
this.config = config;
|
|
46
|
+
}
|
|
47
|
+
/** Start the retry loop. Idempotent; a no-op when retry is disabled. */
|
|
48
|
+
start() {
|
|
49
|
+
if (this.running) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (this.config.maxAttempts <= 0) {
|
|
53
|
+
this.logger.log('OutboxRetryScheduler disabled (maxAttempts = 0)');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
this.running = true;
|
|
57
|
+
this.scheduleNext();
|
|
58
|
+
this.logger.log(`OutboxRetryScheduler started (interval: ${this.config.interval}ms, ` +
|
|
59
|
+
`maxAttempts: ${this.config.maxAttempts})`);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Stop the loop and drain the pass already in flight, bounded by
|
|
63
|
+
* `config.shutdownTimeout`. Idempotent; never rejects.
|
|
64
|
+
*/
|
|
65
|
+
async stop() {
|
|
66
|
+
this.running = false;
|
|
67
|
+
if (this.retryLoop !== null) {
|
|
68
|
+
clearTimeout(this.retryLoop);
|
|
69
|
+
this.retryLoop = null;
|
|
70
|
+
}
|
|
71
|
+
const inFlight = this.inFlightPass;
|
|
72
|
+
if (inFlight !== null) {
|
|
73
|
+
const drained = await (0, drain_1.drainWithTimeout)(inFlight, this.config.shutdownTimeout);
|
|
74
|
+
if (!drained) {
|
|
75
|
+
this.logger.warn(`OutboxRetryScheduler drain timed out after ${this.config.shutdownTimeout}ms — ` +
|
|
76
|
+
'the in-flight pass was abandoned.');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Run a single retry pass and return how many publications were
|
|
82
|
+
* resubmitted. Invoked by the scheduled loop, and directly by tests
|
|
83
|
+
* and one-shot tooling.
|
|
84
|
+
*
|
|
85
|
+
* Never rejects: a failing pass is logged and reported as `0`, so a
|
|
86
|
+
* database blip cannot kill the loop.
|
|
87
|
+
*/
|
|
88
|
+
async runOnce() {
|
|
89
|
+
try {
|
|
90
|
+
const resubmitted = await this.failed.resubmit(resubmission_options_1.ResubmissionOptions.defaults()
|
|
91
|
+
.withBatchSize(this.config.batchSize)
|
|
92
|
+
// The repository narrows by attempt count; `maxAttempts` counts
|
|
93
|
+
// the first delivery, so a publication is eligible only while
|
|
94
|
+
// it has attempts left.
|
|
95
|
+
.withMaxAttempts(this.config.maxAttempts - 1)
|
|
96
|
+
.withFilter((publication) => this.isDue(publication)));
|
|
97
|
+
if (resubmitted > 0) {
|
|
98
|
+
this.logger.log(`Resubmitted ${resubmitted} failed publication(s) for retry`);
|
|
99
|
+
}
|
|
100
|
+
return resubmitted;
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
this.logger.error('Retry pass failed', err instanceof Error ? err.stack : String(err));
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Whether `publication` has waited out its backoff window.
|
|
109
|
+
*
|
|
110
|
+
* The window is measured from the last attempt we can see. There is no
|
|
111
|
+
* failure timestamp in the schema, so a publication that has never
|
|
112
|
+
* been resubmitted is measured from `publicationDate` — which makes
|
|
113
|
+
* its *first* retry eager if it lived a long time before failing.
|
|
114
|
+
* Every later window is exact, because resubmission stamps
|
|
115
|
+
* `lastResubmissionDate`. See DD-026 for why that beats a migration.
|
|
116
|
+
*/
|
|
117
|
+
isDue(publication) {
|
|
118
|
+
const lastAttemptAt = publication.lastResubmissionDate ?? publication.publicationDate;
|
|
119
|
+
const waited = Date.now() - lastAttemptAt.getTime();
|
|
120
|
+
return waited >= this.delayFor(publication.completionAttempts);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* `baseDelay * factor^(attempts - 1)`, clamped to `maxDelay` and then
|
|
124
|
+
* spread by `jitter`.
|
|
125
|
+
*
|
|
126
|
+
* Jitter scales the window by a factor in `[1 - jitter, 1 + jitter]`,
|
|
127
|
+
* so retries after a mass failure do not all become due at the same
|
|
128
|
+
* instant. It can shorten a window but never to zero, so a
|
|
129
|
+
* just-failed publication still waits.
|
|
130
|
+
*/
|
|
131
|
+
delayFor(attempts) {
|
|
132
|
+
const exponent = Math.max(0, attempts - 1);
|
|
133
|
+
const raw = this.config.baseDelay * Math.pow(this.config.factor, exponent);
|
|
134
|
+
const clamped = Math.min(raw, this.config.maxDelay);
|
|
135
|
+
if (this.config.jitter <= 0) {
|
|
136
|
+
return clamped;
|
|
137
|
+
}
|
|
138
|
+
const spread = clamped * this.config.jitter;
|
|
139
|
+
return clamped - spread + Math.random() * spread * 2;
|
|
140
|
+
}
|
|
141
|
+
scheduleNext() {
|
|
142
|
+
if (!this.running) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
this.retryLoop = setTimeout(() => {
|
|
146
|
+
this.retryLoop = null;
|
|
147
|
+
const pass = this.runOnce();
|
|
148
|
+
this.inFlightPass = pass;
|
|
149
|
+
void pass.finally(() => {
|
|
150
|
+
if (this.inFlightPass === pass) {
|
|
151
|
+
this.inFlightPass = null;
|
|
152
|
+
}
|
|
153
|
+
this.scheduleNext();
|
|
154
|
+
});
|
|
155
|
+
}, this.config.interval);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
exports.OutboxRetryScheduler = OutboxRetryScheduler;
|
|
159
|
+
exports.OutboxRetryScheduler = OutboxRetryScheduler = OutboxRetryScheduler_1 = __decorate([
|
|
160
|
+
(0, common_1.Injectable)(),
|
|
161
|
+
__metadata("design:paramtypes", [failed_event_publications_1.FailedEventPublications, Object])
|
|
162
|
+
], OutboxRetryScheduler);
|
|
163
|
+
//# sourceMappingURL=outbox-retry-scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbox-retry-scheduler.js","sourceRoot":"","sources":["../../src/recovery/outbox-retry-scheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,11 +22,19 @@ export declare class StalenessMonitor {
|
|
|
22
22
|
private readonly logger;
|
|
23
23
|
private running;
|
|
24
24
|
private monitorLoop;
|
|
25
|
+
/** The sweep currently running, or `null` when idle. See {@link stop}. */
|
|
26
|
+
private inFlightSweep;
|
|
25
27
|
constructor(repository: EventPublicationRepository, config: StalenessConfig);
|
|
26
28
|
/** Start the watchdog. Idempotent. */
|
|
27
29
|
start(): void;
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Stop the watchdog and drain the sweep already in flight.
|
|
32
|
+
*
|
|
33
|
+
* Resolves once no sweep is running, or once
|
|
34
|
+
* `config.shutdownTimeout` elapses — whichever comes first. Idempotent
|
|
35
|
+
* and never rejects.
|
|
36
|
+
*/
|
|
37
|
+
stop(): Promise<void>;
|
|
30
38
|
/**
|
|
31
39
|
* Run a single staleness sweep — invoked both by the scheduled loop
|
|
32
40
|
* and directly by tests / one-shot tooling. Never rejects; infra
|
|
@@ -16,6 +16,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
16
16
|
exports.StalenessMonitor = void 0;
|
|
17
17
|
const common_1 = require("@nestjs/common");
|
|
18
18
|
const event_publication_repository_1 = require("../repository/event-publication-repository");
|
|
19
|
+
const drain_1 = require("../shutdown/drain");
|
|
19
20
|
const publication_status_1 = require("../types/publication-status");
|
|
20
21
|
/**
|
|
21
22
|
* Periodic watchdog that flips publications stuck in non-terminal
|
|
@@ -39,6 +40,8 @@ let StalenessMonitor = StalenessMonitor_1 = class StalenessMonitor {
|
|
|
39
40
|
logger = new common_1.Logger(StalenessMonitor_1.name);
|
|
40
41
|
running = false;
|
|
41
42
|
monitorLoop = null;
|
|
43
|
+
/** The sweep currently running, or `null` when idle. See {@link stop}. */
|
|
44
|
+
inFlightSweep = null;
|
|
42
45
|
constructor(repository, config) {
|
|
43
46
|
this.repository = repository;
|
|
44
47
|
this.config = config;
|
|
@@ -58,13 +61,27 @@ let StalenessMonitor = StalenessMonitor_1 = class StalenessMonitor {
|
|
|
58
61
|
this.scheduleNext();
|
|
59
62
|
this.logger.log(`StalenessMonitor started (interval: ${this.config.monitorInterval}ms)`);
|
|
60
63
|
}
|
|
61
|
-
/**
|
|
62
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Stop the watchdog and drain the sweep already in flight.
|
|
66
|
+
*
|
|
67
|
+
* Resolves once no sweep is running, or once
|
|
68
|
+
* `config.shutdownTimeout` elapses — whichever comes first. Idempotent
|
|
69
|
+
* and never rejects.
|
|
70
|
+
*/
|
|
71
|
+
async stop() {
|
|
63
72
|
this.running = false;
|
|
64
73
|
if (this.monitorLoop !== null) {
|
|
65
74
|
clearTimeout(this.monitorLoop);
|
|
66
75
|
this.monitorLoop = null;
|
|
67
76
|
}
|
|
77
|
+
const inFlight = this.inFlightSweep;
|
|
78
|
+
if (inFlight !== null) {
|
|
79
|
+
const drained = await (0, drain_1.drainWithTimeout)(inFlight, this.config.shutdownTimeout);
|
|
80
|
+
if (!drained) {
|
|
81
|
+
this.logger.warn(`StalenessMonitor drain timed out after ${this.config.shutdownTimeout}ms — ` +
|
|
82
|
+
'the in-flight sweep was abandoned.');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
68
85
|
}
|
|
69
86
|
/**
|
|
70
87
|
* Run a single staleness sweep — invoked both by the scheduled loop
|
|
@@ -93,7 +110,15 @@ let StalenessMonitor = StalenessMonitor_1 = class StalenessMonitor {
|
|
|
93
110
|
return;
|
|
94
111
|
}
|
|
95
112
|
this.monitorLoop = setTimeout(() => {
|
|
96
|
-
|
|
113
|
+
this.monitorLoop = null;
|
|
114
|
+
const sweep = this.checkStaleness();
|
|
115
|
+
this.inFlightSweep = sweep;
|
|
116
|
+
void sweep.finally(() => {
|
|
117
|
+
if (this.inFlightSweep === sweep) {
|
|
118
|
+
this.inFlightSweep = null;
|
|
119
|
+
}
|
|
120
|
+
this.scheduleNext();
|
|
121
|
+
});
|
|
97
122
|
}, this.config.monitorInterval);
|
|
98
123
|
}
|
|
99
124
|
async markStale(beforeDate, statuses, reason) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"staleness-monitor.js","sourceRoot":"","sources":["../../src/recovery/staleness-monitor.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAA4D;AAE5D,6FAGoD;AACpD,oEAAgE;AAGhE;;;;;;;;;;;;;;;GAeG;AAEI,IAAM,gBAAgB,wBAAtB,MAAM,gBAAgB;
|
|
1
|
+
{"version":3,"file":"staleness-monitor.js","sourceRoot":"","sources":["../../src/recovery/staleness-monitor.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,7 +12,7 @@ import type { EventPublication } from '../types/event-publication';
|
|
|
12
12
|
* `COMPLETED` / `FAILED` → `RESUBMITTED`);
|
|
13
13
|
* - hand out deserialized event payloads ready for listener invocation.
|
|
14
14
|
*
|
|
15
|
-
* Does NOT invoke listeners — that is the dispatcher's job
|
|
15
|
+
* Does NOT invoke listeners — that is the dispatcher's job.
|
|
16
16
|
* Inspired by the internals of Spring Modulith's Event Publication
|
|
17
17
|
* Registry.
|
|
18
18
|
*/
|
|
@@ -28,7 +28,7 @@ const publication_status_1 = require("../types/publication-status");
|
|
|
28
28
|
* `COMPLETED` / `FAILED` → `RESUBMITTED`);
|
|
29
29
|
* - hand out deserialized event payloads ready for listener invocation.
|
|
30
30
|
*
|
|
31
|
-
* Does NOT invoke listeners — that is the dispatcher's job
|
|
31
|
+
* Does NOT invoke listeners — that is the dispatcher's job.
|
|
32
32
|
* Inspired by the internals of Spring Modulith's Event Publication
|
|
33
33
|
* Registry.
|
|
34
34
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-publication-registry.js","sourceRoot":"","sources":["../../src/registry/event-publication-registry.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAoD;AAEpD,
|
|
1
|
+
{"version":3,"file":"event-publication-registry.js","sourceRoot":"","sources":["../../src/registry/event-publication-registry.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAoD;AAEpD,6FAA0F;AAE1F,wEAAqE;AAErE,8DAA0D;AAE1D,oEAAgE;AAEhE;;;;;;;;;;;;;GAaG;AAEI,IAAM,wBAAwB,GAA9B,MAAM,wBAAwB;IAGhB;IAEA;IAJnB,YAEmB,UAAsC,EAEtC,UAA2B;QAF3B,eAAU,GAAV,UAAU,CAA4B;QAEtC,eAAU,GAAV,UAAU,CAAiB;IAC3C,CAAC;IAEJ;;;;;;;;;OASG;IACH,KAAK,CAAC,OAAO,CAAC,KAAc,EAAE,WAA8B;QAC1D,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7B,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,oEAAoE;QACpE,kEAAkE;QAClE,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACpD,MAAM,SAAS,GAAI,KAAgB,CAAC,WAAW,CAAC,IAAI,CAAC;QAErD,MAAM,MAAM,GAA0B,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;YACrE,UAAU;YACV,SAAS;YACT,eAAe,EAAE,UAAU;SAC5B,CAAC,CAAC,CAAC;QAEJ,OAAO,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC3C,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,QAAQ,CAAC,aAAqB;QAClC,OAAO,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;IACjD,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,aAAa,CACjB,aAAqB,EACrB,OAAuB,gCAAc,CAAC,MAAM;QAE5C,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,gCAAc,CAAC,MAAM;gBACxB,MAAM,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,aAAa,EAAE,sCAAiB,CAAC,SAAS,EAAE;oBAC7E,cAAc,EAAE,IAAI,IAAI,EAAE;iBAC3B,CAAC,CAAC;gBACH,OAAO;YACT,KAAK,gCAAc,CAAC,MAAM;gBACxB,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;gBAC5C,OAAO;YACT,KAAK,gCAAc,CAAC,OAAO;gBACzB,MAAM,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC;gBACtD,OAAO;QACX,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,UAAU,CAAC,aAAqB,EAAE,MAAc;QACpD,MAAM,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,aAAa,EAAE,sCAAiB,CAAC,MAAM,EAAE;YAC1E,aAAa,EAAE,MAAM;SACtB,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,eAAe,CAAC,aAAqB;QACzC,MAAM,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,aAAa,EAAE,sCAAiB,CAAC,WAAW,EAAE;YAC/E,oBAAoB,EAAE,IAAI,IAAI,EAAE;SACjC,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACH,WAAW,CAAC,WAA6B;QACvC,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,WAAW,CAAC,eAAe,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IACzF,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,sBAAsB,CAAC,KAAa;QACxC,OAAO,IAAI,CAAC,UAAU,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAC;IACvD,CAAC;CACF,CAAA;AA5GY,4DAAwB;mCAAxB,wBAAwB;IADpC,IAAA,mBAAU,GAAE;IAGR,WAAA,IAAA,eAAM,EAAC,2DAA4B,CAAC,CAAA;IAEpC,WAAA,IAAA,eAAM,EAAC,mCAAgB,CAAC,CAAA;;GAJhB,wBAAwB,CA4GpC"}
|
|
@@ -22,8 +22,8 @@ export interface RegisteredOutboxListener {
|
|
|
22
22
|
* keyed by both the listener id (for targeted dispatch / resume) and
|
|
23
23
|
* the event class name (for fan-out when a new event is published).
|
|
24
24
|
*
|
|
25
|
-
* Populated at application bootstrap by the scanner
|
|
26
|
-
*
|
|
25
|
+
* Populated at application bootstrap by the scanner. Stateless
|
|
26
|
+
* beyond the two index maps.
|
|
27
27
|
*/
|
|
28
28
|
export declare class OutboxListenerRegistry {
|
|
29
29
|
private readonly listenersByEventType;
|
|
@@ -16,8 +16,8 @@ const errors_1 = require("../types/errors");
|
|
|
16
16
|
* keyed by both the listener id (for targeted dispatch / resume) and
|
|
17
17
|
* the event class name (for fan-out when a new event is published).
|
|
18
18
|
*
|
|
19
|
-
* Populated at application bootstrap by the scanner
|
|
20
|
-
*
|
|
19
|
+
* Populated at application bootstrap by the scanner. Stateless
|
|
20
|
+
* beyond the two index maps.
|
|
21
21
|
*/
|
|
22
22
|
let OutboxListenerRegistry = class OutboxListenerRegistry {
|
|
23
23
|
listenersByEventType = new Map();
|
|
@@ -19,15 +19,22 @@ import { type OutboxDataSourceNames } from '../dispatcher/outbox-event-publisher
|
|
|
19
19
|
* source-level clarity.
|
|
20
20
|
*/
|
|
21
21
|
export declare const OUTBOX_LISTENER_REGISTRAR_TOKEN: unique symbol;
|
|
22
|
-
/**
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Shape of an entry registered through the structural registrar port.
|
|
24
|
+
*
|
|
25
|
+
* Exported because it is the parameter type of the public
|
|
26
|
+
* {@link MultiDsOutboxListenerRegistrar.register} — without it a consumer
|
|
27
|
+
* cannot name what that method takes. Flagged by api-extractor's
|
|
28
|
+
* `ae-forgotten-export`.
|
|
29
|
+
*/
|
|
30
|
+
export interface RegistrarListenerEntry {
|
|
24
31
|
readonly id: string;
|
|
25
32
|
readonly eventType: string;
|
|
26
33
|
readonly invoke: (event: unknown) => Promise<void>;
|
|
27
34
|
}
|
|
28
35
|
/**
|
|
29
36
|
* Multi-dataSource implementation of the cqrs package's
|
|
30
|
-
* `OutboxListenerRegistrar` structural port
|
|
37
|
+
* `OutboxListenerRegistrar` structural port.
|
|
31
38
|
*
|
|
32
39
|
* Walks every per-dataSource {@link EventTypeRegistry} to resolve
|
|
33
40
|
* which dataSource owns each incoming listener's event class, then
|
|
@@ -74,5 +81,4 @@ export declare class MultiDsOutboxListenerRegistrar {
|
|
|
74
81
|
*/
|
|
75
82
|
private collectEventTypeRegistries;
|
|
76
83
|
}
|
|
77
|
-
export {};
|
|
78
84
|
//# sourceMappingURL=multi-ds-listener-registrar.d.ts.map
|
|
@@ -40,7 +40,7 @@ const token_utils_1 = require("../tokens/token-utils");
|
|
|
40
40
|
exports.OUTBOX_LISTENER_REGISTRAR_TOKEN = Symbol.for('@nestjs-transactional/cqrs/outbox-listener-registrar');
|
|
41
41
|
/**
|
|
42
42
|
* Multi-dataSource implementation of the cqrs package's
|
|
43
|
-
* `OutboxListenerRegistrar` structural port
|
|
43
|
+
* `OutboxListenerRegistrar` structural port.
|
|
44
44
|
*
|
|
45
45
|
* Walks every per-dataSource {@link EventTypeRegistry} to resolve
|
|
46
46
|
* which dataSource owns each incoming listener's event class, then
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multi-ds-listener-registrar.js","sourceRoot":"","sources":["../../src/registry/multi-ds-listener-registrar.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAA4D;AAC5D,uCAAyC;AAEzC,iFAG8C;AAE9C,8EAAwF;AACxF,
|
|
1
|
+
{"version":3,"file":"multi-ds-listener-registrar.js","sourceRoot":"","sources":["../../src/registry/multi-ds-listener-registrar.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAA4D;AAC5D,uCAAyC;AAEzC,iFAG8C;AAE9C,8EAAwF;AACxF,uDAAkG;AAIlG;;;;;;;;;;;;;;;;;GAiBG;AACU,QAAA,+BAA+B,GAAG,MAAM,CAAC,GAAG,CACvD,sDAAsD,CACvD,CAAC;AAgBF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEI,IAAM,8BAA8B,sCAApC,MAAM,8BAA8B;IAItB;IAEA;IALF,MAAM,GAAG,IAAI,eAAM,CAAC,gCAA8B,CAAC,IAAI,CAAC,CAAC;IAE1E,YACmB,SAAoB,EAEpB,eAAsC;QAFtC,cAAS,GAAT,SAAS,CAAW;QAEpB,oBAAe,GAAf,eAAe,CAAuB;IACtD,CAAC;IAEJ;;;;;OAKG;IACH,QAAQ,CAAC,QAAgC;QACvC,MAAM,mBAAmB,GAAG,IAAI,CAAC,0BAA0B,EAAE,CAAC;QAC9D,MAAM,UAAU,GAAG,IAAA,sDAAgC,EAAC,QAAQ,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;QAE7F,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CACjC,IAAA,4CAA8B,EAAC,UAAU,CAAC,EAC1C,EAAE,MAAM,EAAE,KAAK,EAAE,CAClB,CAAC;QACF,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAE5B,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,uBAAuB,QAAQ,CAAC,EAAE,gBAAgB,QAAQ,CAAC,SAAS,mBAAmB,UAAU,GAAG,CACrG,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACK,0BAA0B;QAChC,MAAM,UAAU,GAAG,IAAI,GAAG,EAA6B,CAAC;QACxD,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC;YACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CACjC,IAAA,uCAAyB,EAAC,UAAU,CAAC,EACrC,EAAE,MAAM,EAAE,KAAK,EAAE,CAClB,CAAC;YACF,UAAU,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QACvC,CAAC;QACD,OAAO,UAAU,CAAC;IACpB,CAAC;CACF,CAAA;AAhDY,wEAA8B;yCAA9B,8BAA8B;IAD1C,IAAA,mBAAU,GAAE;IAMR,WAAA,IAAA,eAAM,EAAC,iDAAwB,CAAC,CAAA;qCADL,gBAAS;GAJ5B,8BAA8B,CAgD1C"}
|
|
@@ -9,7 +9,7 @@ import { type OutboxDataSourceNames } from '../dispatcher/outbox-event-publisher
|
|
|
9
9
|
* the per-dataSource {@link OutboxListenerRegistry} that owns each
|
|
10
10
|
* decorated event class.
|
|
11
11
|
*
|
|
12
|
-
* **Per-dataSource routing
|
|
12
|
+
* **Per-dataSource routing.** Multi-`OutboxModule.forRoot`
|
|
13
13
|
* deployments register events to per-DS `EventTypeRegistry`
|
|
14
14
|
* instances via `OutboxModule.forFeature(events, { dataSource })`.
|
|
15
15
|
* The scanner walks every per-DS `EventTypeRegistry`, resolves which
|
|
@@ -30,7 +30,7 @@ const errors_1 = require("../types/errors");
|
|
|
30
30
|
* the per-dataSource {@link OutboxListenerRegistry} that owns each
|
|
31
31
|
* decorated event class.
|
|
32
32
|
*
|
|
33
|
-
* **Per-dataSource routing
|
|
33
|
+
* **Per-dataSource routing.** Multi-`OutboxModule.forRoot`
|
|
34
34
|
* deployments register events to per-DS `EventTypeRegistry`
|
|
35
35
|
* instances via `OutboxModule.forFeature(events, { dataSource })`.
|
|
36
36
|
* The scanner walks every per-DS `EventTypeRegistry`, resolves which
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outbox-listener-scanner.js","sourceRoot":"","sources":["../../src/registry/outbox-listener-scanner.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"outbox-listener-scanner.js","sourceRoot":"","sources":["../../src/registry/outbox-listener-scanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -64,15 +64,35 @@ export interface EventPublicationRepository {
|
|
|
64
64
|
* Atomically claim a publication: transition `PUBLISHED` or
|
|
65
65
|
* `RESUBMITTED` → `PROCESSING` (and increment `completionAttempts`)
|
|
66
66
|
* iff the current status is one of those. Returns `true` when the
|
|
67
|
-
* claim succeeded, `false` otherwise
|
|
68
|
-
*
|
|
67
|
+
* claim succeeded, `false` otherwise.
|
|
68
|
+
*
|
|
69
|
+
* **This method carries the SPI's whole concurrency guarantee.** It is
|
|
70
|
+
* what prevents double-processing across workers, so the status check
|
|
71
|
+
* and the transition MUST be one indivisible operation — a conditional
|
|
72
|
+
* `UPDATE ... WHERE status IN (...)` reporting affected-row count, a
|
|
73
|
+
* `findOneAndUpdate` with the status in the filter, or an equivalent
|
|
74
|
+
* compare-and-set. A read-then-write implementation is incorrect: two
|
|
75
|
+
* workers would both observe `PUBLISHED` and both dispatch.
|
|
76
|
+
*
|
|
77
|
+
* See DD-025.
|
|
69
78
|
*/
|
|
70
79
|
tryClaim(id: string): Promise<boolean>;
|
|
71
80
|
/**
|
|
72
81
|
* Find publications ready for processing (status `PUBLISHED` or
|
|
73
|
-
* `RESUBMITTED`)
|
|
74
|
-
*
|
|
75
|
-
*
|
|
82
|
+
* `RESUBMITTED`), oldest first.
|
|
83
|
+
*
|
|
84
|
+
* Deliberately NOT exclusive: implementations need not lock rows or
|
|
85
|
+
* hand out disjoint sets, and concurrent workers may receive
|
|
86
|
+
* overlapping results. Correctness comes from {@link tryClaim} — a
|
|
87
|
+
* worker that loses the claim skips the publication without invoking
|
|
88
|
+
* its listener, so overlap costs a wasted read, never a duplicate
|
|
89
|
+
* dispatch.
|
|
90
|
+
*
|
|
91
|
+
* Row-locking implementations (`SELECT ... FOR UPDATE SKIP LOCKED`)
|
|
92
|
+
* are permitted but not expected, and neither shipped implementation
|
|
93
|
+
* uses one: a pessimistic lock has to be held by a transaction that
|
|
94
|
+
* spans the listener invocation, which is unsafe for long-running
|
|
95
|
+
* listeners. See DD-025.
|
|
76
96
|
*/
|
|
77
97
|
findReadyForProcessing(limit: number): Promise<EventPublication[]>;
|
|
78
98
|
/**
|
|
@@ -80,7 +100,18 @@ export interface EventPublicationRepository {
|
|
|
80
100
|
* strictly before `beforeDate`. Used by the staleness monitor.
|
|
81
101
|
*/
|
|
82
102
|
findStale(beforeDate: Date, statuses: PublicationStatus[]): Promise<EventPublication[]>;
|
|
83
|
-
/**
|
|
103
|
+
/**
|
|
104
|
+
* Find all completed publications, with optional pagination / cutoff,
|
|
105
|
+
* ordered by `completionDate` **oldest first**.
|
|
106
|
+
*
|
|
107
|
+
* The ordering is part of the contract because `limit` makes it one.
|
|
108
|
+
* A retention job asks for a bounded page and deletes it; if the page
|
|
109
|
+
* came back newest-first, a job that cannot keep up with the
|
|
110
|
+
* completion rate would delete only the recent rows and never reach
|
|
111
|
+
* the old ones, which is the exact opposite of what a retention window
|
|
112
|
+
* promises. Draining from the oldest end degrades correctly instead:
|
|
113
|
+
* falling behind shrinks the backlog, it does not strand its tail.
|
|
114
|
+
*/
|
|
84
115
|
findCompleted(options?: FindCompletedOptions): Promise<EventPublication[]>;
|
|
85
116
|
/**
|
|
86
117
|
* Find all incomplete publications (status other than `COMPLETED`).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-publication-repository.js","sourceRoot":"","sources":["../../src/repository/event-publication-repository.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"event-publication-repository.js","sourceRoot":"","sources":["../../src/repository/event-publication-repository.ts"],"names":[],"mappings":";;;AA4JA,kEAAkE;AACrD,QAAA,4BAA4B,GAAG,MAAM,CAAC,8BAA8B,CAAC,CAAC"}
|
|
@@ -6,7 +6,7 @@ import type { EventTypeRegistry } from './event-type-registry';
|
|
|
6
6
|
* the event-type — otherwise throws {@link OutboxError} with an
|
|
7
7
|
* actionable message.
|
|
8
8
|
*
|
|
9
|
-
* Single source of truth shared by three consumers
|
|
9
|
+
* Single source of truth shared by three consumers:
|
|
10
10
|
*
|
|
11
11
|
* - {@link OutboxEventPublisher.resolveDataSource} — smart-facade
|
|
12
12
|
* publisher routes events to the dataSource that registered them.
|
|
@@ -9,7 +9,7 @@ const errors_1 = require("../types/errors");
|
|
|
9
9
|
* the event-type — otherwise throws {@link OutboxError} with an
|
|
10
10
|
* actionable message.
|
|
11
11
|
*
|
|
12
|
-
* Single source of truth shared by three consumers
|
|
12
|
+
* Single source of truth shared by three consumers:
|
|
13
13
|
*
|
|
14
14
|
* - {@link OutboxEventPublisher.resolveDataSource} — smart-facade
|
|
15
15
|
* publisher routes events to the dataSource that registered them.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-event-serializer.js","sourceRoot":"","sources":["../../src/serialization/json-event-serializer.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAE5C,4CAA2E;AAG3E,+DAA0D;AAE1D;;;;;;;;;;GAUG;AAEI,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IACD;IAA7B,YAA6B,QAA2B;QAA3B,aAAQ,GAAR,QAAQ,CAAmB;IAAG,CAAC;IAE5D,SAAS,CAAC,KAAc;QACtB,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACxC,MAAM,IAAI,2BAAkB,CAAC,gCAAgC,OAAO,KAAK,EAAE,CAAC,CAAC;QAC/E,CAAC;QACD,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,2BAAkB,
|
|
1
|
+
{"version":3,"file":"json-event-serializer.js","sourceRoot":"","sources":["../../src/serialization/json-event-serializer.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAE5C,4CAA2E;AAG3E,+DAA0D;AAE1D;;;;;;;;;;GAUG;AAEI,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IACD;IAA7B,YAA6B,QAA2B;QAA3B,aAAQ,GAAR,QAAQ,CAAmB;IAAG,CAAC;IAE5D,SAAS,CAAC,KAAc;QACtB,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACxC,MAAM,IAAI,2BAAkB,CAAC,gCAAgC,OAAO,KAAK,EAAE,CAAC,CAAC;QAC/E,CAAC;QACD,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,2BAAkB,CAAC,8BAA+B,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IAED,WAAW,CAAC,UAAkB,EAAE,SAAiB;QAC/C,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAEhD,IAAI,IAAa,CAAC;QAClB,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAChC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,6BAAoB,CAC5B,gCAAgC,SAAS,MAAO,GAAa,CAAC,OAAO,EAAE,CACxE,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,SAAS,GAAW,UAAU,CAAC,SAAmB,CAAC;QACzD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAW,EAAE,IAAI,CAAC,CAAC;IACjE,CAAC;CACF,CAAA;AAjCY,kDAAmB;8BAAnB,mBAAmB;IAD/B,IAAA,mBAAU,GAAE;qCAE4B,uCAAiB;GAD7C,mBAAmB,CAiC/B"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default drain budget for the outbox background workers, in
|
|
3
|
+
* milliseconds.
|
|
4
|
+
*
|
|
5
|
+
* Sized against platform grace periods rather than against any
|
|
6
|
+
* particular listener: Kubernetes' `terminationGracePeriodSeconds`
|
|
7
|
+
* defaults to 30s, and the rest of the shutdown path (closing the
|
|
8
|
+
* connection pool, flushing logs) needs room after the drain. 10s
|
|
9
|
+
* leaves that margin.
|
|
10
|
+
*/
|
|
11
|
+
export declare const DEFAULT_DRAIN_TIMEOUT_MS = 10000;
|
|
12
|
+
/**
|
|
13
|
+
* Await `inFlight`, giving up after `timeoutMs`.
|
|
14
|
+
*
|
|
15
|
+
* Resolves `true` when the work finished within the budget and `false`
|
|
16
|
+
* when the budget ran out first. A `timeoutMs` of `0` means "do not
|
|
17
|
+
* wait at all" and reports `false` without awaiting anything.
|
|
18
|
+
*
|
|
19
|
+
* The abandoned work is NOT cancelled — there is no safe way to
|
|
20
|
+
* interrupt a half-finished listener invocation or SQL statement. The
|
|
21
|
+
* caller's contract is weaker and deliberately so: shutdown proceeds,
|
|
22
|
+
* and whatever was left mid-flight is recovered on the next boot by
|
|
23
|
+
* the staleness monitor / startup recovery. Blocking shutdown
|
|
24
|
+
* indefinitely on user code would be the worse failure — a deployment
|
|
25
|
+
* that never completes.
|
|
26
|
+
*
|
|
27
|
+
* Never rejects: both the processor's `processBatch` and the monitor's
|
|
28
|
+
* `checkStaleness` already swallow their own failures, and a drain that
|
|
29
|
+
* threw would turn an orderly shutdown into an unhandled rejection.
|
|
30
|
+
*/
|
|
31
|
+
export declare function drainWithTimeout(inFlight: Promise<unknown>, timeoutMs: number): Promise<boolean>;
|
|
32
|
+
//# sourceMappingURL=drain.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_DRAIN_TIMEOUT_MS = void 0;
|
|
4
|
+
exports.drainWithTimeout = drainWithTimeout;
|
|
5
|
+
/**
|
|
6
|
+
* Default drain budget for the outbox background workers, in
|
|
7
|
+
* milliseconds.
|
|
8
|
+
*
|
|
9
|
+
* Sized against platform grace periods rather than against any
|
|
10
|
+
* particular listener: Kubernetes' `terminationGracePeriodSeconds`
|
|
11
|
+
* defaults to 30s, and the rest of the shutdown path (closing the
|
|
12
|
+
* connection pool, flushing logs) needs room after the drain. 10s
|
|
13
|
+
* leaves that margin.
|
|
14
|
+
*/
|
|
15
|
+
exports.DEFAULT_DRAIN_TIMEOUT_MS = 10_000;
|
|
16
|
+
/**
|
|
17
|
+
* Await `inFlight`, giving up after `timeoutMs`.
|
|
18
|
+
*
|
|
19
|
+
* Resolves `true` when the work finished within the budget and `false`
|
|
20
|
+
* when the budget ran out first. A `timeoutMs` of `0` means "do not
|
|
21
|
+
* wait at all" and reports `false` without awaiting anything.
|
|
22
|
+
*
|
|
23
|
+
* The abandoned work is NOT cancelled — there is no safe way to
|
|
24
|
+
* interrupt a half-finished listener invocation or SQL statement. The
|
|
25
|
+
* caller's contract is weaker and deliberately so: shutdown proceeds,
|
|
26
|
+
* and whatever was left mid-flight is recovered on the next boot by
|
|
27
|
+
* the staleness monitor / startup recovery. Blocking shutdown
|
|
28
|
+
* indefinitely on user code would be the worse failure — a deployment
|
|
29
|
+
* that never completes.
|
|
30
|
+
*
|
|
31
|
+
* Never rejects: both the processor's `processBatch` and the monitor's
|
|
32
|
+
* `checkStaleness` already swallow their own failures, and a drain that
|
|
33
|
+
* threw would turn an orderly shutdown into an unhandled rejection.
|
|
34
|
+
*/
|
|
35
|
+
async function drainWithTimeout(inFlight, timeoutMs) {
|
|
36
|
+
if (timeoutMs <= 0) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
let timer;
|
|
40
|
+
const expired = new Promise((resolve) => {
|
|
41
|
+
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
42
|
+
// Do not hold the event loop open purely to police the deadline —
|
|
43
|
+
// if everything else has finished, the process should be free to
|
|
44
|
+
// exit.
|
|
45
|
+
timer.unref?.();
|
|
46
|
+
});
|
|
47
|
+
try {
|
|
48
|
+
return await Promise.race([inFlight.then(() => true), expired]);
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
if (timer !== undefined) {
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=drain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drain.js","sourceRoot":"","sources":["../../src/shutdown/drain.ts"],"names":[],"mappings":";;;AA+BA,4CAwBC;AAvDD;;;;;;;;;GASG;AACU,QAAA,wBAAwB,GAAG,MAAM,CAAC;AAE/C;;;;;;;;;;;;;;;;;;GAkBG;AACI,KAAK,UAAU,gBAAgB,CACpC,QAA0B,EAC1B,SAAiB;IAEjB,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;QACnB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,KAAiC,CAAC;IACtC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAQ,CAAC,OAAO,EAAE,EAAE;QAC7C,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC,CAAC;QACpD,kEAAkE;QAClE,iEAAiE;QACjE,QAAQ;QACR,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IAClE,CAAC;YAAS,CAAC;QACT,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;AACH,CAAC"}
|