@relaybus/core 0.3.0 → 0.4.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 +6 -1
- package/dist/{chunk-3OHMEMZA.cjs → chunk-J76M7XTS.cjs} +127 -9
- package/dist/chunk-J76M7XTS.cjs.map +1 -0
- package/dist/{chunk-WS274DL3.js → chunk-NOR5JHVN.js} +121 -9
- package/dist/chunk-NOR5JHVN.js.map +1 -0
- package/dist/index.cjs +2301 -1903
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +88 -475
- package/dist/index.d.ts +88 -475
- package/dist/index.js +2316 -1924
- package/dist/index.js.map +1 -1
- package/dist/internal/index.cjs +492 -140
- package/dist/internal/index.cjs.map +1 -1
- package/dist/internal/index.d.cts +321 -9
- package/dist/internal/index.d.ts +321 -9
- package/dist/internal/index.js +450 -124
- package/dist/internal/index.js.map +1 -1
- package/dist/transport-DIFdeXQY.d.cts +536 -0
- package/dist/transport-DIFdeXQY.d.ts +536 -0
- package/package.json +1 -1
- package/dist/chunk-3OHMEMZA.cjs.map +0 -1
- package/dist/chunk-WS274DL3.js.map +0 -1
- package/dist/event-DCRTuYKI.d.cts +0 -142
- package/dist/event-DCRTuYKI.d.ts +0 -142
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkJ76M7XTS_cjs = require('./chunk-J76M7XTS.cjs');
|
|
4
4
|
var crypto = require('crypto');
|
|
5
5
|
var async_hooks = require('async_hooks');
|
|
6
6
|
var os = require('os');
|
|
@@ -10,47 +10,6 @@ var systemClock = {
|
|
|
10
10
|
now: () => /* @__PURE__ */ new Date()
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
// src/duration.ts
|
|
14
|
-
var MILLISECONDS_PER_UNIT = {
|
|
15
|
-
ms: 1,
|
|
16
|
-
s: 1e3,
|
|
17
|
-
m: 6e4,
|
|
18
|
-
h: 36e5,
|
|
19
|
-
d: 864e5
|
|
20
|
-
};
|
|
21
|
-
var DURATION_PATTERN = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/;
|
|
22
|
-
function toMilliseconds(value, name) {
|
|
23
|
-
if (typeof value === "number") {
|
|
24
|
-
if (!Number.isFinite(value) || value < 0) {
|
|
25
|
-
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
26
|
-
`${name} must be a non-negative finite number of milliseconds, received ${value}`
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
return value;
|
|
30
|
-
}
|
|
31
|
-
const match = DURATION_PATTERN.exec(value);
|
|
32
|
-
if (match === null) {
|
|
33
|
-
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
34
|
-
`${name} must look like '500ms', '30s', '5m', '1h' or '7d', received '${value}'`
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
const [, amount, unit] = match;
|
|
38
|
-
return Number(amount) * MILLISECONDS_PER_UNIT[unit];
|
|
39
|
-
}
|
|
40
|
-
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
41
|
-
function toTimerMilliseconds(value, name) {
|
|
42
|
-
const milliseconds = toMilliseconds(value, name);
|
|
43
|
-
if (milliseconds === 0) {
|
|
44
|
-
throw new chunk3OHMEMZA_cjs.ConfigurationError(`${name} must be greater than zero`);
|
|
45
|
-
}
|
|
46
|
-
if (milliseconds > MAX_TIMER_DELAY_MS) {
|
|
47
|
-
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
48
|
-
`${name} must not exceed ${MAX_TIMER_DELAY_MS}ms (about 24.8 days), received ${milliseconds}ms`
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
return milliseconds;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
13
|
// src/catalog.ts
|
|
55
14
|
var EVENT_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
56
15
|
var descriptions = /* @__PURE__ */ new WeakMap();
|
|
@@ -58,25 +17,25 @@ function defineCatalog(events, options = {}) {
|
|
|
58
17
|
const label = options.name === void 0 ? "Catalog" : `Catalog '${options.name}'`;
|
|
59
18
|
const names = Object.keys(events);
|
|
60
19
|
if (names.length === 0) {
|
|
61
|
-
throw new
|
|
20
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(`${label} must contain at least one event`);
|
|
62
21
|
}
|
|
63
22
|
const owners = /* @__PURE__ */ new Map();
|
|
64
23
|
const entries = names.map((name) => {
|
|
65
24
|
const eventClass = events[name];
|
|
66
25
|
if (!EVENT_NAME_PATTERN.test(name)) {
|
|
67
|
-
throw new
|
|
26
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(
|
|
68
27
|
`${label} entry '${name}' must be a valid identifier made of letters, digits and underscores because it names the handler method on${name} and its handler id`
|
|
69
28
|
);
|
|
70
29
|
}
|
|
71
30
|
if (typeof eventClass !== "function" || typeof eventClass.type !== "string") {
|
|
72
|
-
throw new
|
|
31
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(
|
|
73
32
|
`${label} entry '${name}' must be an event class created with defineEvent`
|
|
74
33
|
);
|
|
75
34
|
}
|
|
76
|
-
|
|
35
|
+
chunkJ76M7XTS_cjs.assertEventType(eventClass.type);
|
|
77
36
|
const owner = owners.get(eventClass.type);
|
|
78
37
|
if (owner !== void 0) {
|
|
79
|
-
throw new
|
|
38
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(
|
|
80
39
|
`${label} entries '${owner}' and '${name}' share the event type '${eventClass.type}'`
|
|
81
40
|
);
|
|
82
41
|
}
|
|
@@ -90,7 +49,7 @@ function defineCatalog(events, options = {}) {
|
|
|
90
49
|
function describeCatalog(catalog) {
|
|
91
50
|
const description = descriptions.get(catalog);
|
|
92
51
|
if (description === void 0) {
|
|
93
|
-
throw new
|
|
52
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError("Expected a catalog created with defineCatalog");
|
|
94
53
|
}
|
|
95
54
|
return description;
|
|
96
55
|
}
|
|
@@ -105,19 +64,19 @@ function selectCatalogEntries(catalog, events) {
|
|
|
105
64
|
}
|
|
106
65
|
const label = catalogLabel(catalog);
|
|
107
66
|
if (events.length === 0) {
|
|
108
|
-
throw new
|
|
67
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(
|
|
109
68
|
`The events selected from ${label} must name at least one event; omit events to select all of them`
|
|
110
69
|
);
|
|
111
70
|
}
|
|
112
71
|
const selected = /* @__PURE__ */ new Set();
|
|
113
72
|
for (const event of events) {
|
|
114
73
|
if (!entries.some((entry) => entry.name === event)) {
|
|
115
|
-
throw new
|
|
74
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(
|
|
116
75
|
`${capitalize(label)} has no event named '${String(event)}'; its events are ${entries.map((entry) => entry.name).join(", ")}`
|
|
117
76
|
);
|
|
118
77
|
}
|
|
119
78
|
if (selected.has(event)) {
|
|
120
|
-
throw new
|
|
79
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(`Event '${String(event)}' of ${label} is selected twice`);
|
|
121
80
|
}
|
|
122
81
|
selected.add(event);
|
|
123
82
|
}
|
|
@@ -129,7 +88,7 @@ function capitalize(text) {
|
|
|
129
88
|
function requireCatalogEntry(catalog, event) {
|
|
130
89
|
const entry = describeCatalog(catalog).entries.find((candidate) => candidate.type === event.type);
|
|
131
90
|
if (entry === void 0) {
|
|
132
|
-
throw new
|
|
91
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(
|
|
133
92
|
`Event type '${String(event.type)}' is not part of ${catalogLabel(catalog)}`
|
|
134
93
|
);
|
|
135
94
|
}
|
|
@@ -145,13 +104,13 @@ function resolveCatalogHandlers(catalog, {
|
|
|
145
104
|
const lookup = handlers;
|
|
146
105
|
const missing = entries.filter((entry) => typeof lookup[entry.handlerName] !== "function");
|
|
147
106
|
if (missing.length > 0) {
|
|
148
|
-
throw new
|
|
107
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(
|
|
149
108
|
`Handlers for catalog ${name === void 0 ? "events" : `'${name}'`} lack methods: ${missing.map((entry) => entry.handlerName).join(", ")}`
|
|
150
109
|
);
|
|
151
110
|
}
|
|
152
111
|
const prefix = idPrefix ?? name ?? classNameOf(handlers);
|
|
153
112
|
if (prefix === void 0) {
|
|
154
|
-
throw new
|
|
113
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(
|
|
155
114
|
"Cannot derive handler ids: name the catalog, pass an idPrefix or use a class instance as handlers"
|
|
156
115
|
);
|
|
157
116
|
}
|
|
@@ -182,7 +141,7 @@ function assertExactHandlers(catalog, { entries, handlers }) {
|
|
|
182
141
|
...extra.map((key) => `unexpected ${key}`),
|
|
183
142
|
...notFunctions.map((key) => `${key} is not a function`)
|
|
184
143
|
];
|
|
185
|
-
throw new
|
|
144
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(
|
|
186
145
|
`Handlers do not match ${catalogLabel(catalog)}: ${problems.join(", ")}`
|
|
187
146
|
);
|
|
188
147
|
}
|
|
@@ -233,7 +192,7 @@ var CatalogPublisher = class {
|
|
|
233
192
|
#assertPublishable(event) {
|
|
234
193
|
const { name } = requireCatalogEntry(this.#catalog, event);
|
|
235
194
|
if (!this.#publishes(name)) {
|
|
236
|
-
throw new
|
|
195
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(
|
|
237
196
|
`Event type '${event.type}' is not one of the events this view of ${catalogLabel(
|
|
238
197
|
this.#catalog
|
|
239
198
|
)} publishes (${this.#entries.map((entry) => entry.name).join(", ")})`
|
|
@@ -287,7 +246,7 @@ function assertCatalogsCovered(requirements, registrationsOf) {
|
|
|
287
246
|
}
|
|
288
247
|
if (problems.size > 0) {
|
|
289
248
|
const heading = onlyBareCatalogs ? "Catalog events without a handler or route:" : "Catalog events this service does not cover:";
|
|
290
|
-
throw new
|
|
249
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(
|
|
291
250
|
`${heading}
|
|
292
251
|
${[...problems].map((line) => ` - ${line}`).join("\n")}`
|
|
293
252
|
);
|
|
@@ -341,6 +300,302 @@ function createUuidV7(timestamp = Date.now()) {
|
|
|
341
300
|
return bytes.toString("hex").replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, "$1-$2-$3-$4-$5");
|
|
342
301
|
}
|
|
343
302
|
|
|
303
|
+
// src/in-memory/stored-records.ts
|
|
304
|
+
function applyTransition(delivery, change) {
|
|
305
|
+
if (change.kind === "reschedule") {
|
|
306
|
+
delivery.status = "pending";
|
|
307
|
+
delivery.availableAt = change.availableAt;
|
|
308
|
+
delivery.lastError = change.error;
|
|
309
|
+
} else if (change.kind === "release") {
|
|
310
|
+
delivery.status = "pending";
|
|
311
|
+
delivery.attempts -= 1;
|
|
312
|
+
delivery.availableAt = change.now;
|
|
313
|
+
} else {
|
|
314
|
+
delivery.status = "dead";
|
|
315
|
+
delivery.lastError = change.error;
|
|
316
|
+
}
|
|
317
|
+
delivery.updatedAt = change.now;
|
|
318
|
+
clearLease(delivery);
|
|
319
|
+
}
|
|
320
|
+
function blocksKey(other, { delivery, now }) {
|
|
321
|
+
if (other === delivery || other.handler !== delivery.handler || other.key !== delivery.key) {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
const unfinishedPredecessor = other.sequence < delivery.sequence && (other.status === "pending" || other.status === "processing");
|
|
325
|
+
const runningUnderLiveLease = other.status === "processing" && other.leaseExpiresAt > now;
|
|
326
|
+
return unfinishedPredecessor || runningUnderLiveLease;
|
|
327
|
+
}
|
|
328
|
+
function eventCutoffs(request) {
|
|
329
|
+
const cutoffs = [request.completedBefore, request.deadBefore].filter((cutoff) => cutoff !== void 0).map((cutoff) => cutoff.getTime());
|
|
330
|
+
return {
|
|
331
|
+
orphanCutoff: cutoffs.length === 0 ? Number.NEGATIVE_INFINITY : Math.min(...cutoffs),
|
|
332
|
+
completedCutoff: request.completedBefore?.getTime() ?? Number.NEGATIVE_INFINITY
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function serializeEnvelope(envelope) {
|
|
336
|
+
return JSON.parse(JSON.stringify(envelope));
|
|
337
|
+
}
|
|
338
|
+
function isDue(delivery, now) {
|
|
339
|
+
if (delivery.status === "pending") {
|
|
340
|
+
return delivery.availableAt <= now;
|
|
341
|
+
}
|
|
342
|
+
return delivery.status === "processing" && delivery.leaseExpiresAt <= now;
|
|
343
|
+
}
|
|
344
|
+
function dueAtOf(delivery) {
|
|
345
|
+
return delivery.status === "pending" ? delivery.availableAt : delivery.leaseExpiresAt;
|
|
346
|
+
}
|
|
347
|
+
function compareDue(left, right) {
|
|
348
|
+
const byDueTime = dueAtOf(left).getTime() - dueAtOf(right).getTime();
|
|
349
|
+
return byDueTime === 0 ? compareIds(left.id, right.id) : byDueTime;
|
|
350
|
+
}
|
|
351
|
+
function compareIds(left, right) {
|
|
352
|
+
return Number(left > right) - Number(left < right);
|
|
353
|
+
}
|
|
354
|
+
function clearLease(delivery) {
|
|
355
|
+
delivery.leaseOwner = null;
|
|
356
|
+
delivery.leaseToken = null;
|
|
357
|
+
delivery.leaseExpiresAt = null;
|
|
358
|
+
}
|
|
359
|
+
function matchesQuery(delivery, { event, query }) {
|
|
360
|
+
return (query.status === void 0 || delivery.status === query.status) && (query.handler === void 0 || delivery.handler === query.handler) && (query.eventType === void 0 || event.envelope.type === query.eventType) && (query.eventId === void 0 || delivery.eventId === query.eventId);
|
|
361
|
+
}
|
|
362
|
+
function isPurgeable(delivery, request) {
|
|
363
|
+
if (delivery.status === "completed" && request.completedBefore !== void 0) {
|
|
364
|
+
return delivery.completedAt < request.completedBefore;
|
|
365
|
+
}
|
|
366
|
+
if (delivery.status === "dead" && request.deadBefore !== void 0) {
|
|
367
|
+
return delivery.updatedAt < request.deadBefore;
|
|
368
|
+
}
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// src/in-memory/in-memory-maintenance.ts
|
|
373
|
+
var DEFAULT_LIST_LIMIT = 100;
|
|
374
|
+
var InMemoryMaintenance = class {
|
|
375
|
+
#tables;
|
|
376
|
+
#locks;
|
|
377
|
+
constructor(tables, locks) {
|
|
378
|
+
this.#tables = tables;
|
|
379
|
+
this.#locks = locks;
|
|
380
|
+
}
|
|
381
|
+
listDeliveries(query) {
|
|
382
|
+
const limit = query.limit ?? DEFAULT_LIST_LIMIT;
|
|
383
|
+
const { events, deliveries } = this.#tables;
|
|
384
|
+
return [...deliveries.values()].filter((delivery) => matchesQuery(delivery, { event: events.get(delivery.eventId), query })).sort(
|
|
385
|
+
(left, right) => left.createdAt.getTime() - right.createdAt.getTime() || compareIds(left.id, right.id)
|
|
386
|
+
).slice(0, limit).map((delivery) => ({
|
|
387
|
+
id: delivery.id,
|
|
388
|
+
handler: delivery.handler,
|
|
389
|
+
status: delivery.status,
|
|
390
|
+
attempts: delivery.attempts,
|
|
391
|
+
availableAt: delivery.availableAt,
|
|
392
|
+
leaseOwner: delivery.leaseOwner,
|
|
393
|
+
leaseExpiresAt: delivery.leaseExpiresAt,
|
|
394
|
+
lastError: delivery.lastError,
|
|
395
|
+
createdAt: delivery.createdAt,
|
|
396
|
+
updatedAt: delivery.updatedAt,
|
|
397
|
+
completedAt: delivery.completedAt,
|
|
398
|
+
event: this.#tables.readEnvelope(delivery.eventId)
|
|
399
|
+
}));
|
|
400
|
+
}
|
|
401
|
+
requeue(deliveryIds, now) {
|
|
402
|
+
let moved = 0;
|
|
403
|
+
for (const deliveryId of new Set(deliveryIds)) {
|
|
404
|
+
const delivery = this.#tables.deliveries.get(deliveryId);
|
|
405
|
+
if (delivery?.status !== "dead") {
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
delivery.status = "pending";
|
|
409
|
+
delivery.attempts = 0;
|
|
410
|
+
delivery.availableAt = now;
|
|
411
|
+
delivery.updatedAt = now;
|
|
412
|
+
moved += 1;
|
|
413
|
+
}
|
|
414
|
+
return moved;
|
|
415
|
+
}
|
|
416
|
+
purge(request) {
|
|
417
|
+
let deletedDeliveries = 0;
|
|
418
|
+
const touchedEventIds = /* @__PURE__ */ new Set();
|
|
419
|
+
for (const delivery of [...this.#tables.deliveries.values()]) {
|
|
420
|
+
if (isPurgeable(delivery, request)) {
|
|
421
|
+
this.#tables.deliveries.delete(delivery.id);
|
|
422
|
+
touchedEventIds.add(delivery.eventId);
|
|
423
|
+
deletedDeliveries += 1;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return { deletedDeliveries, deletedEvents: this.#purgeEvents(request, touchedEventIds) };
|
|
427
|
+
}
|
|
428
|
+
statistics(now) {
|
|
429
|
+
const counts = {
|
|
430
|
+
pending: 0,
|
|
431
|
+
processing: 0,
|
|
432
|
+
completed: 0,
|
|
433
|
+
dead: 0
|
|
434
|
+
};
|
|
435
|
+
let oldestDueAt = null;
|
|
436
|
+
for (const delivery of this.#tables.deliveries.values()) {
|
|
437
|
+
counts[delivery.status] += 1;
|
|
438
|
+
if (isDue(delivery, now)) {
|
|
439
|
+
const dueAt = dueAtOf(delivery);
|
|
440
|
+
if (oldestDueAt === null || dueAt < oldestDueAt) {
|
|
441
|
+
oldestDueAt = dueAt;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return { ...counts, oldestDueAt };
|
|
446
|
+
}
|
|
447
|
+
/** Deletes the events no delivery references whose retention ran out; returns how many. */
|
|
448
|
+
#purgeEvents(request, touchedEventIds) {
|
|
449
|
+
const { events, deliveries } = this.#tables;
|
|
450
|
+
const referencedEventIds = new Set(
|
|
451
|
+
[...deliveries.values()].map((delivery) => delivery.eventId)
|
|
452
|
+
);
|
|
453
|
+
const { orphanCutoff, completedCutoff } = eventCutoffs(request);
|
|
454
|
+
let deletedEvents = 0;
|
|
455
|
+
for (const [eventId, event] of [...events]) {
|
|
456
|
+
if (referencedEventIds.has(eventId) || this.#locks.isHeld(`event:${eventId}`)) {
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const expired = event.completedAt === null ? touchedEventIds.has(eventId) || event.createdAt.getTime() < orphanCutoff : event.completedAt.getTime() < completedCutoff;
|
|
460
|
+
if (expired) {
|
|
461
|
+
events.delete(eventId);
|
|
462
|
+
deletedEvents += 1;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return deletedEvents;
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
// src/in-memory/in-memory-row-locks.ts
|
|
470
|
+
var InMemoryRowLocks = class {
|
|
471
|
+
#locks = /* @__PURE__ */ new Map();
|
|
472
|
+
#writes = /* @__PURE__ */ new WeakMap();
|
|
473
|
+
/** Whether an open transaction holds `name`. */
|
|
474
|
+
isHeld(name) {
|
|
475
|
+
return this.#locks.has(name);
|
|
476
|
+
}
|
|
477
|
+
/** The events and deliveries the transaction appended so far, which are not applied before it commits. */
|
|
478
|
+
written(transaction) {
|
|
479
|
+
let written = this.#writes.get(transaction);
|
|
480
|
+
if (written === void 0) {
|
|
481
|
+
written = { eventIds: /* @__PURE__ */ new Set(), deliveryKeys: /* @__PURE__ */ new Set() };
|
|
482
|
+
this.#writes.set(transaction, written);
|
|
483
|
+
}
|
|
484
|
+
return written;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Waits for other open transactions holding any of `names`, then holds them
|
|
488
|
+
* until `transaction` finishes, the way a database holds row locks. Sorted
|
|
489
|
+
* acquisition keeps two transactions from waiting on each other.
|
|
490
|
+
*/
|
|
491
|
+
async lock(transaction, names) {
|
|
492
|
+
for (const name of [...new Set(names)].sort()) {
|
|
493
|
+
let held = this.#locks.get(name);
|
|
494
|
+
while (held !== void 0 && held.owner !== transaction) {
|
|
495
|
+
await held.released;
|
|
496
|
+
held = this.#locks.get(name);
|
|
497
|
+
}
|
|
498
|
+
if (held !== void 0) {
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
let release;
|
|
502
|
+
const released = new Promise((resolve) => {
|
|
503
|
+
release = resolve;
|
|
504
|
+
});
|
|
505
|
+
this.#locks.set(name, { owner: transaction, released });
|
|
506
|
+
transaction.onFinish(() => {
|
|
507
|
+
this.#locks.delete(name);
|
|
508
|
+
release();
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
// src/in-memory/in-memory-settlement.ts
|
|
515
|
+
var InMemorySettlement = class extends chunkJ76M7XTS_cjs.LeaseSettlement {
|
|
516
|
+
#tables;
|
|
517
|
+
#retainCompleted;
|
|
518
|
+
constructor(tables, retainCompleted) {
|
|
519
|
+
super();
|
|
520
|
+
this.#tables = tables;
|
|
521
|
+
this.#retainCompleted = retainCompleted;
|
|
522
|
+
}
|
|
523
|
+
/** Completes now, or when `transaction` commits if the lease is still held then. */
|
|
524
|
+
complete(lease, { now, transaction }) {
|
|
525
|
+
const delivery = this.findHeld(lease);
|
|
526
|
+
if (delivery === void 0) {
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
if (transaction === void 0) {
|
|
530
|
+
this.#finish(delivery, now);
|
|
531
|
+
return true;
|
|
532
|
+
}
|
|
533
|
+
transaction.enqueue(() => {
|
|
534
|
+
const held = this.findHeld(lease);
|
|
535
|
+
if (held !== void 0) {
|
|
536
|
+
this.#finish(held, now);
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
findHeld(lease) {
|
|
542
|
+
const delivery = this.#tables.deliveries.get(lease.deliveryId);
|
|
543
|
+
if (delivery?.status === "processing" && delivery.leaseToken === lease.leaseToken) {
|
|
544
|
+
return delivery;
|
|
545
|
+
}
|
|
546
|
+
return void 0;
|
|
547
|
+
}
|
|
548
|
+
writeChange(held, { change }) {
|
|
549
|
+
applyTransition(held, change);
|
|
550
|
+
return true;
|
|
551
|
+
}
|
|
552
|
+
/** The in-memory store always passes `now`: its `extendLease` takes it from the port. */
|
|
553
|
+
writeExtension(held, { leaseExpiresAt, now }) {
|
|
554
|
+
held.leaseExpiresAt = leaseExpiresAt;
|
|
555
|
+
held.updatedAt = now;
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
forgetHandedOut() {
|
|
559
|
+
}
|
|
560
|
+
/** Deletes a completed delivery, or marks it completed when a history is kept. */
|
|
561
|
+
#finish(delivery, now) {
|
|
562
|
+
const event = this.#tables.events.get(delivery.eventId);
|
|
563
|
+
if (event.completedAt === null || event.completedAt < now) {
|
|
564
|
+
event.completedAt = now;
|
|
565
|
+
}
|
|
566
|
+
if (!this.#retainCompleted) {
|
|
567
|
+
this.#tables.deliveries.delete(delivery.id);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
delivery.status = "completed";
|
|
571
|
+
delivery.completedAt = now;
|
|
572
|
+
delivery.updatedAt = now;
|
|
573
|
+
clearLease(delivery);
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
// src/in-memory/in-memory-tables.ts
|
|
578
|
+
var InMemoryTables = class {
|
|
579
|
+
events = /* @__PURE__ */ new Map();
|
|
580
|
+
deliveries = /* @__PURE__ */ new Map();
|
|
581
|
+
#keySequences = /* @__PURE__ */ new Map();
|
|
582
|
+
/** The next sequence of `key`; null for an unkeyed event. */
|
|
583
|
+
nextSequence(key) {
|
|
584
|
+
if (key === null) {
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
const next = (this.#keySequences.get(key) ?? 0) + 1;
|
|
588
|
+
this.#keySequences.set(key, next);
|
|
589
|
+
return next;
|
|
590
|
+
}
|
|
591
|
+
/** A fresh copy of a stored event, as a database would read it back. */
|
|
592
|
+
readEnvelope(eventId) {
|
|
593
|
+
const serialized = this.events.get(eventId).envelope;
|
|
594
|
+
const copy = JSON.parse(JSON.stringify(serialized));
|
|
595
|
+
return { ...copy, occurredAt: new Date(copy.occurredAt) };
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
|
|
344
599
|
// src/in-memory-transaction.ts
|
|
345
600
|
var InMemoryTransaction = class {
|
|
346
601
|
#operations = [];
|
|
@@ -351,7 +606,7 @@ var InMemoryTransaction = class {
|
|
|
351
606
|
}
|
|
352
607
|
enqueue(operation) {
|
|
353
608
|
if (this.#state !== "open") {
|
|
354
|
-
throw new
|
|
609
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(`Cannot write through a ${this.#state} in-memory transaction`);
|
|
355
610
|
}
|
|
356
611
|
this.#operations.push(operation);
|
|
357
612
|
}
|
|
@@ -378,24 +633,20 @@ var InMemoryTransaction = class {
|
|
|
378
633
|
};
|
|
379
634
|
|
|
380
635
|
// src/in-memory-event-store.ts
|
|
381
|
-
var DEFAULT_LIST_LIMIT = 100;
|
|
382
636
|
var InMemoryEventStore = class {
|
|
383
|
-
#
|
|
384
|
-
#
|
|
385
|
-
#
|
|
386
|
-
|
|
387
|
-
#locks = /* @__PURE__ */ new Map();
|
|
388
|
-
#writes = /* @__PURE__ */ new WeakMap();
|
|
637
|
+
#tables = new InMemoryTables();
|
|
638
|
+
#locks = new InMemoryRowLocks();
|
|
639
|
+
#settlement;
|
|
640
|
+
#maintenance = new InMemoryMaintenance(this.#tables, this.#locks);
|
|
389
641
|
#generateIdentifier;
|
|
390
|
-
#retainCompleted;
|
|
391
642
|
constructor(options = {}) {
|
|
392
643
|
if (process.env["NODE_ENV"] === "production" && options.allowInProduction !== true) {
|
|
393
|
-
throw new
|
|
644
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(
|
|
394
645
|
"InMemoryEventStore loses every event on restart. Use a database store in production, or pass allowInProduction: true"
|
|
395
646
|
);
|
|
396
647
|
}
|
|
397
648
|
this.#generateIdentifier = options.generateIdentifier ?? (() => createUuidV7());
|
|
398
|
-
this.#
|
|
649
|
+
this.#settlement = new InMemorySettlement(this.#tables, options.retainCompleted ?? false);
|
|
399
650
|
}
|
|
400
651
|
async transaction(work) {
|
|
401
652
|
const transaction = new InMemoryTransaction();
|
|
@@ -410,14 +661,14 @@ var InMemoryEventStore = class {
|
|
|
410
661
|
}
|
|
411
662
|
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
412
663
|
async append(transaction, entries, now) {
|
|
413
|
-
await this.#lock(
|
|
664
|
+
await this.#locks.lock(
|
|
414
665
|
transaction,
|
|
415
666
|
entries.flatMap((entry) => [
|
|
416
667
|
`event:${entry.event.id}`,
|
|
417
668
|
...entry.event.key === null ? [] : [`key:${entry.event.key}`]
|
|
418
669
|
])
|
|
419
670
|
);
|
|
420
|
-
const written = this.#written(transaction);
|
|
671
|
+
const written = this.#locks.written(transaction);
|
|
421
672
|
const pendingEventIds = written.eventIds;
|
|
422
673
|
const pendingDeliveryKeys = written.deliveryKeys;
|
|
423
674
|
const appendedEventIds = [];
|
|
@@ -426,7 +677,7 @@ var InMemoryEventStore = class {
|
|
|
426
677
|
for (const entry of entries) {
|
|
427
678
|
const eventId = entry.event.id;
|
|
428
679
|
const serialized = serializeEnvelope(entry.event);
|
|
429
|
-
const isDuplicate = this.#events.has(eventId) || pendingEventIds.has(eventId);
|
|
680
|
+
const isDuplicate = this.#tables.events.has(eventId) || pendingEventIds.has(eventId);
|
|
430
681
|
if (isDuplicate) {
|
|
431
682
|
duplicateEventIds.push(eventId);
|
|
432
683
|
} else {
|
|
@@ -448,8 +699,8 @@ var InMemoryEventStore = class {
|
|
|
448
699
|
#newDeliveries(entry, pendingDeliveryKeys) {
|
|
449
700
|
const eventId = entry.event.id;
|
|
450
701
|
return entry.deliveries.filter((delivery) => {
|
|
451
|
-
const deliveryKey =
|
|
452
|
-
if (pendingDeliveryKeys.has(deliveryKey) || this.#events.get(eventId)?.handlers.has(delivery.handler) === true) {
|
|
702
|
+
const deliveryKey = chunkJ76M7XTS_cjs.CompositeKey.of(eventId, delivery.handler);
|
|
703
|
+
if (pendingDeliveryKeys.has(deliveryKey) || this.#tables.events.get(eventId)?.handlers.has(delivery.handler) === true) {
|
|
453
704
|
return false;
|
|
454
705
|
}
|
|
455
706
|
pendingDeliveryKeys.add(deliveryKey);
|
|
@@ -459,19 +710,20 @@ var InMemoryEventStore = class {
|
|
|
459
710
|
/** Applies one entry of an append when its transaction commits. */
|
|
460
711
|
#applyAppend(input) {
|
|
461
712
|
const { eventId, serialized, isDuplicate, newDeliveries, now } = input;
|
|
713
|
+
const { events, deliveries } = this.#tables;
|
|
462
714
|
if (!isDuplicate) {
|
|
463
|
-
|
|
715
|
+
events.set(eventId, {
|
|
464
716
|
envelope: serialized,
|
|
465
|
-
sequence: this.#nextSequence(serialized.key),
|
|
717
|
+
sequence: this.#tables.nextSequence(serialized.key),
|
|
466
718
|
createdAt: now,
|
|
467
719
|
handlers: /* @__PURE__ */ new Set(),
|
|
468
720
|
completedAt: null
|
|
469
721
|
});
|
|
470
722
|
}
|
|
471
|
-
const stored =
|
|
723
|
+
const stored = events.get(eventId);
|
|
472
724
|
for (const delivery of newDeliveries) {
|
|
473
725
|
stored.handlers.add(delivery.handler);
|
|
474
|
-
|
|
726
|
+
deliveries.set(delivery.id, {
|
|
475
727
|
id: delivery.id,
|
|
476
728
|
eventId,
|
|
477
729
|
handler: delivery.handler,
|
|
@@ -492,7 +744,7 @@ var InMemoryEventStore = class {
|
|
|
492
744
|
}
|
|
493
745
|
async claim(request) {
|
|
494
746
|
const handlers = new Set(request.handlers);
|
|
495
|
-
const due = [...this.#deliveries.values()].filter((delivery) => handlers.has(delivery.handler) && isDue(delivery, request.now)).sort(compareDue);
|
|
747
|
+
const due = [...this.#tables.deliveries.values()].filter((delivery) => handlers.has(delivery.handler) && isDue(delivery, request.now)).sort(compareDue);
|
|
496
748
|
const claimed = [];
|
|
497
749
|
for (const delivery of due) {
|
|
498
750
|
if (claimed.length >= request.limit) {
|
|
@@ -513,193 +765,50 @@ var InMemoryEventStore = class {
|
|
|
513
765
|
handler: delivery.handler,
|
|
514
766
|
attempt: delivery.attempts,
|
|
515
767
|
leaseExpiresAt: request.leaseExpiresAt,
|
|
516
|
-
event: this.#readEnvelope(delivery.eventId)
|
|
768
|
+
event: this.#tables.readEnvelope(delivery.eventId)
|
|
517
769
|
});
|
|
518
770
|
}
|
|
519
771
|
return claimed;
|
|
520
772
|
}
|
|
521
773
|
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
522
774
|
async extendLease(lease, leaseExpiresAt, now) {
|
|
523
|
-
return this.#
|
|
524
|
-
delivery.leaseExpiresAt = leaseExpiresAt;
|
|
525
|
-
delivery.updatedAt = now;
|
|
526
|
-
});
|
|
775
|
+
return this.#settlement.extendLease({ lease, leaseExpiresAt, now });
|
|
527
776
|
}
|
|
528
777
|
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
529
778
|
async complete(lease, now, transaction) {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
return false;
|
|
535
|
-
}
|
|
536
|
-
transaction.enqueue(() => {
|
|
537
|
-
const delivery = this.#findLeased(lease);
|
|
538
|
-
if (delivery !== void 0) {
|
|
539
|
-
this.#finish(delivery, now);
|
|
540
|
-
}
|
|
541
|
-
});
|
|
542
|
-
return true;
|
|
779
|
+
return this.#settlement.complete(
|
|
780
|
+
lease,
|
|
781
|
+
transaction === void 0 ? { now } : { now, transaction }
|
|
782
|
+
);
|
|
543
783
|
}
|
|
544
784
|
async completeMany(leases, now) {
|
|
545
785
|
return Promise.all(leases.map((lease) => this.complete(lease, now)));
|
|
546
786
|
}
|
|
547
787
|
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
548
788
|
async reschedule(lease, availableAt, error, now) {
|
|
549
|
-
return this.#
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
delivery.lastError = error;
|
|
553
|
-
delivery.updatedAt = now;
|
|
554
|
-
clearLease(delivery);
|
|
789
|
+
return this.#settlement.changeClaim({
|
|
790
|
+
lease,
|
|
791
|
+
change: { kind: "reschedule", availableAt, error, now }
|
|
555
792
|
});
|
|
556
793
|
}
|
|
557
794
|
async release(lease, now) {
|
|
558
|
-
return this.#
|
|
559
|
-
delivery.status = "pending";
|
|
560
|
-
delivery.attempts -= 1;
|
|
561
|
-
delivery.availableAt = now;
|
|
562
|
-
delivery.updatedAt = now;
|
|
563
|
-
clearLease(delivery);
|
|
564
|
-
});
|
|
795
|
+
return this.#settlement.changeClaim({ lease, change: { kind: "release", now } });
|
|
565
796
|
}
|
|
566
797
|
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
567
798
|
async bury(lease, error, now) {
|
|
568
|
-
return this.#
|
|
569
|
-
delivery.status = "dead";
|
|
570
|
-
delivery.lastError = error;
|
|
571
|
-
delivery.updatedAt = now;
|
|
572
|
-
clearLease(delivery);
|
|
573
|
-
});
|
|
799
|
+
return this.#settlement.changeClaim({ lease, change: { kind: "bury", error, now } });
|
|
574
800
|
}
|
|
575
801
|
async listDeliveries(query) {
|
|
576
|
-
|
|
577
|
-
return [...this.#deliveries.values()].filter(
|
|
578
|
-
(delivery) => matchesQuery(delivery, { event: this.#events.get(delivery.eventId), query })
|
|
579
|
-
).sort(
|
|
580
|
-
(left, right) => left.createdAt.getTime() - right.createdAt.getTime() || compareIds(left.id, right.id)
|
|
581
|
-
).slice(0, limit).map((delivery) => ({
|
|
582
|
-
id: delivery.id,
|
|
583
|
-
handler: delivery.handler,
|
|
584
|
-
status: delivery.status,
|
|
585
|
-
attempts: delivery.attempts,
|
|
586
|
-
availableAt: delivery.availableAt,
|
|
587
|
-
leaseOwner: delivery.leaseOwner,
|
|
588
|
-
leaseExpiresAt: delivery.leaseExpiresAt,
|
|
589
|
-
lastError: delivery.lastError,
|
|
590
|
-
createdAt: delivery.createdAt,
|
|
591
|
-
updatedAt: delivery.updatedAt,
|
|
592
|
-
completedAt: delivery.completedAt,
|
|
593
|
-
event: this.#readEnvelope(delivery.eventId)
|
|
594
|
-
}));
|
|
802
|
+
return this.#maintenance.listDeliveries(query);
|
|
595
803
|
}
|
|
596
804
|
async requeue(deliveryIds, now) {
|
|
597
|
-
|
|
598
|
-
for (const deliveryId of new Set(deliveryIds)) {
|
|
599
|
-
const delivery = this.#deliveries.get(deliveryId);
|
|
600
|
-
if (delivery?.status !== "dead") {
|
|
601
|
-
continue;
|
|
602
|
-
}
|
|
603
|
-
delivery.status = "pending";
|
|
604
|
-
delivery.attempts = 0;
|
|
605
|
-
delivery.availableAt = now;
|
|
606
|
-
delivery.updatedAt = now;
|
|
607
|
-
moved += 1;
|
|
608
|
-
}
|
|
609
|
-
return moved;
|
|
805
|
+
return this.#maintenance.requeue(deliveryIds, now);
|
|
610
806
|
}
|
|
611
807
|
async purge(request) {
|
|
612
|
-
|
|
613
|
-
const touchedEventIds = /* @__PURE__ */ new Set();
|
|
614
|
-
for (const delivery of [...this.#deliveries.values()]) {
|
|
615
|
-
if (isPurgeable(delivery, request)) {
|
|
616
|
-
this.#deliveries.delete(delivery.id);
|
|
617
|
-
touchedEventIds.add(delivery.eventId);
|
|
618
|
-
deletedDeliveries += 1;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
return { deletedDeliveries, deletedEvents: this.#purgeEvents(request, touchedEventIds) };
|
|
622
|
-
}
|
|
623
|
-
/** Deletes the events no delivery references whose retention ran out; returns how many. */
|
|
624
|
-
#purgeEvents(request, touchedEventIds) {
|
|
625
|
-
const referencedEventIds = new Set(
|
|
626
|
-
[...this.#deliveries.values()].map((delivery) => delivery.eventId)
|
|
627
|
-
);
|
|
628
|
-
const { orphanCutoff, completedCutoff } = eventCutoffs(request);
|
|
629
|
-
let deletedEvents = 0;
|
|
630
|
-
for (const [eventId, event] of [...this.#events]) {
|
|
631
|
-
if (referencedEventIds.has(eventId) || this.#locks.has(`event:${eventId}`)) {
|
|
632
|
-
continue;
|
|
633
|
-
}
|
|
634
|
-
const expired = event.completedAt === null ? touchedEventIds.has(eventId) || event.createdAt.getTime() < orphanCutoff : event.completedAt.getTime() < completedCutoff;
|
|
635
|
-
if (expired) {
|
|
636
|
-
this.#events.delete(eventId);
|
|
637
|
-
deletedEvents += 1;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
return deletedEvents;
|
|
808
|
+
return this.#maintenance.purge(request);
|
|
641
809
|
}
|
|
642
810
|
async statistics(now) {
|
|
643
|
-
|
|
644
|
-
pending: 0,
|
|
645
|
-
processing: 0,
|
|
646
|
-
completed: 0,
|
|
647
|
-
dead: 0
|
|
648
|
-
};
|
|
649
|
-
let oldestDueAt = null;
|
|
650
|
-
for (const delivery of this.#deliveries.values()) {
|
|
651
|
-
counts[delivery.status] += 1;
|
|
652
|
-
if (isDue(delivery, now)) {
|
|
653
|
-
const dueAt = dueAtOf(delivery);
|
|
654
|
-
if (oldestDueAt === null || dueAt < oldestDueAt) {
|
|
655
|
-
oldestDueAt = dueAt;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
return { ...counts, oldestDueAt };
|
|
660
|
-
}
|
|
661
|
-
/**
|
|
662
|
-
* Waits for other open transactions holding any of `names`, then holds them
|
|
663
|
-
* until `transaction` finishes, the way a database holds row locks. Sorted
|
|
664
|
-
* acquisition keeps two transactions from waiting on each other.
|
|
665
|
-
*/
|
|
666
|
-
/** The events and deliveries the transaction appended so far, which are not applied before it commits. */
|
|
667
|
-
#written(transaction) {
|
|
668
|
-
let written = this.#writes.get(transaction);
|
|
669
|
-
if (written === void 0) {
|
|
670
|
-
written = { eventIds: /* @__PURE__ */ new Set(), deliveryKeys: /* @__PURE__ */ new Set() };
|
|
671
|
-
this.#writes.set(transaction, written);
|
|
672
|
-
}
|
|
673
|
-
return written;
|
|
674
|
-
}
|
|
675
|
-
async #lock(transaction, names) {
|
|
676
|
-
for (const name of [...new Set(names)].sort()) {
|
|
677
|
-
let held = this.#locks.get(name);
|
|
678
|
-
while (held !== void 0 && held.owner !== transaction) {
|
|
679
|
-
await held.released;
|
|
680
|
-
held = this.#locks.get(name);
|
|
681
|
-
}
|
|
682
|
-
if (held !== void 0) {
|
|
683
|
-
continue;
|
|
684
|
-
}
|
|
685
|
-
let release;
|
|
686
|
-
const released = new Promise((resolve) => {
|
|
687
|
-
release = resolve;
|
|
688
|
-
});
|
|
689
|
-
this.#locks.set(name, { owner: transaction, released });
|
|
690
|
-
transaction.onFinish(() => {
|
|
691
|
-
this.#locks.delete(name);
|
|
692
|
-
release();
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
#nextSequence(key) {
|
|
697
|
-
if (key === null) {
|
|
698
|
-
return null;
|
|
699
|
-
}
|
|
700
|
-
const next = (this.#keySequences.get(key) ?? 0) + 1;
|
|
701
|
-
this.#keySequences.set(key, next);
|
|
702
|
-
return next;
|
|
811
|
+
return this.#maintenance.statistics(now);
|
|
703
812
|
}
|
|
704
813
|
/**
|
|
705
814
|
* A keyed delivery waits while an earlier delivery of the same handler and
|
|
@@ -711,99 +820,188 @@ var InMemoryEventStore = class {
|
|
|
711
820
|
if (delivery.key === null) {
|
|
712
821
|
return false;
|
|
713
822
|
}
|
|
714
|
-
for (const other of this.#deliveries.values()) {
|
|
823
|
+
for (const other of this.#tables.deliveries.values()) {
|
|
715
824
|
if (blocksKey(other, { delivery, now })) {
|
|
716
825
|
return true;
|
|
717
826
|
}
|
|
718
827
|
}
|
|
719
828
|
return false;
|
|
720
829
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
// src/shutdown-deadline.ts
|
|
833
|
+
var DRAIN_SHARE = 0.8;
|
|
834
|
+
var ShutdownDeadline = class {
|
|
835
|
+
#controller = new AbortController();
|
|
836
|
+
#drained;
|
|
837
|
+
#expired;
|
|
838
|
+
#cancelTimers;
|
|
839
|
+
constructor(clock, timeoutMs) {
|
|
840
|
+
let drain;
|
|
841
|
+
this.#drained = new Promise((resolve) => {
|
|
842
|
+
drain = () => resolve("timeout");
|
|
843
|
+
});
|
|
844
|
+
const signal = this.#controller.signal;
|
|
845
|
+
this.#expired = new Promise((resolve) => {
|
|
846
|
+
signal.addEventListener("abort", () => resolve("timeout"), { once: true });
|
|
847
|
+
});
|
|
848
|
+
this.#cancelTimers = [
|
|
849
|
+
chunkJ76M7XTS_cjs.schedule(clock, { run: drain, delayMs: Math.floor(timeoutMs * DRAIN_SHARE) }),
|
|
850
|
+
chunkJ76M7XTS_cjs.schedule(clock, { run: () => this.#controller.abort(), delayMs: timeoutMs })
|
|
851
|
+
];
|
|
852
|
+
}
|
|
853
|
+
/** Aborted when the deadline passes. */
|
|
854
|
+
get signal() {
|
|
855
|
+
return this.#controller.signal;
|
|
856
|
+
}
|
|
857
|
+
get passed() {
|
|
858
|
+
return this.#controller.signal.aborted;
|
|
859
|
+
}
|
|
860
|
+
/** Waits for `work` until the deadline. Rejects when `work` rejects before it. */
|
|
861
|
+
race(work) {
|
|
862
|
+
return Promise.race([work.then(() => "done"), this.#expired]);
|
|
863
|
+
}
|
|
864
|
+
/** Waits for running handlers until the drain point, when they are aborted. */
|
|
865
|
+
raceDrain(work) {
|
|
866
|
+
return Promise.race([work.then(() => "done"), this.#drained]);
|
|
867
|
+
}
|
|
868
|
+
/** Cancels the timers once the shutdown is over. */
|
|
869
|
+
dispose() {
|
|
870
|
+
for (const cancel of this.#cancelTimers) {
|
|
871
|
+
cancel();
|
|
726
872
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
async function withinDeadline({ clock, timeoutMs }, work) {
|
|
876
|
+
const startedAt = clock.now().getTime();
|
|
877
|
+
const deadline = new ShutdownDeadline(clock, timeoutMs);
|
|
878
|
+
try {
|
|
879
|
+
const abandonedDeliveries = await work(deadline);
|
|
880
|
+
return {
|
|
881
|
+
graceful: !deadline.passed,
|
|
882
|
+
abandonedDeliveries,
|
|
883
|
+
durationMs: clock.now().getTime() - startedAt
|
|
884
|
+
};
|
|
885
|
+
} finally {
|
|
886
|
+
deadline.dispose();
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// src/bus/bus-lifecycle.ts
|
|
891
|
+
var BusLifecycle = class {
|
|
892
|
+
#parts;
|
|
893
|
+
#state = "idle";
|
|
894
|
+
#starting;
|
|
895
|
+
#stopping;
|
|
896
|
+
constructor(parts) {
|
|
897
|
+
this.#parts = parts;
|
|
898
|
+
}
|
|
899
|
+
get state() {
|
|
900
|
+
return this.#state;
|
|
901
|
+
}
|
|
902
|
+
/** Throws unless the bus is idle: a bus starts once. */
|
|
903
|
+
assertStartable() {
|
|
904
|
+
if (this.#state !== "idle") {
|
|
905
|
+
throw new chunkJ76M7XTS_cjs.LifecycleError(
|
|
906
|
+
this.#state === "stopped" ? "A stopped relaybus cannot be started again; create a new instance" : `Cannot start a relaybus that is ${this.#state}`
|
|
907
|
+
);
|
|
730
908
|
}
|
|
731
|
-
delivery.status = "completed";
|
|
732
|
-
delivery.completedAt = now;
|
|
733
|
-
delivery.updatedAt = now;
|
|
734
|
-
clearLease(delivery);
|
|
735
909
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
910
|
+
/** Runs `startComponents` as the start in progress, which a stop waits for; returns it. */
|
|
911
|
+
beginStart(startComponents) {
|
|
912
|
+
this.#state = "starting";
|
|
913
|
+
this.#starting = startComponents();
|
|
914
|
+
return this.#starting;
|
|
915
|
+
}
|
|
916
|
+
/** The start in progress settled. */
|
|
917
|
+
endStart() {
|
|
918
|
+
this.#starting = void 0;
|
|
919
|
+
}
|
|
920
|
+
markRunning() {
|
|
921
|
+
this.#state = "running";
|
|
922
|
+
}
|
|
923
|
+
markStopped() {
|
|
924
|
+
this.#state = "stopped";
|
|
925
|
+
}
|
|
926
|
+
/** The stop of the bus within `timeoutMs`; a later call shares the first one's. */
|
|
927
|
+
stop(timeoutMs) {
|
|
928
|
+
if (this.#stopping !== void 0) {
|
|
929
|
+
return { stopping: this.#stopping, first: false };
|
|
740
930
|
}
|
|
741
|
-
|
|
931
|
+
if (this.#state !== "running" && this.#state !== "starting") {
|
|
932
|
+
return { stopping: this.#stopOutsideRun(timeoutMs), first: false };
|
|
933
|
+
}
|
|
934
|
+
this.#stopping = this.#stopComponents(timeoutMs);
|
|
935
|
+
return { stopping: this.#stopping, first: true };
|
|
742
936
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
937
|
+
/**
|
|
938
|
+
* A bus that is not running: an idle one only settles what batches run by hand left behind (and
|
|
939
|
+
* stays startable); a stopped one has nothing left to do.
|
|
940
|
+
*/
|
|
941
|
+
async #stopOutsideRun(timeoutMs) {
|
|
942
|
+
if (this.#state !== "idle") {
|
|
943
|
+
return { graceful: true, abandonedDeliveries: 0, durationMs: 0 };
|
|
747
944
|
}
|
|
748
|
-
|
|
749
|
-
|
|
945
|
+
return withinDeadline(
|
|
946
|
+
{ clock: this.#parts.clock, timeoutMs },
|
|
947
|
+
(deadline) => this.#parts.dispatcher.stop(deadline)
|
|
948
|
+
);
|
|
750
949
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
950
|
+
/**
|
|
951
|
+
* Waits for a start in progress (a failed start leaves nothing to stop), then stops the dispatcher
|
|
952
|
+
* and closes the transports and the store. A start still running at the deadline is given up: the
|
|
953
|
+
* transports and the store are force-closed and the start rejects when it resumes.
|
|
954
|
+
*/
|
|
955
|
+
async #stopComponents(timeoutMs) {
|
|
956
|
+
try {
|
|
957
|
+
return await withinDeadline({ clock: this.#parts.clock, timeoutMs }, async (deadline) => {
|
|
958
|
+
if (this.#state === "starting") {
|
|
959
|
+
const started = await deadline.race(this.#starting.catch(() => void 0));
|
|
960
|
+
if (started === "timeout") {
|
|
961
|
+
this.#state = "stopping";
|
|
962
|
+
await this.#closeConnections(deadline);
|
|
963
|
+
return 0;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
if (this.#state !== "running") {
|
|
967
|
+
return 0;
|
|
968
|
+
}
|
|
969
|
+
this.#state = "stopping";
|
|
970
|
+
this.#parts.maintenance.stop();
|
|
971
|
+
const abandoned = await this.#parts.dispatcher.stop(deadline);
|
|
972
|
+
await this.#closeConnections(deadline);
|
|
973
|
+
return abandoned;
|
|
974
|
+
});
|
|
975
|
+
} finally {
|
|
976
|
+
this.#state = "stopped";
|
|
977
|
+
}
|
|
755
978
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
979
|
+
async #closeConnections(deadline) {
|
|
980
|
+
await this.#parts.closeTransports(deadline);
|
|
981
|
+
const options = { signal: deadline.signal };
|
|
982
|
+
await deadline.race(Promise.resolve(this.#parts.store.close?.(options)));
|
|
760
983
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
function eventCutoffs(request) {
|
|
766
|
-
const cutoffs = [request.completedBefore, request.deadBefore].filter((cutoff) => cutoff !== void 0).map((cutoff) => cutoff.getTime());
|
|
984
|
+
};
|
|
985
|
+
|
|
986
|
+
// src/bus/handler-descriptions.ts
|
|
987
|
+
function toDescriptor(registration, eventType) {
|
|
767
988
|
return {
|
|
768
|
-
|
|
769
|
-
|
|
989
|
+
id: registration.id,
|
|
990
|
+
eventType,
|
|
991
|
+
retry: registration.retry,
|
|
992
|
+
timeoutMs: registration.timeoutMs,
|
|
993
|
+
concurrency: registration.concurrency,
|
|
994
|
+
transactional: registration.transactional,
|
|
995
|
+
forwardsToTransport: registration.forwardsToTransport
|
|
770
996
|
};
|
|
771
997
|
}
|
|
772
|
-
function
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
if (delivery.status === "pending") {
|
|
777
|
-
return delivery.availableAt <= now;
|
|
778
|
-
}
|
|
779
|
-
return delivery.status === "processing" && delivery.leaseExpiresAt <= now;
|
|
780
|
-
}
|
|
781
|
-
function dueAtOf(delivery) {
|
|
782
|
-
return delivery.status === "pending" ? delivery.availableAt : delivery.leaseExpiresAt;
|
|
783
|
-
}
|
|
784
|
-
function compareDue(left, right) {
|
|
785
|
-
const byDueTime = dueAtOf(left).getTime() - dueAtOf(right).getTime();
|
|
786
|
-
return byDueTime === 0 ? compareIds(left.id, right.id) : byDueTime;
|
|
787
|
-
}
|
|
788
|
-
function compareIds(left, right) {
|
|
789
|
-
return Number(left > right) - Number(left < right);
|
|
790
|
-
}
|
|
791
|
-
function clearLease(delivery) {
|
|
792
|
-
delivery.leaseOwner = null;
|
|
793
|
-
delivery.leaseToken = null;
|
|
794
|
-
delivery.leaseExpiresAt = null;
|
|
795
|
-
}
|
|
796
|
-
function matchesQuery(delivery, { event, query }) {
|
|
797
|
-
return (query.status === void 0 || delivery.status === query.status) && (query.handler === void 0 || delivery.handler === query.handler) && (query.eventType === void 0 || event.envelope.type === query.eventType) && (query.eventId === void 0 || delivery.eventId === query.eventId);
|
|
798
|
-
}
|
|
799
|
-
function isPurgeable(delivery, request) {
|
|
800
|
-
if (delivery.status === "completed" && request.completedBefore !== void 0) {
|
|
801
|
-
return delivery.completedAt < request.completedBefore;
|
|
802
|
-
}
|
|
803
|
-
if (delivery.status === "dead" && request.deadBefore !== void 0) {
|
|
804
|
-
return delivery.updatedAt < request.deadBefore;
|
|
998
|
+
function describeEvent(event) {
|
|
999
|
+
if (typeof event === "string") {
|
|
1000
|
+
chunkJ76M7XTS_cjs.assertEventType(event);
|
|
1001
|
+
return { type: event, schema: void 0 };
|
|
805
1002
|
}
|
|
806
|
-
|
|
1003
|
+
chunkJ76M7XTS_cjs.assertEventType(event.type);
|
|
1004
|
+
return { type: event.type, schema: event.schema };
|
|
807
1005
|
}
|
|
808
1006
|
|
|
809
1007
|
// src/tracing.ts
|
|
@@ -861,7 +1059,7 @@ var BusObservation = class _BusObservation {
|
|
|
861
1059
|
eventTypes: input.eventTypes,
|
|
862
1060
|
durationMs: this.#now() - startedAt,
|
|
863
1061
|
outcome,
|
|
864
|
-
...error === void 0 ? {} : { error:
|
|
1062
|
+
...error === void 0 ? {} : { error: chunkJ76M7XTS_cjs.toErrorInfo(error) }
|
|
865
1063
|
});
|
|
866
1064
|
};
|
|
867
1065
|
try {
|
|
@@ -884,7 +1082,7 @@ var BusObservation = class _BusObservation {
|
|
|
884
1082
|
this.#reportReceive(input, { outcome, startedAt, failure: void 0 });
|
|
885
1083
|
return outcome;
|
|
886
1084
|
} catch (error) {
|
|
887
|
-
const outcome = error instanceof
|
|
1085
|
+
const outcome = error instanceof chunkJ76M7XTS_cjs.InvalidEnvelopeError ? "invalid" : "error";
|
|
888
1086
|
this.#reportReceive(input, { outcome, startedAt, failure: error });
|
|
889
1087
|
throw error;
|
|
890
1088
|
}
|
|
@@ -900,7 +1098,7 @@ var BusObservation = class _BusObservation {
|
|
|
900
1098
|
traceContext: isObject(headers) ? traceContextOf(headers) : void 0,
|
|
901
1099
|
outcome: report.outcome,
|
|
902
1100
|
durationMs: this.#now() - report.startedAt,
|
|
903
|
-
...report.failure === void 0 ? {} : { error:
|
|
1101
|
+
...report.failure === void 0 ? {} : { error: chunkJ76M7XTS_cjs.toErrorInfo(report.failure) }
|
|
904
1102
|
});
|
|
905
1103
|
}
|
|
906
1104
|
/** The handler of a route: sends inside the tracer's send span and reports each send. */
|
|
@@ -930,7 +1128,7 @@ var BusObservation = class _BusObservation {
|
|
|
930
1128
|
destination,
|
|
931
1129
|
durationMs: this.#now() - startedAt,
|
|
932
1130
|
outcome,
|
|
933
|
-
...error === void 0 ? {} : { error:
|
|
1131
|
+
...error === void 0 ? {} : { error: chunkJ76M7XTS_cjs.toErrorInfo(error) }
|
|
934
1132
|
});
|
|
935
1133
|
};
|
|
936
1134
|
try {
|
|
@@ -950,7 +1148,7 @@ var BusObservation = class _BusObservation {
|
|
|
950
1148
|
return void 0;
|
|
951
1149
|
}
|
|
952
1150
|
let reading = false;
|
|
953
|
-
return
|
|
1151
|
+
return chunkJ76M7XTS_cjs.schedule(this.#clock, {
|
|
954
1152
|
run: () => {
|
|
955
1153
|
if (reading) {
|
|
956
1154
|
return;
|
|
@@ -1030,7 +1228,7 @@ var DispatchObservation = class _DispatchObservation {
|
|
|
1030
1228
|
lanes: 0,
|
|
1031
1229
|
durationMs: this.#clock.now().getTime() - startedAt,
|
|
1032
1230
|
outcome: "error",
|
|
1033
|
-
error:
|
|
1231
|
+
error: chunkJ76M7XTS_cjs.toErrorInfo(error)
|
|
1034
1232
|
});
|
|
1035
1233
|
throw error;
|
|
1036
1234
|
}
|
|
@@ -1061,7 +1259,7 @@ var DispatchObservation = class _DispatchObservation {
|
|
|
1061
1259
|
await settled;
|
|
1062
1260
|
} catch (error) {
|
|
1063
1261
|
const durationMs2 = this.#clock.now().getTime() - startedAt;
|
|
1064
|
-
const errorInfo =
|
|
1262
|
+
const errorInfo = chunkJ76M7XTS_cjs.toErrorInfo(error);
|
|
1065
1263
|
this.#notifier.notify("onHandlerFinished", {
|
|
1066
1264
|
delivery,
|
|
1067
1265
|
kind,
|
|
@@ -1076,226 +1274,451 @@ var DispatchObservation = class _DispatchObservation {
|
|
|
1076
1274
|
}
|
|
1077
1275
|
};
|
|
1078
1276
|
|
|
1079
|
-
// src/
|
|
1080
|
-
var
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
#
|
|
1085
|
-
#
|
|
1086
|
-
#
|
|
1087
|
-
|
|
1277
|
+
// src/dispatch/claim-loop.ts
|
|
1278
|
+
var WAKE_COOLDOWN_MS = 5;
|
|
1279
|
+
var QUIET_REFILL_MS = 10;
|
|
1280
|
+
var REFILL_FRACTION = 0.7;
|
|
1281
|
+
var ClaimLoop = class {
|
|
1282
|
+
#parts;
|
|
1283
|
+
#running = false;
|
|
1284
|
+
#wakeUp;
|
|
1285
|
+
/** Free slots the claim the loop was woken for needs at least; undefined while nobody woke it. */
|
|
1286
|
+
#signalledMinimum;
|
|
1287
|
+
/** Free slots a claim needs unless the poll interval or the quiet refill time passed: claims refill in groups. */
|
|
1288
|
+
#refillThreshold;
|
|
1289
|
+
/** Handlers whose last claim got less than it asked for: the store has nothing more for them right now. */
|
|
1290
|
+
#exhaustedHandlers = /* @__PURE__ */ new Set();
|
|
1291
|
+
/** Grows with every wake-up: a claim that started before one must not mark a handler exhausted. */
|
|
1292
|
+
#wakeGeneration = 0;
|
|
1293
|
+
#lastClaimStartedAt = Number.NEGATIVE_INFINITY;
|
|
1294
|
+
#cooldownTimer;
|
|
1295
|
+
#refillScheduled = false;
|
|
1296
|
+
#quietRefillTimer;
|
|
1297
|
+
constructor(parts) {
|
|
1298
|
+
this.#parts = parts;
|
|
1299
|
+
const { concurrency, batchSize } = parts.dependencies.settings;
|
|
1300
|
+
this.#refillThreshold = Math.ceil(Math.min(concurrency, batchSize) * REFILL_FRACTION);
|
|
1301
|
+
}
|
|
1302
|
+
get isRunning() {
|
|
1303
|
+
return this.#running;
|
|
1304
|
+
}
|
|
1305
|
+
/** Marks the loop running; {@link ClaimLoop.run} then claims until {@link ClaimLoop.halt}. */
|
|
1306
|
+
open() {
|
|
1307
|
+
this.#running = true;
|
|
1308
|
+
}
|
|
1309
|
+
/** Stops claiming: the running pass ends at its next wait. */
|
|
1310
|
+
halt() {
|
|
1311
|
+
this.#running = false;
|
|
1312
|
+
}
|
|
1313
|
+
/** Cancels the claims waiting for a timer and ends the current wait, so a halted loop returns. */
|
|
1314
|
+
interrupt() {
|
|
1315
|
+
this.#cooldownTimer?.();
|
|
1316
|
+
this.#cooldownTimer = void 0;
|
|
1317
|
+
this.#quietRefillTimer?.();
|
|
1318
|
+
this.#quietRefillTimer = void 0;
|
|
1319
|
+
this.#signal(this.#refillThreshold);
|
|
1320
|
+
}
|
|
1088
1321
|
/**
|
|
1089
|
-
*
|
|
1090
|
-
*
|
|
1322
|
+
* Asks for a claim soon, for example right after a publish: at once after a quiet period, otherwise
|
|
1323
|
+
* at the end of a short cooldown that merges the wake-ups arriving meanwhile into one claim.
|
|
1091
1324
|
*/
|
|
1092
|
-
|
|
1093
|
-
this.#
|
|
1094
|
-
this.#
|
|
1095
|
-
this.#
|
|
1096
|
-
|
|
1325
|
+
wake() {
|
|
1326
|
+
this.#exhaustedHandlers.clear();
|
|
1327
|
+
this.#wakeGeneration += 1;
|
|
1328
|
+
const wait = this.#lastClaimStartedAt + WAKE_COOLDOWN_MS - Date.now();
|
|
1329
|
+
if (wait <= 0) {
|
|
1330
|
+
this.#requestClaim();
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
this.#cooldownTimer ??= chunkJ76M7XTS_cjs.schedule(this.#parts.dependencies.clock, {
|
|
1334
|
+
run: () => {
|
|
1335
|
+
this.#cooldownTimer = void 0;
|
|
1336
|
+
this.#requestClaim();
|
|
1337
|
+
},
|
|
1338
|
+
delayMs: wait
|
|
1339
|
+
});
|
|
1097
1340
|
}
|
|
1098
1341
|
/**
|
|
1099
|
-
*
|
|
1100
|
-
*
|
|
1342
|
+
* Claims until halted. A claim that filled its batch is followed by the next one at once while enough
|
|
1343
|
+
* slots are free. Otherwise the loop sleeps until a wake-up (a publish, a store notification, or enough
|
|
1344
|
+
* deliveries settling to refill a group of slots) or the poll interval. Only the poll interval claims
|
|
1345
|
+
* for fewer free slots than the refill threshold, so a busy dispatcher never claims slot by slot.
|
|
1101
1346
|
*/
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
}
|
|
1347
|
+
async run() {
|
|
1348
|
+
let minimumFree = 1;
|
|
1349
|
+
while (this.#running) {
|
|
1350
|
+
let round = { requested: 0, claimed: 0 };
|
|
1351
|
+
if (this.#freeSlots() >= minimumFree) {
|
|
1352
|
+
try {
|
|
1353
|
+
round = await this.claimAndStart();
|
|
1354
|
+
} catch (error) {
|
|
1355
|
+
this.#parts.dependencies.notifier.failure("claim", error);
|
|
1356
|
+
}
|
|
1112
1357
|
}
|
|
1113
|
-
|
|
1358
|
+
if (round.requested > 0 && round.claimed === round.requested && this.#freeSlots() >= this.#refillThreshold) {
|
|
1359
|
+
minimumFree = this.#refillThreshold;
|
|
1360
|
+
continue;
|
|
1361
|
+
}
|
|
1362
|
+
minimumFree = await this.#waitForWakeUp();
|
|
1363
|
+
if (this.#freeSlots() < minimumFree) {
|
|
1364
|
+
this.#requestClaim();
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1114
1367
|
}
|
|
1115
1368
|
/**
|
|
1116
|
-
*
|
|
1117
|
-
*
|
|
1369
|
+
* A delivery settled. Its slot is worth refilling when the store may have more for its handler: a keyed
|
|
1370
|
+
* delivery unblocks the next one of its key, a delivery that did not succeed may be due again at once
|
|
1371
|
+
* (a retry without delay, a release), and a handler whose last claim was full may have a backlog.
|
|
1118
1372
|
*/
|
|
1119
|
-
|
|
1120
|
-
this.#
|
|
1121
|
-
|
|
1122
|
-
this.#flush();
|
|
1373
|
+
onSettled(delivery, succeeded) {
|
|
1374
|
+
if (delivery.event.key !== null || !succeeded || !this.#exhaustedHandlers.has(delivery.handler)) {
|
|
1375
|
+
this.#requestClaim();
|
|
1123
1376
|
}
|
|
1124
1377
|
}
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
const
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
)
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1378
|
+
/** Claims for every handler with room and starts what was claimed. */
|
|
1379
|
+
async claimAndStart() {
|
|
1380
|
+
const { dependencies, planner, shutdown, start } = this.#parts;
|
|
1381
|
+
const { store, registry, clock, settings, observation } = dependencies;
|
|
1382
|
+
const started = [];
|
|
1383
|
+
let requested = 0;
|
|
1384
|
+
for (const request of planner.plan(registry.all(), this.#freeSlots())) {
|
|
1385
|
+
const now = clock.now();
|
|
1386
|
+
const wakeGeneration = this.#wakeGeneration;
|
|
1387
|
+
this.#lastClaimStartedAt = Date.now();
|
|
1388
|
+
this.#quietRefillTimer?.();
|
|
1389
|
+
this.#quietRefillTimer = void 0;
|
|
1390
|
+
requested += request.limit;
|
|
1391
|
+
const claim = {
|
|
1392
|
+
handlers: request.handlers,
|
|
1393
|
+
limit: request.limit,
|
|
1394
|
+
leaseOwner: settings.owner,
|
|
1395
|
+
leaseExpiresAt: new Date(now.getTime() + settings.leaseDurationMs),
|
|
1396
|
+
now
|
|
1397
|
+
};
|
|
1398
|
+
const claimed = await (observation === void 0 ? store.claim(claim) : observation.claim(store, claim));
|
|
1399
|
+
this.#markExhausted(
|
|
1400
|
+
request.handlers,
|
|
1401
|
+
claimed.length < request.limit && wakeGeneration === this.#wakeGeneration
|
|
1402
|
+
);
|
|
1403
|
+
for (const delivery of shutdown.abandoned ? [] : claimed) {
|
|
1404
|
+
started.push(start(delivery));
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
return { requested, claimed: started.length, started };
|
|
1139
1408
|
}
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
this.#clock = clock;
|
|
1409
|
+
#markExhausted(handlers, exhausted) {
|
|
1410
|
+
for (const handler of handlers) {
|
|
1411
|
+
if (exhausted) {
|
|
1412
|
+
this.#exhaustedHandlers.add(handler);
|
|
1413
|
+
} else {
|
|
1414
|
+
this.#exhaustedHandlers.delete(handler);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1149
1417
|
}
|
|
1150
|
-
/**
|
|
1151
|
-
|
|
1152
|
-
|
|
1418
|
+
/** Ends the current wait of the claim loop, or the next one at once, for a claim once `minimumFree` slots are free. */
|
|
1419
|
+
#signal(minimumFree) {
|
|
1420
|
+
this.#signalledMinimum = Math.min(this.#signalledMinimum ?? minimumFree, minimumFree);
|
|
1421
|
+
this.#wakeUp?.();
|
|
1153
1422
|
}
|
|
1154
|
-
/**
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1423
|
+
/** Waits for a wake-up or the poll interval. Resolves with the free slots the next claim needs. */
|
|
1424
|
+
async #waitForWakeUp() {
|
|
1425
|
+
if (this.#signalledMinimum === void 0) {
|
|
1426
|
+
await new Promise((resolve) => {
|
|
1427
|
+
const cancel = chunkJ76M7XTS_cjs.schedule(this.#parts.dependencies.clock, {
|
|
1428
|
+
run: () => {
|
|
1429
|
+
this.#signalledMinimum = 1;
|
|
1430
|
+
resolve();
|
|
1431
|
+
},
|
|
1432
|
+
delayMs: this.#parts.dependencies.settings.pollIntervalMs
|
|
1433
|
+
});
|
|
1434
|
+
this.#wakeUp = () => {
|
|
1435
|
+
cancel();
|
|
1436
|
+
resolve();
|
|
1437
|
+
};
|
|
1438
|
+
});
|
|
1439
|
+
this.#wakeUp = void 0;
|
|
1440
|
+
}
|
|
1441
|
+
const minimumFree = this.#signalledMinimum;
|
|
1442
|
+
this.#signalledMinimum = void 0;
|
|
1443
|
+
return minimumFree;
|
|
1159
1444
|
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1445
|
+
/**
|
|
1446
|
+
* Asks the loop for a claim. Once a group of slots is free the claim waits only for the deliveries
|
|
1447
|
+
* settling in the same tick, so they are claimed for together; fewer free slots are refilled when no
|
|
1448
|
+
* claim started for a short while. With no free slot the next settlement asks again.
|
|
1449
|
+
*/
|
|
1450
|
+
#requestClaim() {
|
|
1451
|
+
const free = this.#freeSlots();
|
|
1452
|
+
if (free >= this.#refillThreshold) {
|
|
1453
|
+
if (!this.#refillScheduled) {
|
|
1454
|
+
this.#refillScheduled = true;
|
|
1455
|
+
setImmediate(() => {
|
|
1456
|
+
this.#refillScheduled = false;
|
|
1457
|
+
this.#signal(this.#refillThreshold);
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
} else if (free > 0) {
|
|
1461
|
+
this.#quietRefillTimer ??= chunkJ76M7XTS_cjs.schedule(this.#parts.dependencies.clock, {
|
|
1462
|
+
run: () => {
|
|
1463
|
+
this.#quietRefillTimer = void 0;
|
|
1464
|
+
this.#signal(1);
|
|
1465
|
+
},
|
|
1466
|
+
delayMs: Math.max(0, this.#lastClaimStartedAt + QUIET_REFILL_MS - Date.now())
|
|
1467
|
+
});
|
|
1164
1468
|
}
|
|
1165
1469
|
}
|
|
1166
|
-
#
|
|
1167
|
-
|
|
1168
|
-
cancelTimer();
|
|
1169
|
-
this.#waiting.delete(cancel);
|
|
1170
|
-
};
|
|
1171
|
-
const cancelTimer = chunk3OHMEMZA_cjs.schedule(this.#clock, {
|
|
1172
|
-
run: () => {
|
|
1173
|
-
this.#waiting.delete(cancel);
|
|
1174
|
-
task.run();
|
|
1175
|
-
},
|
|
1176
|
-
delayMs: task.delayMs
|
|
1177
|
-
});
|
|
1178
|
-
this.#waiting.set(cancel, onCancel);
|
|
1179
|
-
return cancel;
|
|
1470
|
+
#freeSlots() {
|
|
1471
|
+
return this.#parts.dependencies.settings.concurrency - this.#parts.inFlight.size;
|
|
1180
1472
|
}
|
|
1181
1473
|
};
|
|
1182
1474
|
|
|
1183
|
-
// src/
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
});
|
|
1191
|
-
}
|
|
1192
|
-
var storage = new async_hooks.AsyncLocalStorage();
|
|
1193
|
-
function runInScope(scope, work) {
|
|
1194
|
-
return storage.run({ ...storage.getStore(), ...scope }, work);
|
|
1195
|
-
}
|
|
1196
|
-
function runInIsolatedScope(scope, work) {
|
|
1197
|
-
return storage.run(scope, work);
|
|
1198
|
-
}
|
|
1199
|
-
function getCurrentScope() {
|
|
1200
|
-
return storage.getStore();
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
// src/shutdown-deadline.ts
|
|
1204
|
-
var DRAIN_SHARE = 0.8;
|
|
1205
|
-
var ShutdownDeadline = class {
|
|
1206
|
-
#controller = new AbortController();
|
|
1207
|
-
#drained;
|
|
1208
|
-
#expired;
|
|
1209
|
-
#cancelTimers;
|
|
1210
|
-
constructor(clock, timeoutMs) {
|
|
1211
|
-
let drain;
|
|
1212
|
-
this.#drained = new Promise((resolve) => {
|
|
1213
|
-
drain = () => resolve("timeout");
|
|
1214
|
-
});
|
|
1215
|
-
const signal = this.#controller.signal;
|
|
1216
|
-
this.#expired = new Promise((resolve) => {
|
|
1217
|
-
signal.addEventListener("abort", () => resolve("timeout"), { once: true });
|
|
1218
|
-
});
|
|
1219
|
-
this.#cancelTimers = [
|
|
1220
|
-
chunk3OHMEMZA_cjs.schedule(clock, { run: drain, delayMs: Math.floor(timeoutMs * DRAIN_SHARE) }),
|
|
1221
|
-
chunk3OHMEMZA_cjs.schedule(clock, { run: () => this.#controller.abort(), delayMs: timeoutMs })
|
|
1222
|
-
];
|
|
1223
|
-
}
|
|
1224
|
-
/** Aborted when the deadline passes. */
|
|
1225
|
-
get signal() {
|
|
1226
|
-
return this.#controller.signal;
|
|
1227
|
-
}
|
|
1228
|
-
get passed() {
|
|
1229
|
-
return this.#controller.signal.aborted;
|
|
1230
|
-
}
|
|
1231
|
-
/** Waits for `work` until the deadline. Rejects when `work` rejects before it. */
|
|
1232
|
-
race(work) {
|
|
1233
|
-
return Promise.race([work.then(() => "done"), this.#expired]);
|
|
1234
|
-
}
|
|
1235
|
-
/** Waits for running handlers until the drain point, when they are aborted. */
|
|
1236
|
-
raceDrain(work) {
|
|
1237
|
-
return Promise.race([work.then(() => "done"), this.#drained]);
|
|
1475
|
+
// src/dispatch/claim-planner.ts
|
|
1476
|
+
var ClaimPlanner = class {
|
|
1477
|
+
#inFlight;
|
|
1478
|
+
#batchSize;
|
|
1479
|
+
constructor(inFlight, batchSize) {
|
|
1480
|
+
this.#inFlight = inFlight;
|
|
1481
|
+
this.#batchSize = batchSize;
|
|
1238
1482
|
}
|
|
1239
|
-
/**
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1483
|
+
/** One claim for handlers without a concurrency limit, one per limited handler with room. */
|
|
1484
|
+
plan(registrations, freeSlots) {
|
|
1485
|
+
let free = Math.min(freeSlots, this.#batchSize);
|
|
1486
|
+
const plans = [];
|
|
1487
|
+
const unlimited = registrations.filter((registration) => registration.concurrency === null);
|
|
1488
|
+
for (const registration of registrations) {
|
|
1489
|
+
if (registration.concurrency === null || free === 0) {
|
|
1490
|
+
continue;
|
|
1491
|
+
}
|
|
1492
|
+
const room = registration.concurrency - this.#inFlight.countOf(registration);
|
|
1493
|
+
const limit = Math.min(room, free);
|
|
1494
|
+
if (limit > 0) {
|
|
1495
|
+
plans.push({ handlers: [...registration.claimIds], limit });
|
|
1496
|
+
free -= limit;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
if (unlimited.length > 0 && free > 0) {
|
|
1500
|
+
plans.push({
|
|
1501
|
+
handlers: unlimited.flatMap((registration) => registration.claimIds),
|
|
1502
|
+
limit: free
|
|
1503
|
+
});
|
|
1243
1504
|
}
|
|
1505
|
+
return plans;
|
|
1244
1506
|
}
|
|
1245
1507
|
};
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
return
|
|
1252
|
-
graceful: !deadline.passed,
|
|
1253
|
-
abandonedDeliveries,
|
|
1254
|
-
durationMs: clock.now().getTime() - startedAt
|
|
1255
|
-
};
|
|
1256
|
-
} finally {
|
|
1257
|
-
deadline.dispose();
|
|
1508
|
+
|
|
1509
|
+
// src/dispatch/dispatch-context.ts
|
|
1510
|
+
var ShutdownLatch = class {
|
|
1511
|
+
#abandoned = false;
|
|
1512
|
+
get abandoned() {
|
|
1513
|
+
return this.#abandoned;
|
|
1258
1514
|
}
|
|
1259
|
-
|
|
1515
|
+
abandon() {
|
|
1516
|
+
this.#abandoned = true;
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1260
1519
|
|
|
1261
1520
|
// src/lease-heartbeat.ts
|
|
1262
1521
|
var RENEWALS_PER_LEASE = 3;
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1522
|
+
var LeaseRenewal = class {
|
|
1523
|
+
#dependencies;
|
|
1524
|
+
#target;
|
|
1525
|
+
#confirmedExpiry;
|
|
1526
|
+
#renewing = false;
|
|
1527
|
+
constructor(dependencies, target) {
|
|
1528
|
+
this.#dependencies = dependencies;
|
|
1529
|
+
this.#target = target;
|
|
1530
|
+
this.#confirmedExpiry = target.delivery.leaseExpiresAt.getTime();
|
|
1531
|
+
}
|
|
1532
|
+
/** One beat: aborts once the confirmed expiry passed, else renews unless a renewal is running. */
|
|
1533
|
+
beat() {
|
|
1534
|
+
const { store, clock, notifier, settings } = this.#dependencies;
|
|
1535
|
+
const now = clock.now();
|
|
1536
|
+
if (now.getTime() >= this.#confirmedExpiry) {
|
|
1537
|
+
this.#abortLeaseLost();
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
if (this.#renewing) {
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
this.#renewing = true;
|
|
1544
|
+
const expiry = now.getTime() + settings.leaseDurationMs;
|
|
1545
|
+
store.extendLease(this.#target.delivery, new Date(expiry), now).then((extended) => {
|
|
1546
|
+
if (extended) {
|
|
1547
|
+
this.#confirmedExpiry = expiry;
|
|
1548
|
+
} else {
|
|
1549
|
+
this.#abortLeaseLost(true);
|
|
1550
|
+
}
|
|
1551
|
+
}).catch((error) => notifier.failure("extend-lease", error)).finally(() => {
|
|
1552
|
+
this.#renewing = false;
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
#abortLeaseLost(storeConfirmed = false) {
|
|
1556
|
+
const { delivery, controller } = this.#target;
|
|
1267
1557
|
controller.abort(
|
|
1268
|
-
new
|
|
1558
|
+
new chunkJ76M7XTS_cjs.LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost`, { storeConfirmed })
|
|
1269
1559
|
);
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
}
|
|
1278
|
-
if (renewing) {
|
|
1279
|
-
return;
|
|
1280
|
-
}
|
|
1281
|
-
renewing = true;
|
|
1282
|
-
const expiry = now.getTime() + settings.leaseDurationMs;
|
|
1283
|
-
store.extendLease(delivery, new Date(expiry), now).then((extended) => {
|
|
1284
|
-
if (extended) {
|
|
1285
|
-
confirmedExpiry = expiry;
|
|
1286
|
-
} else {
|
|
1287
|
-
abortLeaseLost(true);
|
|
1288
|
-
}
|
|
1289
|
-
}).catch((error) => notifier.failure("extend-lease", error)).finally(() => {
|
|
1290
|
-
renewing = false;
|
|
1291
|
-
});
|
|
1292
|
-
},
|
|
1560
|
+
}
|
|
1561
|
+
};
|
|
1562
|
+
function startLeaseHeartbeat(dependencies, target) {
|
|
1563
|
+
const renewal = new LeaseRenewal(dependencies, target);
|
|
1564
|
+
const { clock, settings } = dependencies;
|
|
1565
|
+
return chunkJ76M7XTS_cjs.schedule(clock, {
|
|
1566
|
+
run: () => renewal.beat(),
|
|
1293
1567
|
delayMs: Math.max(1, Math.floor(settings.leaseDurationMs / RENEWALS_PER_LEASE)),
|
|
1294
1568
|
repeat: true,
|
|
1295
1569
|
unref: true
|
|
1296
1570
|
});
|
|
1297
1571
|
}
|
|
1298
1572
|
|
|
1573
|
+
// src/dispatch/delivery-executor.ts
|
|
1574
|
+
var DeliveryExecutor = class {
|
|
1575
|
+
#parts;
|
|
1576
|
+
/** Stops the lease renewal (or transactional lease guard) of each delivery whose handler runs. */
|
|
1577
|
+
#leaseKeepers = /* @__PURE__ */ new Map();
|
|
1578
|
+
constructor(parts) {
|
|
1579
|
+
this.#parts = parts;
|
|
1580
|
+
}
|
|
1581
|
+
/** Starts a delivery and tracks it as in flight until it settled. */
|
|
1582
|
+
start(delivery) {
|
|
1583
|
+
const { dependencies, inFlight, onSettled } = this.#parts;
|
|
1584
|
+
const controller = new AbortController();
|
|
1585
|
+
let succeeded = false;
|
|
1586
|
+
const entry = {
|
|
1587
|
+
handler: delivery.handler,
|
|
1588
|
+
controller,
|
|
1589
|
+
settled: Promise.resolve().then(async () => {
|
|
1590
|
+
succeeded = await this.#execute(delivery, controller);
|
|
1591
|
+
}).catch((error) => dependencies.notifier.failure("dispatch", error)).finally(() => {
|
|
1592
|
+
inFlight.delete(entry);
|
|
1593
|
+
onSettled(delivery, succeeded);
|
|
1594
|
+
})
|
|
1595
|
+
};
|
|
1596
|
+
inFlight.add(entry);
|
|
1597
|
+
return entry;
|
|
1598
|
+
}
|
|
1599
|
+
/** Stop gave up: lets every lease run out, so that another process takes the deliveries. */
|
|
1600
|
+
abandon() {
|
|
1601
|
+
for (const stopKeeping of this.#leaseKeepers.values()) {
|
|
1602
|
+
stopKeeping();
|
|
1603
|
+
}
|
|
1604
|
+
this.#leaseKeepers.clear();
|
|
1605
|
+
}
|
|
1606
|
+
/** Runs one delivery. Resolves true when its handler succeeded, so the delivery is done for good. */
|
|
1607
|
+
async #execute(delivery, controller) {
|
|
1608
|
+
const { dependencies, recovery, outcomes } = this.#parts;
|
|
1609
|
+
const { registry, notifier, clock } = dependencies;
|
|
1610
|
+
const registration = registry.get(delivery.handler);
|
|
1611
|
+
recovery.forgetEarlierClaims(delivery);
|
|
1612
|
+
notifier.notify("onDeliveryStarted", { delivery });
|
|
1613
|
+
const startedAt = clock.now().getTime();
|
|
1614
|
+
if (delivery.attempt > registration.retry.attempts) {
|
|
1615
|
+
await outcomes.bury(
|
|
1616
|
+
delivery,
|
|
1617
|
+
new chunkJ76M7XTS_cjs.AttemptsExhaustedError(
|
|
1618
|
+
`Delivery reached attempt ${delivery.attempt} of ${registration.retry.attempts} after its lease expired`
|
|
1619
|
+
)
|
|
1620
|
+
);
|
|
1621
|
+
return false;
|
|
1622
|
+
}
|
|
1623
|
+
let stopHeartbeat = () => void 0;
|
|
1624
|
+
try {
|
|
1625
|
+
stopHeartbeat = registration.transactional ? await this.#guardTransactionalLease(registration, { delivery, controller }) : startLeaseHeartbeat(dependencies, { delivery, controller });
|
|
1626
|
+
this.#leaseKeepers.set(delivery, stopHeartbeat);
|
|
1627
|
+
const outcome = await this.#runHandler(registration, { delivery, controller });
|
|
1628
|
+
this.#reportSuccess(delivery, { outcome, durationMs: clock.now().getTime() - startedAt });
|
|
1629
|
+
return true;
|
|
1630
|
+
} catch (error) {
|
|
1631
|
+
if (this.#parts.shutdown.abandoned) {
|
|
1632
|
+
return false;
|
|
1633
|
+
}
|
|
1634
|
+
const recording = this.#recordFailure(registration, { delivery, error });
|
|
1635
|
+
if (recording !== void 0) {
|
|
1636
|
+
await recording;
|
|
1637
|
+
}
|
|
1638
|
+
return false;
|
|
1639
|
+
} finally {
|
|
1640
|
+
stopHeartbeat();
|
|
1641
|
+
this.#leaseKeepers.delete(delivery);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
/** Reports a success now, or once a completion the store did not confirm is recovered. */
|
|
1645
|
+
#reportSuccess(delivery, { outcome, durationMs }) {
|
|
1646
|
+
const { dependencies, outcomes, recovery } = this.#parts;
|
|
1647
|
+
const { notifier } = dependencies;
|
|
1648
|
+
if (outcome === "completed") {
|
|
1649
|
+
notifier.notify("onDeliverySucceeded", { delivery, durationMs });
|
|
1650
|
+
} else {
|
|
1651
|
+
recovery.retain({
|
|
1652
|
+
delivery,
|
|
1653
|
+
settle: () => outcomes.complete(delivery),
|
|
1654
|
+
applied: () => notifier.notify("onDeliverySucceeded", { delivery, durationMs })
|
|
1655
|
+
});
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
/** Records a failed run; undefined when there is nothing to record (the store confirmed the lost lease). */
|
|
1659
|
+
#recordFailure(registration, { delivery, error }) {
|
|
1660
|
+
const { dependencies, outcomes } = this.#parts;
|
|
1661
|
+
if (error instanceof chunkJ76M7XTS_cjs.LeaseLostError) {
|
|
1662
|
+
dependencies.notifier.notify("onLeaseLost", { delivery });
|
|
1663
|
+
return error.storeConfirmed ? void 0 : outcomes.giveBack(registration, { delivery, error });
|
|
1664
|
+
}
|
|
1665
|
+
if (error instanceof chunkJ76M7XTS_cjs.DispatcherStoppedError) {
|
|
1666
|
+
return outcomes.release(delivery);
|
|
1667
|
+
}
|
|
1668
|
+
return outcomes.fail(registration, { delivery, error });
|
|
1669
|
+
}
|
|
1670
|
+
async #runHandler(registration, { delivery, controller }) {
|
|
1671
|
+
const { dependencies, invocation, outcomes } = this.#parts;
|
|
1672
|
+
const { store, clock } = dependencies;
|
|
1673
|
+
const payload = await chunkJ76M7XTS_cjs.validatePayload(
|
|
1674
|
+
delivery.event.type,
|
|
1675
|
+
registration.schema,
|
|
1676
|
+
delivery.event.payload
|
|
1677
|
+
);
|
|
1678
|
+
const event = { ...delivery.event, payload };
|
|
1679
|
+
if (registration.transactional) {
|
|
1680
|
+
await store.transaction(async (transaction) => {
|
|
1681
|
+
await invocation.invoke({ registration, event, delivery, controller, transaction });
|
|
1682
|
+
if (!await store.complete(delivery, clock.now(), transaction)) {
|
|
1683
|
+
throw new chunkJ76M7XTS_cjs.LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost`, {
|
|
1684
|
+
storeConfirmed: true
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1687
|
+
});
|
|
1688
|
+
return "completed";
|
|
1689
|
+
}
|
|
1690
|
+
await invocation.invoke({ registration, event, delivery, controller, transaction: void 0 });
|
|
1691
|
+
return outcomes.confirmCompletion(delivery);
|
|
1692
|
+
}
|
|
1693
|
+
/**
|
|
1694
|
+
* Transactional handlers get no renewals: a renewal would write the delivery
|
|
1695
|
+
* the open transaction completes, which databases with snapshot isolation
|
|
1696
|
+
* (MongoDB) turn into a write conflict that reruns the handler. Instead the
|
|
1697
|
+
* lease is extended once to cover the handler timeout, and the handler is
|
|
1698
|
+
* aborted when that lease ends.
|
|
1699
|
+
*/
|
|
1700
|
+
async #guardTransactionalLease(registration, { delivery, controller }) {
|
|
1701
|
+
const { store, clock, settings } = this.#parts.dependencies;
|
|
1702
|
+
const now = clock.now();
|
|
1703
|
+
let expiry = delivery.leaseExpiresAt.getTime();
|
|
1704
|
+
if (registration.timeoutMs !== null && now.getTime() + registration.timeoutMs >= expiry) {
|
|
1705
|
+
expiry = now.getTime() + registration.timeoutMs + settings.leaseDurationMs;
|
|
1706
|
+
if (!await store.extendLease(delivery, new Date(expiry), now)) {
|
|
1707
|
+
throw new chunkJ76M7XTS_cjs.LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost`, {
|
|
1708
|
+
storeConfirmed: true
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
return chunkJ76M7XTS_cjs.schedule(clock, {
|
|
1713
|
+
run: () => {
|
|
1714
|
+
controller.abort(new chunkJ76M7XTS_cjs.LeaseLostError(`Lease on delivery ${delivery.deliveryId} expired`));
|
|
1715
|
+
},
|
|
1716
|
+
delayMs: Math.max(0, expiry - clock.now().getTime()),
|
|
1717
|
+
unref: true
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
};
|
|
1721
|
+
|
|
1299
1722
|
// src/retry-policy.ts
|
|
1300
1723
|
var DEFAULT_RETRY_POLICY = {
|
|
1301
1724
|
attempts: 10,
|
|
@@ -1321,7 +1744,7 @@ function createRetryPolicy(options, base = DEFAULT_RETRY_POLICY) {
|
|
|
1321
1744
|
}
|
|
1322
1745
|
function mergeRetryPolicy(options, base) {
|
|
1323
1746
|
const overrides = options ?? {};
|
|
1324
|
-
const initialDelayMs = overrides.initialDelay === void 0 ? base.initialDelayMs :
|
|
1747
|
+
const initialDelayMs = overrides.initialDelay === void 0 ? base.initialDelayMs : chunkJ76M7XTS_cjs.Milliseconds.of(overrides.initialDelay, "retry.initialDelay");
|
|
1325
1748
|
return {
|
|
1326
1749
|
attempts: overrides.attempts ?? base.attempts,
|
|
1327
1750
|
initialDelayMs,
|
|
@@ -1329,25 +1752,25 @@ function mergeRetryPolicy(options, base) {
|
|
|
1329
1752
|
maxDelayMs: overrides.maxDelay === void 0 ? (
|
|
1330
1753
|
// Raising only the initial delay must not trip the inherited cap.
|
|
1331
1754
|
Math.max(base.maxDelayMs, initialDelayMs)
|
|
1332
|
-
) :
|
|
1755
|
+
) : chunkJ76M7XTS_cjs.Milliseconds.of(overrides.maxDelay, "retry.maxDelay"),
|
|
1333
1756
|
jitter: overrides.jitter ?? base.jitter,
|
|
1334
1757
|
retryIf: overrides.retryIf ?? base.retryIf
|
|
1335
1758
|
};
|
|
1336
1759
|
}
|
|
1337
1760
|
function assertValidRetryPolicy(policy) {
|
|
1338
1761
|
if (policy.attempts !== Number.POSITIVE_INFINITY && (!Number.isInteger(policy.attempts) || policy.attempts < 1)) {
|
|
1339
|
-
throw new
|
|
1762
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(
|
|
1340
1763
|
`retry.attempts must be a positive integer or Infinity, received ${policy.attempts}`
|
|
1341
1764
|
);
|
|
1342
1765
|
}
|
|
1343
1766
|
if (!Number.isFinite(policy.factor) || policy.factor < 1) {
|
|
1344
|
-
throw new
|
|
1767
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(`retry.factor must be at least 1, received ${policy.factor}`);
|
|
1345
1768
|
}
|
|
1346
1769
|
if (policy.maxDelayMs < policy.initialDelayMs) {
|
|
1347
|
-
throw new
|
|
1770
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError("retry.maxDelay must not be smaller than retry.initialDelay");
|
|
1348
1771
|
}
|
|
1349
1772
|
if (!JITTER_STRATEGIES.includes(policy.jitter)) {
|
|
1350
|
-
throw new
|
|
1773
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(
|
|
1351
1774
|
`retry.jitter must be one of ${JITTER_STRATEGIES.join(", ")}, received '${String(policy.jitter)}'`
|
|
1352
1775
|
);
|
|
1353
1776
|
}
|
|
@@ -1365,7 +1788,7 @@ function computeRetryDelay(policy, failedAttempts, random = Math.random) {
|
|
|
1365
1788
|
}
|
|
1366
1789
|
}
|
|
1367
1790
|
function isRetryableError(policy, error) {
|
|
1368
|
-
if (error instanceof
|
|
1791
|
+
if (error instanceof chunkJ76M7XTS_cjs.NonRetryableError) {
|
|
1369
1792
|
return false;
|
|
1370
1793
|
}
|
|
1371
1794
|
if (policy.retryIf === void 0) {
|
|
@@ -1378,7 +1801,7 @@ function isRetryableError(policy, error) {
|
|
|
1378
1801
|
}
|
|
1379
1802
|
}
|
|
1380
1803
|
|
|
1381
|
-
// src/
|
|
1804
|
+
// src/dispatch/delivery-outcomes.ts
|
|
1382
1805
|
var FIRST_COMPLETION_RETRY_MS = 50;
|
|
1383
1806
|
var SECOND_COMPLETION_RETRY_MS = 250;
|
|
1384
1807
|
var LAST_COMPLETION_RETRY_MS = 1e3;
|
|
@@ -1387,205 +1810,317 @@ var COMPLETION_RETRY_DELAYS_MS = [
|
|
|
1387
1810
|
SECOND_COMPLETION_RETRY_MS,
|
|
1388
1811
|
LAST_COMPLETION_RETRY_MS
|
|
1389
1812
|
];
|
|
1390
|
-
var
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
#dependencies;
|
|
1395
|
-
#sleep;
|
|
1396
|
-
#inFlight = /* @__PURE__ */ new Set();
|
|
1397
|
-
#completions;
|
|
1398
|
-
#pending = /* @__PURE__ */ new Map();
|
|
1399
|
-
#recoveryTimer;
|
|
1400
|
-
#recovery;
|
|
1401
|
-
#running = false;
|
|
1402
|
-
#loop;
|
|
1403
|
-
#wakeUp;
|
|
1404
|
-
/** Free slots the claim the loop was woken for needs at least; undefined while nobody woke it. */
|
|
1405
|
-
#signalledMinimum;
|
|
1406
|
-
/** Free slots a claim needs unless the poll interval or the quiet refill time passed: claims refill in groups. */
|
|
1407
|
-
#refillThreshold;
|
|
1408
|
-
/** Handlers whose last claim got less than it asked for: the store has nothing more for them right now. */
|
|
1409
|
-
#exhaustedHandlers = /* @__PURE__ */ new Set();
|
|
1410
|
-
/** Grows with every wake-up: a claim that started before one must not mark a handler exhausted. */
|
|
1411
|
-
#wakeGeneration = 0;
|
|
1412
|
-
#lastClaimStartedAt = Number.NEGATIVE_INFINITY;
|
|
1413
|
-
#cooldownTimer;
|
|
1414
|
-
#refillScheduled = false;
|
|
1415
|
-
#quietRefillTimer;
|
|
1416
|
-
#unwatch;
|
|
1417
|
-
#watching;
|
|
1418
|
-
/** Set when a stop gave up at its deadline: the store may be closed, so nothing more is started or retried. */
|
|
1419
|
-
#abandoned = false;
|
|
1420
|
-
/** Stops the lease renewal (or transactional lease guard) of each delivery whose handler runs. */
|
|
1421
|
-
#leaseKeepers = /* @__PURE__ */ new Map();
|
|
1422
|
-
#timers;
|
|
1423
|
-
constructor(dependencies) {
|
|
1424
|
-
this.#dependencies = dependencies;
|
|
1425
|
-
this.#timers = new DeliveryTimers(dependencies.clock);
|
|
1426
|
-
this.#sleep = dependencies.sleep ?? ((milliseconds) => this.#timers.sleep(milliseconds));
|
|
1427
|
-
const { store, clock, settings } = dependencies;
|
|
1428
|
-
this.#refillThreshold = Math.ceil(
|
|
1429
|
-
Math.min(settings.concurrency, settings.batchSize) * REFILL_FRACTION
|
|
1430
|
-
);
|
|
1431
|
-
const { completionBatchWindowMs: windowMs = 0, completionBatchSize: maxBatchSize = 1 } = settings;
|
|
1432
|
-
if (store.completeMany !== void 0 && windowMs > 0) {
|
|
1433
|
-
this.#completions = new CompletionBatcher(
|
|
1434
|
-
{ completeMany: (leases, now) => store.completeMany(leases, now) },
|
|
1435
|
-
clock,
|
|
1436
|
-
{ windowMs, maxBatchSize },
|
|
1437
|
-
() => this.#inFlight.size
|
|
1438
|
-
);
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
get isRunning() {
|
|
1442
|
-
return this.#running;
|
|
1443
|
-
}
|
|
1444
|
-
get inFlightCount() {
|
|
1445
|
-
return this.#inFlight.size;
|
|
1813
|
+
var DeliveryOutcomes = class {
|
|
1814
|
+
#parts;
|
|
1815
|
+
constructor(parts) {
|
|
1816
|
+
this.#parts = parts;
|
|
1446
1817
|
}
|
|
1447
1818
|
/**
|
|
1448
|
-
*
|
|
1449
|
-
*
|
|
1819
|
+
* Records a completion, retrying transient store errors while the lease is
|
|
1820
|
+
* still held. Gives up to lease expiry, which reruns the handler: at-least-once.
|
|
1450
1821
|
*/
|
|
1451
|
-
|
|
1452
|
-
|
|
1822
|
+
async confirmCompletion(delivery) {
|
|
1823
|
+
const { dependencies, shutdown, sleep } = this.#parts;
|
|
1824
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
1825
|
+
if (shutdown.abandoned) {
|
|
1826
|
+
return "unconfirmed";
|
|
1827
|
+
}
|
|
1828
|
+
let completed;
|
|
1829
|
+
try {
|
|
1830
|
+
completed = await this.complete(delivery);
|
|
1831
|
+
} catch (error) {
|
|
1832
|
+
const delay = COMPLETION_RETRY_DELAYS_MS[attempt];
|
|
1833
|
+
if (delay === void 0) {
|
|
1834
|
+
dependencies.notifier.failure("complete", error);
|
|
1835
|
+
return "unconfirmed";
|
|
1836
|
+
}
|
|
1837
|
+
await sleep(delay);
|
|
1838
|
+
continue;
|
|
1839
|
+
}
|
|
1840
|
+
if (!completed) {
|
|
1841
|
+
throw new chunkJ76M7XTS_cjs.LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost`, {
|
|
1842
|
+
storeConfirmed: true
|
|
1843
|
+
});
|
|
1844
|
+
}
|
|
1845
|
+
return "completed";
|
|
1846
|
+
}
|
|
1453
1847
|
}
|
|
1454
|
-
|
|
1455
|
-
|
|
1848
|
+
/** Records one completion, grouped with its neighbours when the store can complete many at once. */
|
|
1849
|
+
complete(delivery) {
|
|
1850
|
+
const { dependencies, completions } = this.#parts;
|
|
1851
|
+
const { store, clock } = dependencies;
|
|
1852
|
+
return completions === void 0 ? store.complete(delivery, clock.now()) : completions.submit(delivery);
|
|
1853
|
+
}
|
|
1854
|
+
async fail(registration, { delivery, error }) {
|
|
1855
|
+
const policy = registration.retry;
|
|
1856
|
+
if (delivery.attempt >= policy.attempts || !isRetryableError(policy, error)) {
|
|
1857
|
+
await this.bury(delivery, error);
|
|
1456
1858
|
return;
|
|
1457
1859
|
}
|
|
1458
|
-
this.#
|
|
1459
|
-
this.#completions?.setClosing(false);
|
|
1460
|
-
this.#watching = this.#watchNewDeliveries();
|
|
1461
|
-
this.#loop = this.#runLoop();
|
|
1860
|
+
await this.#reschedule(registration, { delivery, error });
|
|
1462
1861
|
}
|
|
1463
1862
|
/**
|
|
1464
|
-
*
|
|
1465
|
-
*
|
|
1863
|
+
* A worker that only noticed locally that its lease ran out may still own the delivery in the
|
|
1864
|
+
* store, where other deliveries of its lane keep renewing the lane: it must not stay in flight
|
|
1865
|
+
* for ever. The attempt took longer than its lease, so it counts as a failed attempt (an
|
|
1866
|
+
* exhausted delivery is dead-lettered, whatever a custom `retryIf` says): a handler that always
|
|
1867
|
+
* outlives its lease cannot run for ever at attempt 1. If the lease is gone for real the store
|
|
1868
|
+
* refuses and nothing changes. The lost lease was reported already.
|
|
1466
1869
|
*/
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
this.#requestClaim();
|
|
1473
|
-
return;
|
|
1870
|
+
async giveBack(registration, { delivery, error }) {
|
|
1871
|
+
if (delivery.attempt >= registration.retry.attempts) {
|
|
1872
|
+
await this.bury(delivery, error);
|
|
1873
|
+
} else {
|
|
1874
|
+
await this.#reschedule(registration, { delivery, error });
|
|
1474
1875
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1876
|
+
}
|
|
1877
|
+
bury(delivery, error) {
|
|
1878
|
+
const { store, clock, notifier } = this.#parts.dependencies;
|
|
1879
|
+
const errorInfo = chunkJ76M7XTS_cjs.toErrorInfo(error);
|
|
1880
|
+
return this.#change({
|
|
1881
|
+
operation: "bury",
|
|
1882
|
+
delivery,
|
|
1883
|
+
write: () => store.bury(delivery, errorInfo, clock.now()),
|
|
1884
|
+
rewrite: () => store.bury(delivery, errorInfo, clock.now()),
|
|
1885
|
+
applied: () => notifier.notify("onDeliveryDead", { delivery, error: errorInfo })
|
|
1481
1886
|
});
|
|
1482
1887
|
}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
this.#
|
|
1486
|
-
|
|
1888
|
+
release(delivery) {
|
|
1889
|
+
const { store, clock, notifier } = this.#parts.dependencies;
|
|
1890
|
+
return this.#change({
|
|
1891
|
+
operation: "release",
|
|
1892
|
+
delivery,
|
|
1893
|
+
write: () => store.release(delivery, clock.now()),
|
|
1894
|
+
rewrite: () => store.release(delivery, clock.now()),
|
|
1895
|
+
applied: () => notifier.notify("onDeliveryReleased", { delivery })
|
|
1896
|
+
});
|
|
1487
1897
|
}
|
|
1488
|
-
/**
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
const
|
|
1898
|
+
/** Schedules the next attempt with the retry policy's backoff. */
|
|
1899
|
+
#reschedule(registration, { delivery, error }) {
|
|
1900
|
+
const { store, clock, random, notifier } = this.#parts.dependencies;
|
|
1901
|
+
const policy = registration.retry;
|
|
1902
|
+
const errorInfo = chunkJ76M7XTS_cjs.toErrorInfo(error);
|
|
1903
|
+
const now = clock.now();
|
|
1904
|
+
const availableAt = new Date(
|
|
1905
|
+
now.getTime() + computeRetryDelay(policy, delivery.attempt, random)
|
|
1906
|
+
);
|
|
1907
|
+
const write = () => store.reschedule(delivery, availableAt, errorInfo, now);
|
|
1908
|
+
return this.#change({
|
|
1909
|
+
operation: "reschedule",
|
|
1910
|
+
delivery,
|
|
1911
|
+
write,
|
|
1912
|
+
rewrite: write,
|
|
1913
|
+
applied: () => notifier.notify("onDeliveryRetryScheduled", { delivery, error: errorInfo, availableAt })
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
/** Writes a state change; reports it, or the lost lease, or retains it when the store failed. */
|
|
1917
|
+
async #change(change) {
|
|
1918
|
+
const { dependencies, recovery } = this.#parts;
|
|
1919
|
+
const { delivery } = change;
|
|
1920
|
+
let changed;
|
|
1494
1921
|
try {
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
notifier.failure("watch", error);
|
|
1922
|
+
changed = await change.write();
|
|
1923
|
+
} catch (storeError) {
|
|
1924
|
+
dependencies.notifier.failure(change.operation, storeError);
|
|
1925
|
+
recovery.retain({ delivery, settle: change.rewrite, applied: change.applied });
|
|
1926
|
+
return;
|
|
1927
|
+
}
|
|
1928
|
+
if (changed) {
|
|
1929
|
+
change.applied();
|
|
1930
|
+
} else {
|
|
1931
|
+
dependencies.notifier.notify("onLeaseLost", { delivery });
|
|
1506
1932
|
}
|
|
1507
1933
|
}
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1934
|
+
};
|
|
1935
|
+
var storage = new async_hooks.AsyncLocalStorage();
|
|
1936
|
+
function runInScope(scope, work) {
|
|
1937
|
+
return storage.run({ ...storage.getStore(), ...scope }, work);
|
|
1938
|
+
}
|
|
1939
|
+
function runInIsolatedScope(scope, work) {
|
|
1940
|
+
return storage.run(scope, work);
|
|
1941
|
+
}
|
|
1942
|
+
function getCurrentScope() {
|
|
1943
|
+
return storage.getStore();
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
// src/dispatch/handler-invocation.ts
|
|
1947
|
+
function rejectOnAbort(signal) {
|
|
1948
|
+
return new Promise((_, reject) => {
|
|
1949
|
+
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
var HandlerInvocation = class {
|
|
1953
|
+
#dependencies;
|
|
1954
|
+
#timers;
|
|
1955
|
+
constructor(dependencies, timers) {
|
|
1956
|
+
this.#dependencies = dependencies;
|
|
1957
|
+
this.#timers = timers;
|
|
1958
|
+
}
|
|
1959
|
+
async invoke(input) {
|
|
1960
|
+
const { registration, event, delivery, controller, transaction } = input;
|
|
1961
|
+
const context = contextOf(input);
|
|
1962
|
+
const scope = transaction === void 0 ? { event } : { event, transaction };
|
|
1963
|
+
if (controller.signal.aborted) {
|
|
1964
|
+
throw controller.signal.reason;
|
|
1965
|
+
}
|
|
1966
|
+
const handle = () => runInIsolatedScope(scope, () => registration.handle(event, context));
|
|
1967
|
+
const { observation } = this.#dependencies;
|
|
1968
|
+
const work = Promise.resolve().then(
|
|
1969
|
+
observation === void 0 ? handle : () => observation.process({ registration, delivery }, handle)
|
|
1970
|
+
);
|
|
1971
|
+
work.catch(() => void 0);
|
|
1972
|
+
const cancelTimeout = this.#startTimeout(registration, controller);
|
|
1513
1973
|
try {
|
|
1514
|
-
|
|
1974
|
+
const settled = Promise.race([work, rejectOnAbort(controller.signal)]);
|
|
1975
|
+
await (observation === void 0 ? settled : observation.observe({ registration, delivery }, settled));
|
|
1515
1976
|
} catch (error) {
|
|
1516
|
-
this.#
|
|
1977
|
+
await this.#awaitAbandonedHandler(work, {
|
|
1978
|
+
reason: error,
|
|
1979
|
+
transactional: transaction !== void 0
|
|
1980
|
+
});
|
|
1981
|
+
throw error;
|
|
1982
|
+
} finally {
|
|
1983
|
+
cancelTimeout?.();
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
/** Aborts the handler when its timeout passes; undefined for a handler without one. */
|
|
1987
|
+
#startTimeout(registration, controller) {
|
|
1988
|
+
if (registration.timeoutMs === null) {
|
|
1989
|
+
return void 0;
|
|
1517
1990
|
}
|
|
1991
|
+
const timeoutMs = registration.timeoutMs;
|
|
1992
|
+
return this.#timers.start({
|
|
1993
|
+
run: () => {
|
|
1994
|
+
controller.abort(
|
|
1995
|
+
new chunkJ76M7XTS_cjs.HandlerTimeoutError(
|
|
1996
|
+
`Handler '${registration.id}' did not settle within ${timeoutMs}ms`
|
|
1997
|
+
)
|
|
1998
|
+
);
|
|
1999
|
+
},
|
|
2000
|
+
delayMs: timeoutMs
|
|
2001
|
+
});
|
|
1518
2002
|
}
|
|
1519
2003
|
/**
|
|
1520
|
-
*
|
|
1521
|
-
*
|
|
1522
|
-
*
|
|
1523
|
-
*
|
|
2004
|
+
* After an abort the handler may still be running. Waiting for it keeps a
|
|
2005
|
+
* retry from overlapping the abandoned run, and keeps a transactional
|
|
2006
|
+
* handler from writing through a transaction that was already released.
|
|
2007
|
+
* Transactional handlers are always awaited; others for one lease duration,
|
|
2008
|
+
* except at shutdown, which already waited.
|
|
1524
2009
|
*/
|
|
1525
|
-
async
|
|
1526
|
-
const
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
2010
|
+
async #awaitAbandonedHandler(work, { reason, transactional }) {
|
|
2011
|
+
const settledWork = work.then(
|
|
2012
|
+
() => void 0,
|
|
2013
|
+
() => void 0
|
|
2014
|
+
);
|
|
2015
|
+
if (transactional) {
|
|
2016
|
+
await settledWork;
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
if (reason instanceof chunkJ76M7XTS_cjs.DispatcherStoppedError) {
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
2022
|
+
const deadline = Date.now() + this.#dependencies.settings.leaseDurationMs;
|
|
2023
|
+
await waitUntil(this.#dependencies.clock, { work: settledWork, deadline });
|
|
2024
|
+
}
|
|
2025
|
+
};
|
|
2026
|
+
function contextOf(input) {
|
|
2027
|
+
const { registration, delivery, controller, transaction } = input;
|
|
2028
|
+
return {
|
|
2029
|
+
deliveryId: delivery.deliveryId,
|
|
2030
|
+
handler: delivery.handler,
|
|
2031
|
+
attempt: delivery.attempt,
|
|
2032
|
+
maxAttempts: registration.retry.attempts,
|
|
2033
|
+
signal: controller.signal,
|
|
2034
|
+
transaction
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
2037
|
+
async function waitUntil(clock, { work, deadline }) {
|
|
2038
|
+
let cancel;
|
|
2039
|
+
const timedOut = new Promise((resolve) => {
|
|
2040
|
+
cancel = chunkJ76M7XTS_cjs.schedule(clock, {
|
|
2041
|
+
run: () => resolve("timeout"),
|
|
2042
|
+
delayMs: Math.max(0, deadline - Date.now())
|
|
2043
|
+
});
|
|
2044
|
+
});
|
|
2045
|
+
const outcome = await Promise.race([work.then(() => "done"), timedOut]);
|
|
2046
|
+
cancel?.();
|
|
2047
|
+
return outcome;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
// src/dispatch/in-flight-deliveries.ts
|
|
2051
|
+
var InFlightDeliveries = class {
|
|
2052
|
+
#deliveries = /* @__PURE__ */ new Set();
|
|
2053
|
+
get size() {
|
|
2054
|
+
return this.#deliveries.size;
|
|
2055
|
+
}
|
|
2056
|
+
add(delivery) {
|
|
2057
|
+
this.#deliveries.add(delivery);
|
|
2058
|
+
}
|
|
2059
|
+
delete(delivery) {
|
|
2060
|
+
this.#deliveries.delete(delivery);
|
|
2061
|
+
}
|
|
2062
|
+
/** How many of the deliveries run a handler of `registration`. */
|
|
2063
|
+
countOf(registration) {
|
|
2064
|
+
let count = 0;
|
|
2065
|
+
for (const delivery of this.#deliveries) {
|
|
2066
|
+
if (registration.claimIds.includes(delivery.handler)) {
|
|
2067
|
+
count += 1;
|
|
1533
2068
|
}
|
|
1534
2069
|
}
|
|
2070
|
+
return count;
|
|
1535
2071
|
}
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
2072
|
+
/** Settles once every delivery running now settled, whatever its outcome. */
|
|
2073
|
+
allSettled() {
|
|
2074
|
+
return Promise.allSettled([...this.#deliveries].map((delivery) => delivery.settled));
|
|
2075
|
+
}
|
|
2076
|
+
abortAll(reason) {
|
|
2077
|
+
for (const delivery of this.#deliveries) {
|
|
2078
|
+
delivery.controller.abort(reason);
|
|
1540
2079
|
}
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
2080
|
+
}
|
|
2081
|
+
};
|
|
2082
|
+
|
|
2083
|
+
// src/dispatch/settlement-recovery.ts
|
|
2084
|
+
function pendingKeyOf(delivery) {
|
|
2085
|
+
return chunkJ76M7XTS_cjs.DeliveryLeaseIdentity.of(delivery.deliveryId, delivery.leaseToken);
|
|
2086
|
+
}
|
|
2087
|
+
var SettlementRecovery = class {
|
|
2088
|
+
#dependencies;
|
|
2089
|
+
#shutdown;
|
|
2090
|
+
#pending = /* @__PURE__ */ new Map();
|
|
2091
|
+
#recoveryTimer;
|
|
2092
|
+
#recovery;
|
|
2093
|
+
constructor(dependencies, shutdown) {
|
|
2094
|
+
this.#dependencies = dependencies;
|
|
2095
|
+
this.#shutdown = shutdown;
|
|
2096
|
+
}
|
|
2097
|
+
retain(entry) {
|
|
2098
|
+
if (this.#shutdown.abandoned) {
|
|
2099
|
+
return;
|
|
1558
2100
|
}
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
2101
|
+
this.#pending.set(pendingKeyOf(entry.delivery), entry);
|
|
2102
|
+
this.#recoveryTimer ??= chunkJ76M7XTS_cjs.schedule(this.#dependencies.clock, {
|
|
2103
|
+
run: () => void this.#recoverSettlements(),
|
|
2104
|
+
delayMs: this.#dependencies.settings.pollIntervalMs,
|
|
2105
|
+
repeat: true,
|
|
2106
|
+
unref: true
|
|
2107
|
+
});
|
|
1562
2108
|
}
|
|
1563
|
-
/**
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
#abandon() {
|
|
1570
|
-
this.#abandoned = true;
|
|
1571
|
-
for (const stopKeeping of this.#leaseKeepers.values()) {
|
|
1572
|
-
stopKeeping();
|
|
2109
|
+
/** A new claim of this delivery makes whatever was pending for its earlier claims moot. */
|
|
2110
|
+
forgetEarlierClaims(delivery) {
|
|
2111
|
+
for (const key of [...this.#pending.keys()].filter(
|
|
2112
|
+
(pending) => pending.startsWith(chunkJ76M7XTS_cjs.DeliveryLeaseIdentity.prefixOf(delivery.deliveryId))
|
|
2113
|
+
)) {
|
|
2114
|
+
this.#pending.delete(key);
|
|
1573
2115
|
}
|
|
1574
|
-
this.#leaseKeepers.clear();
|
|
1575
|
-
this.#timers.cancelAll();
|
|
1576
|
-
this.#recoveryTimer?.();
|
|
1577
|
-
this.#recoveryTimer = void 0;
|
|
1578
|
-
this.#pending.clear();
|
|
1579
|
-
return this.#inFlight.size;
|
|
1580
2116
|
}
|
|
1581
2117
|
/**
|
|
1582
2118
|
* Gives the settlements still pending one last try and forgets them. A pass of the timer may be inside the
|
|
1583
2119
|
* store: it ends before the last pass starts. A store that does not answer before the deadline gets no
|
|
1584
2120
|
* further call, since it may be closed right after.
|
|
1585
2121
|
*/
|
|
1586
|
-
async
|
|
1587
|
-
this.#
|
|
1588
|
-
this.#recoveryTimer = void 0;
|
|
2122
|
+
async close(deadline) {
|
|
2123
|
+
this.#stopTimer();
|
|
1589
2124
|
const lastTry = (async () => {
|
|
1590
2125
|
await this.#recovery;
|
|
1591
2126
|
await this.#recoverSettlements();
|
|
@@ -1593,6 +2128,15 @@ var Dispatcher = class {
|
|
|
1593
2128
|
await deadline.race(lastTry);
|
|
1594
2129
|
this.#pending.clear();
|
|
1595
2130
|
}
|
|
2131
|
+
/** Stop gave up: forgets every settlement still to retry, which may reach a closed store. */
|
|
2132
|
+
abandon() {
|
|
2133
|
+
this.#stopTimer();
|
|
2134
|
+
this.#pending.clear();
|
|
2135
|
+
}
|
|
2136
|
+
#stopTimer() {
|
|
2137
|
+
this.#recoveryTimer?.();
|
|
2138
|
+
this.#recoveryTimer = void 0;
|
|
2139
|
+
}
|
|
1596
2140
|
/**
|
|
1597
2141
|
* Retries every pending settlement once; a pass that is already running is the one returned. A store that
|
|
1598
2142
|
* still fails leaves the settlement for the next attempt (it was reported when it first failed).
|
|
@@ -1610,7 +2154,7 @@ var Dispatcher = class {
|
|
|
1610
2154
|
}
|
|
1611
2155
|
try {
|
|
1612
2156
|
const applied = await entry.settle();
|
|
1613
|
-
this.#pending.delete(
|
|
2157
|
+
this.#pending.delete(pendingKeyOf(entry.delivery));
|
|
1614
2158
|
if (applied) {
|
|
1615
2159
|
entry.applied();
|
|
1616
2160
|
} else {
|
|
@@ -1620,688 +2164,909 @@ var Dispatcher = class {
|
|
|
1620
2164
|
}
|
|
1621
2165
|
}
|
|
1622
2166
|
}
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
2167
|
+
};
|
|
2168
|
+
|
|
2169
|
+
// src/completion-batcher.ts
|
|
2170
|
+
var CompletionBatcher = class {
|
|
2171
|
+
#store;
|
|
2172
|
+
#clock;
|
|
2173
|
+
#settings;
|
|
2174
|
+
#runningCount;
|
|
2175
|
+
#pending = [];
|
|
2176
|
+
#timer;
|
|
2177
|
+
#closing = false;
|
|
2178
|
+
/**
|
|
2179
|
+
* @param runningCount Number of deliveries the dispatcher is running right
|
|
2180
|
+
* now, including the ones waiting in this batcher.
|
|
2181
|
+
*/
|
|
2182
|
+
constructor(store, clock, settings, runningCount) {
|
|
2183
|
+
this.#store = store;
|
|
2184
|
+
this.#clock = clock;
|
|
2185
|
+
this.#settings = settings;
|
|
2186
|
+
this.#runningCount = runningCount;
|
|
2187
|
+
}
|
|
2188
|
+
/**
|
|
2189
|
+
* Queues a completion. Resolves with whether the lease was still held;
|
|
2190
|
+
* rejects with the store error when the batch could not be written.
|
|
2191
|
+
*/
|
|
2192
|
+
submit(lease) {
|
|
2193
|
+
return new Promise((resolve, reject) => {
|
|
2194
|
+
this.#pending.push({ lease, resolve, reject });
|
|
2195
|
+
if (this.#closing || this.#pending.length >= this.#settings.maxBatchSize || this.#pending.length >= this.#runningCount()) {
|
|
2196
|
+
this.#flush();
|
|
2197
|
+
} else {
|
|
2198
|
+
this.#timer ??= chunkJ76M7XTS_cjs.schedule(this.#clock, {
|
|
2199
|
+
run: () => this.#flush(),
|
|
2200
|
+
delayMs: this.#settings.windowMs
|
|
2201
|
+
});
|
|
2202
|
+
}
|
|
1633
2203
|
});
|
|
1634
2204
|
}
|
|
1635
|
-
/**
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
2205
|
+
/**
|
|
2206
|
+
* Shutdown has begun (or ended): while closing, nothing waits for company any more, so the completions
|
|
2207
|
+
* already queued are written now and later ones at once.
|
|
2208
|
+
*/
|
|
2209
|
+
setClosing(closing) {
|
|
2210
|
+
this.#closing = closing;
|
|
2211
|
+
if (closing && this.#pending.length > 0) {
|
|
2212
|
+
this.#flush();
|
|
1642
2213
|
}
|
|
1643
2214
|
}
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
2215
|
+
#flush() {
|
|
2216
|
+
this.#timer?.();
|
|
2217
|
+
this.#timer = void 0;
|
|
2218
|
+
const batch = this.#pending;
|
|
2219
|
+
this.#pending = [];
|
|
2220
|
+
Promise.resolve().then(
|
|
2221
|
+
() => this.#store.completeMany(
|
|
2222
|
+
batch.map((entry) => entry.lease),
|
|
2223
|
+
this.#clock.now()
|
|
2224
|
+
)
|
|
2225
|
+
).then(
|
|
2226
|
+
(outcomes) => batch.forEach((entry, index) => entry.resolve(outcomes[index])),
|
|
2227
|
+
(error) => batch.forEach((entry) => entry.reject(error))
|
|
2228
|
+
);
|
|
1649
2229
|
}
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
2230
|
+
};
|
|
2231
|
+
|
|
2232
|
+
// src/delivery-timers.ts
|
|
2233
|
+
var DeliveryTimers = class {
|
|
2234
|
+
#clock;
|
|
2235
|
+
/** Cancel of every timer still waiting, with what its cancellation must also do. */
|
|
2236
|
+
#waiting = /* @__PURE__ */ new Map();
|
|
2237
|
+
constructor(clock) {
|
|
2238
|
+
this.#clock = clock;
|
|
2239
|
+
}
|
|
2240
|
+
/** Runs `task.run` after `task.delayMs`; returns what cancels it. */
|
|
2241
|
+
start(task) {
|
|
2242
|
+
return this.#start(task, () => void 0);
|
|
2243
|
+
}
|
|
2244
|
+
/** Resolves after `milliseconds`, or at once when {@link DeliveryTimers.cancelAll} runs. */
|
|
2245
|
+
sleep(milliseconds) {
|
|
2246
|
+
return new Promise((resolve) => {
|
|
2247
|
+
this.#start({ run: resolve, delayMs: milliseconds }, resolve);
|
|
1657
2248
|
});
|
|
1658
|
-
const outcome = await Promise.race([work.then(() => "done"), timedOut]);
|
|
1659
|
-
cancel?.();
|
|
1660
|
-
return outcome;
|
|
1661
2249
|
}
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
* for fewer free slots than the refill threshold, so a busy dispatcher never claims slot by slot.
|
|
1667
|
-
*/
|
|
1668
|
-
async #runLoop() {
|
|
1669
|
-
let minimumFree = 1;
|
|
1670
|
-
while (this.#running) {
|
|
1671
|
-
let round = { requested: 0, claimed: 0 };
|
|
1672
|
-
if (this.#freeSlots() >= minimumFree) {
|
|
1673
|
-
try {
|
|
1674
|
-
round = await this.#claimAndStart();
|
|
1675
|
-
} catch (error) {
|
|
1676
|
-
this.#dependencies.notifier.failure("claim", error);
|
|
1677
|
-
}
|
|
1678
|
-
}
|
|
1679
|
-
if (round.requested > 0 && round.claimed === round.requested && this.#freeSlots() >= this.#refillThreshold) {
|
|
1680
|
-
minimumFree = this.#refillThreshold;
|
|
1681
|
-
continue;
|
|
1682
|
-
}
|
|
1683
|
-
minimumFree = await this.#waitForWakeUp();
|
|
1684
|
-
if (this.#freeSlots() < minimumFree) {
|
|
1685
|
-
this.#requestClaim();
|
|
1686
|
-
}
|
|
2250
|
+
cancelAll() {
|
|
2251
|
+
for (const [cancel, onCancel] of [...this.#waiting]) {
|
|
2252
|
+
cancel();
|
|
2253
|
+
onCancel();
|
|
1687
2254
|
}
|
|
1688
2255
|
}
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
2256
|
+
#start(task, onCancel) {
|
|
2257
|
+
const cancel = () => {
|
|
2258
|
+
cancelTimer();
|
|
2259
|
+
this.#waiting.delete(cancel);
|
|
2260
|
+
};
|
|
2261
|
+
const cancelTimer = chunkJ76M7XTS_cjs.schedule(this.#clock, {
|
|
2262
|
+
run: () => {
|
|
2263
|
+
this.#waiting.delete(cancel);
|
|
2264
|
+
task.run();
|
|
2265
|
+
},
|
|
2266
|
+
delayMs: task.delayMs
|
|
2267
|
+
});
|
|
2268
|
+
this.#waiting.set(cancel, onCancel);
|
|
2269
|
+
return cancel;
|
|
2270
|
+
}
|
|
2271
|
+
};
|
|
2272
|
+
|
|
2273
|
+
// src/dispatcher.ts
|
|
2274
|
+
var Dispatcher = class {
|
|
2275
|
+
#dependencies;
|
|
2276
|
+
#inFlight = new InFlightDeliveries();
|
|
2277
|
+
#shutdown = new ShutdownLatch();
|
|
2278
|
+
#timers;
|
|
2279
|
+
#completions;
|
|
2280
|
+
#recovery;
|
|
2281
|
+
#executor;
|
|
2282
|
+
#loop;
|
|
2283
|
+
#looping;
|
|
2284
|
+
#unwatch;
|
|
2285
|
+
#watching;
|
|
2286
|
+
constructor(dependencies) {
|
|
2287
|
+
this.#dependencies = dependencies;
|
|
2288
|
+
this.#timers = new DeliveryTimers(dependencies.clock);
|
|
2289
|
+
this.#completions = this.#createCompletionBatcher();
|
|
2290
|
+
this.#recovery = new SettlementRecovery(dependencies, this.#shutdown);
|
|
2291
|
+
this.#executor = this.#createExecutor();
|
|
2292
|
+
this.#loop = new ClaimLoop({
|
|
2293
|
+
dependencies,
|
|
2294
|
+
inFlight: this.#inFlight,
|
|
2295
|
+
planner: new ClaimPlanner(this.#inFlight, dependencies.settings.batchSize),
|
|
2296
|
+
shutdown: this.#shutdown,
|
|
2297
|
+
start: (delivery) => this.#executor.start(delivery)
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
#createCompletionBatcher() {
|
|
2301
|
+
const { store, clock, settings } = this.#dependencies;
|
|
2302
|
+
const { completionBatchWindowMs: windowMs = 0, completionBatchSize: maxBatchSize = 1 } = settings;
|
|
2303
|
+
if (store.completeMany === void 0 || !(windowMs > 0)) {
|
|
2304
|
+
return void 0;
|
|
1706
2305
|
}
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
2306
|
+
return new CompletionBatcher(
|
|
2307
|
+
{ completeMany: (leases, now) => store.completeMany(leases, now) },
|
|
2308
|
+
clock,
|
|
2309
|
+
{ windowMs, maxBatchSize },
|
|
2310
|
+
() => this.#inFlight.size
|
|
2311
|
+
);
|
|
2312
|
+
}
|
|
2313
|
+
#createExecutor() {
|
|
2314
|
+
const dependencies = this.#dependencies;
|
|
2315
|
+
const outcomes = new DeliveryOutcomes({
|
|
2316
|
+
dependencies,
|
|
2317
|
+
recovery: this.#recovery,
|
|
2318
|
+
shutdown: this.#shutdown,
|
|
2319
|
+
completions: this.#completions,
|
|
2320
|
+
sleep: dependencies.sleep ?? ((milliseconds) => this.#timers.sleep(milliseconds))
|
|
2321
|
+
});
|
|
2322
|
+
return new DeliveryExecutor({
|
|
2323
|
+
dependencies,
|
|
2324
|
+
inFlight: this.#inFlight,
|
|
2325
|
+
recovery: this.#recovery,
|
|
2326
|
+
outcomes,
|
|
2327
|
+
invocation: new HandlerInvocation(dependencies, this.#timers),
|
|
2328
|
+
timers: this.#timers,
|
|
2329
|
+
shutdown: this.#shutdown,
|
|
2330
|
+
onSettled: (delivery, succeeded) => this.#loop.onSettled(delivery, succeeded)
|
|
2331
|
+
});
|
|
2332
|
+
}
|
|
2333
|
+
get isRunning() {
|
|
2334
|
+
return this.#loop.isRunning;
|
|
2335
|
+
}
|
|
2336
|
+
get inFlightCount() {
|
|
2337
|
+
return this.#inFlight.size;
|
|
1710
2338
|
}
|
|
1711
2339
|
/**
|
|
1712
|
-
*
|
|
1713
|
-
*
|
|
1714
|
-
* (a retry without delay, a release), and a handler whose last claim was full may have a backlog.
|
|
2340
|
+
* True while the store reports new deliveries to this dispatcher by itself (see
|
|
2341
|
+
* {@link EventStore.watchNewDeliveries}), from every process: a publisher then has no reason to wake it.
|
|
1715
2342
|
*/
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
2343
|
+
get watchesNewDeliveries() {
|
|
2344
|
+
return this.#unwatch !== void 0;
|
|
2345
|
+
}
|
|
2346
|
+
start() {
|
|
2347
|
+
if (this.#loop.isRunning) {
|
|
2348
|
+
return;
|
|
1719
2349
|
}
|
|
2350
|
+
this.#loop.open();
|
|
2351
|
+
this.#completions?.setClosing(false);
|
|
2352
|
+
this.#watching = this.#watchNewDeliveries();
|
|
2353
|
+
this.#looping = this.#loop.run();
|
|
1720
2354
|
}
|
|
1721
2355
|
/**
|
|
1722
|
-
* Asks
|
|
1723
|
-
*
|
|
1724
|
-
* claim started for a short while. With no free slot the next settlement asks again.
|
|
2356
|
+
* Asks for a claim soon, for example right after a publish: at once after a quiet period, otherwise
|
|
2357
|
+
* at the end of a short cooldown that merges the wake-ups arriving meanwhile into one claim.
|
|
1725
2358
|
*/
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
if (free >= this.#refillThreshold) {
|
|
1729
|
-
if (!this.#refillScheduled) {
|
|
1730
|
-
this.#refillScheduled = true;
|
|
1731
|
-
setImmediate(() => {
|
|
1732
|
-
this.#refillScheduled = false;
|
|
1733
|
-
this.#signal(this.#refillThreshold);
|
|
1734
|
-
});
|
|
1735
|
-
}
|
|
1736
|
-
} else if (free > 0) {
|
|
1737
|
-
this.#quietRefillTimer ??= chunk3OHMEMZA_cjs.schedule(this.#dependencies.clock, {
|
|
1738
|
-
run: () => {
|
|
1739
|
-
this.#quietRefillTimer = void 0;
|
|
1740
|
-
this.#signal(1);
|
|
1741
|
-
},
|
|
1742
|
-
delayMs: Math.max(0, this.#lastClaimStartedAt + QUIET_REFILL_MS - Date.now())
|
|
1743
|
-
});
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
#freeSlots() {
|
|
1747
|
-
return this.#dependencies.settings.concurrency - this.#inFlight.size;
|
|
2359
|
+
wake() {
|
|
2360
|
+
this.#loop.wake();
|
|
1748
2361
|
}
|
|
1749
|
-
/**
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
const
|
|
1757
|
-
this.#
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
requested += request.limit;
|
|
1761
|
-
const claim = {
|
|
1762
|
-
handlers: request.handlers,
|
|
1763
|
-
limit: request.limit,
|
|
1764
|
-
leaseOwner: settings.owner,
|
|
1765
|
-
leaseExpiresAt: new Date(now.getTime() + settings.leaseDurationMs),
|
|
1766
|
-
now
|
|
1767
|
-
};
|
|
1768
|
-
const claimed = await (observation === void 0 ? store.claim(claim) : observation.claim(store, claim));
|
|
1769
|
-
for (const handler of request.handlers) {
|
|
1770
|
-
if (claimed.length < request.limit && wakeGeneration === this.#wakeGeneration) {
|
|
1771
|
-
this.#exhaustedHandlers.add(handler);
|
|
1772
|
-
} else {
|
|
1773
|
-
this.#exhaustedHandlers.delete(handler);
|
|
1774
|
-
}
|
|
2362
|
+
/**
|
|
2363
|
+
* Starts listening to the store's reports of new deliveries. Polling stays the safety net: a store
|
|
2364
|
+
* that cannot listen, or fails to, only costs latency, so a failure is reported and otherwise ignored.
|
|
2365
|
+
*/
|
|
2366
|
+
async #watchNewDeliveries() {
|
|
2367
|
+
const { store, notifier } = this.#dependencies;
|
|
2368
|
+
try {
|
|
2369
|
+
const unwatch = await store.watchNewDeliveries?.(() => this.wake());
|
|
2370
|
+
if (unwatch !== void 0 && !this.#loop.isRunning) {
|
|
2371
|
+
await unwatch();
|
|
2372
|
+
return;
|
|
1775
2373
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
2374
|
+
this.#unwatch = unwatch;
|
|
2375
|
+
if (unwatch !== void 0) {
|
|
2376
|
+
this.wake();
|
|
1778
2377
|
}
|
|
2378
|
+
} catch (error) {
|
|
2379
|
+
notifier.failure("watch", error);
|
|
1779
2380
|
}
|
|
1780
|
-
return { requested, claimed: started.length, started };
|
|
1781
2381
|
}
|
|
1782
|
-
/**
|
|
1783
|
-
#
|
|
1784
|
-
|
|
1785
|
-
const
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
const room = registration.concurrency - this.#countInFlight(registration);
|
|
1792
|
-
const limit = Math.min(room, free);
|
|
1793
|
-
if (limit > 0) {
|
|
1794
|
-
plans.push({ handlers: [...registration.claimIds], limit });
|
|
1795
|
-
free -= limit;
|
|
1796
|
-
}
|
|
2382
|
+
/** Stops listening to the store's reports of new deliveries. */
|
|
2383
|
+
async #stopWatching() {
|
|
2384
|
+
await this.#watching;
|
|
2385
|
+
const unwatch = this.#unwatch;
|
|
2386
|
+
this.#unwatch = void 0;
|
|
2387
|
+
try {
|
|
2388
|
+
await unwatch?.();
|
|
2389
|
+
} catch (error) {
|
|
2390
|
+
this.#dependencies.notifier.failure("watch", error);
|
|
1797
2391
|
}
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
2392
|
+
}
|
|
2393
|
+
/**
|
|
2394
|
+
* Stops claiming and waits for running handlers until the drain point of `deadline` (default: the
|
|
2395
|
+
* shutdown timeout from now), then aborts the rest and releases their deliveries for another
|
|
2396
|
+
* process. Deliveries still in flight when the deadline itself passes are abandoned: their leases
|
|
2397
|
+
* expire and they run again elsewhere. Resolves with how many were abandoned.
|
|
2398
|
+
*/
|
|
2399
|
+
async stop(deadline) {
|
|
2400
|
+
const { clock, settings } = this.#dependencies;
|
|
2401
|
+
const own = deadline ?? new ShutdownDeadline(clock, settings.shutdownTimeoutMs);
|
|
2402
|
+
try {
|
|
2403
|
+
return await this.#stopWithin(own);
|
|
2404
|
+
} finally {
|
|
2405
|
+
if (deadline === void 0) {
|
|
2406
|
+
own.dispose();
|
|
2407
|
+
}
|
|
1803
2408
|
}
|
|
1804
|
-
return plans;
|
|
1805
2409
|
}
|
|
1806
|
-
#
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
2410
|
+
async #stopWithin(deadline) {
|
|
2411
|
+
if (!this.#loop.isRunning) {
|
|
2412
|
+
await this.#recovery.close(deadline);
|
|
2413
|
+
return 0;
|
|
2414
|
+
}
|
|
2415
|
+
this.#loop.halt();
|
|
2416
|
+
this.#completions?.setClosing(true);
|
|
2417
|
+
this.#loop.interrupt();
|
|
2418
|
+
await deadline.race(this.#stopWatching());
|
|
2419
|
+
await deadline.race(this.#looping);
|
|
2420
|
+
const settled = this.#inFlight.allSettled();
|
|
2421
|
+
if (await deadline.raceDrain(settled) === "timeout") {
|
|
2422
|
+
this.#inFlight.abortAll(new chunkJ76M7XTS_cjs.DispatcherStoppedError("relaybus dispatcher stopped"));
|
|
2423
|
+
if (await deadline.race(settled) === "timeout") {
|
|
2424
|
+
return this.#abandon();
|
|
1811
2425
|
}
|
|
1812
2426
|
}
|
|
1813
|
-
|
|
2427
|
+
await this.#recovery.close(deadline);
|
|
2428
|
+
await deadline.race(this.#relinquish());
|
|
2429
|
+
return 0;
|
|
1814
2430
|
}
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
};
|
|
1828
|
-
this.#inFlight.add(entry);
|
|
1829
|
-
return entry;
|
|
2431
|
+
/**
|
|
2432
|
+
* The deadline passed with deliveries still in flight (aborted, their release not answered): forgets
|
|
2433
|
+
* them and every settlement still to retry. Whatever they release or complete from now on may reach a
|
|
2434
|
+
* closed store; a delivery whose state change is lost stays leased until the lease expires and then
|
|
2435
|
+
* runs again (at-least-once).
|
|
2436
|
+
*/
|
|
2437
|
+
#abandon() {
|
|
2438
|
+
this.#shutdown.abandon();
|
|
2439
|
+
this.#executor.abandon();
|
|
2440
|
+
this.#timers.cancelAll();
|
|
2441
|
+
this.#recovery.abandon();
|
|
2442
|
+
return this.#inFlight.size;
|
|
1830
2443
|
}
|
|
1831
|
-
/**
|
|
1832
|
-
async #
|
|
1833
|
-
const {
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
this.#pending.delete(key);
|
|
2444
|
+
/** Gives lease groups the owner no longer works on back to the store, so other workers need not wait for expiry. */
|
|
2445
|
+
async #relinquish() {
|
|
2446
|
+
const { store, clock, notifier, settings } = this.#dependencies;
|
|
2447
|
+
try {
|
|
2448
|
+
await store.relinquish?.(settings.owner, clock.now());
|
|
2449
|
+
} catch (error) {
|
|
2450
|
+
notifier.failure("relinquish", error);
|
|
1839
2451
|
}
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
2452
|
+
}
|
|
2453
|
+
/** Claims one batch and resolves when every claimed delivery settled. Returns how many ran. */
|
|
2454
|
+
async processBatch() {
|
|
2455
|
+
const { started } = await this.#loop.claimAndStart();
|
|
2456
|
+
await Promise.allSettled(started.map((delivery) => delivery.settled));
|
|
2457
|
+
return started.length;
|
|
2458
|
+
}
|
|
2459
|
+
};
|
|
2460
|
+
|
|
2461
|
+
// src/handler-registry.ts
|
|
2462
|
+
var HANDLER_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$/;
|
|
2463
|
+
var HandlerRegistry = class {
|
|
2464
|
+
#byId = /* @__PURE__ */ new Map();
|
|
2465
|
+
#byClaimId = /* @__PURE__ */ new Map();
|
|
2466
|
+
#byEventType = /* @__PURE__ */ new Map();
|
|
2467
|
+
register(input, defaults) {
|
|
2468
|
+
const { options } = input;
|
|
2469
|
+
if (!HANDLER_ID_PATTERN.test(options.id)) {
|
|
2470
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(
|
|
2471
|
+
`Handler id '${options.id}' must be 1-255 characters of letters, digits, '.', '_', ':', '/' or '-'`
|
|
1848
2472
|
);
|
|
1849
|
-
return false;
|
|
1850
2473
|
}
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
} else {
|
|
1879
|
-
await this.#fail(registration, { delivery, error });
|
|
2474
|
+
const claimIds = [options.id, ...input.legacyIds ?? []];
|
|
2475
|
+
this.#assertUnregistered(claimIds);
|
|
2476
|
+
if (options.concurrency !== void 0) {
|
|
2477
|
+
chunkJ76M7XTS_cjs.PositiveInteger.of(options.concurrency, `Concurrency of handler '${options.id}'`);
|
|
2478
|
+
}
|
|
2479
|
+
const registration = {
|
|
2480
|
+
id: options.id,
|
|
2481
|
+
eventTypes: input.eventTypes,
|
|
2482
|
+
claimIds,
|
|
2483
|
+
schema: input.schema,
|
|
2484
|
+
handle: input.handle,
|
|
2485
|
+
retry: createRetryPolicy(options.retry, defaults.retry),
|
|
2486
|
+
timeoutMs: resolveTimeout(options.timeout, {
|
|
2487
|
+
fallback: defaults.timeoutMs,
|
|
2488
|
+
handlerId: options.id
|
|
2489
|
+
}),
|
|
2490
|
+
concurrency: options.concurrency ?? null,
|
|
2491
|
+
transactional: options.transactional ?? false,
|
|
2492
|
+
forwardsToTransport: input.forwardsToTransport
|
|
2493
|
+
};
|
|
2494
|
+
this.#index(registration);
|
|
2495
|
+
return registration;
|
|
2496
|
+
}
|
|
2497
|
+
#assertUnregistered(claimIds) {
|
|
2498
|
+
for (const id of claimIds) {
|
|
2499
|
+
if (this.#byClaimId.has(id)) {
|
|
2500
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(`Handler id '${id}' is already registered`);
|
|
1880
2501
|
}
|
|
1881
|
-
return false;
|
|
1882
|
-
} finally {
|
|
1883
|
-
stopHeartbeat();
|
|
1884
|
-
this.#leaseKeepers.delete(delivery);
|
|
1885
2502
|
}
|
|
1886
2503
|
}
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
const
|
|
1893
|
-
|
|
1894
|
-
registration
|
|
1895
|
-
|
|
1896
|
-
);
|
|
1897
|
-
const event = { ...delivery.event, payload };
|
|
1898
|
-
if (registration.transactional) {
|
|
1899
|
-
await store.transaction(async (transaction) => {
|
|
1900
|
-
await this.#invoke({ registration, event, delivery, controller, transaction });
|
|
1901
|
-
if (!await store.complete(delivery, clock.now(), transaction)) {
|
|
1902
|
-
throw new chunk3OHMEMZA_cjs.LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost`, {
|
|
1903
|
-
storeConfirmed: true
|
|
1904
|
-
});
|
|
1905
|
-
}
|
|
1906
|
-
});
|
|
1907
|
-
return "completed";
|
|
2504
|
+
#index(registration) {
|
|
2505
|
+
this.#byId.set(registration.id, registration);
|
|
2506
|
+
for (const id of registration.claimIds) {
|
|
2507
|
+
this.#byClaimId.set(id, registration);
|
|
2508
|
+
}
|
|
2509
|
+
for (const eventType of registration.eventTypes) {
|
|
2510
|
+
const forType = this.#byEventType.get(eventType) ?? [];
|
|
2511
|
+
forType.push(registration);
|
|
2512
|
+
this.#byEventType.set(eventType, forType);
|
|
1908
2513
|
}
|
|
1909
|
-
await this.#invoke({ registration, event, delivery, controller, transaction: void 0 });
|
|
1910
|
-
return this.#confirmCompletion(delivery);
|
|
1911
2514
|
}
|
|
1912
|
-
/**
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
2515
|
+
/** The registration that runs deliveries stored under `id`, its own id or a legacy one. */
|
|
2516
|
+
get(id) {
|
|
2517
|
+
return this.#byClaimId.get(id);
|
|
2518
|
+
}
|
|
2519
|
+
forEventType(eventType) {
|
|
2520
|
+
return this.#byEventType.get(eventType) ?? [];
|
|
2521
|
+
}
|
|
2522
|
+
all() {
|
|
2523
|
+
return [...this.#byId.values()];
|
|
2524
|
+
}
|
|
2525
|
+
};
|
|
2526
|
+
function resolveTimeout(timeout, { fallback, handlerId }) {
|
|
2527
|
+
if (timeout === void 0) {
|
|
2528
|
+
return fallback;
|
|
2529
|
+
}
|
|
2530
|
+
if (timeout === false) {
|
|
2531
|
+
return null;
|
|
2532
|
+
}
|
|
2533
|
+
return chunkJ76M7XTS_cjs.Milliseconds.forTimer(timeout, `Timeout of handler '${handlerId}'`);
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
// src/notifier.ts
|
|
2537
|
+
var Notifier = class {
|
|
2538
|
+
#observers;
|
|
2539
|
+
#logger;
|
|
2540
|
+
/** Hooks at least one observer implements, so callers skip building events nobody reads. */
|
|
2541
|
+
#implemented;
|
|
2542
|
+
constructor(observers, logger) {
|
|
2543
|
+
this.#observers = observers;
|
|
2544
|
+
this.#logger = logger;
|
|
2545
|
+
this.#implemented = new Set(
|
|
2546
|
+
observers.flatMap((observer) => hookNames(observer))
|
|
2547
|
+
);
|
|
2548
|
+
}
|
|
2549
|
+
/** True when some observer implements `method`. */
|
|
2550
|
+
wants(method) {
|
|
2551
|
+
return this.#implemented.has(method);
|
|
2552
|
+
}
|
|
2553
|
+
notify(method, argument) {
|
|
2554
|
+
for (const observer of this.#observers) {
|
|
1923
2555
|
try {
|
|
1924
|
-
|
|
2556
|
+
observer[method]?.call(
|
|
2557
|
+
observer,
|
|
2558
|
+
argument
|
|
2559
|
+
);
|
|
1925
2560
|
} catch (error) {
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
2561
|
+
this.#reportObserverFailure(observer, error);
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
failure(stage, error) {
|
|
2566
|
+
const errorInfo = chunkJ76M7XTS_cjs.toErrorInfo(error);
|
|
2567
|
+
this.#logger.error(
|
|
2568
|
+
`[Notifier](failure) relaybus ${stage} failed | ${errorInfo.name}: ${errorInfo.message}`
|
|
2569
|
+
);
|
|
2570
|
+
for (const observer of this.#observers) {
|
|
2571
|
+
try {
|
|
2572
|
+
observer.onFailure?.({ stage, error: errorInfo });
|
|
2573
|
+
} catch (observerError) {
|
|
2574
|
+
this.#logObserverFailure(observerError);
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
#reportObserverFailure(failedObserver, error) {
|
|
2579
|
+
this.#logObserverFailure(error);
|
|
2580
|
+
const errorInfo = chunkJ76M7XTS_cjs.toErrorInfo(error);
|
|
2581
|
+
for (const observer of this.#observers) {
|
|
2582
|
+
if (observer === failedObserver) {
|
|
1932
2583
|
continue;
|
|
1933
2584
|
}
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
2585
|
+
try {
|
|
2586
|
+
observer.onFailure?.({ stage: "observer", error: errorInfo });
|
|
2587
|
+
} catch (observerError) {
|
|
2588
|
+
this.#logObserverFailure(observerError);
|
|
1938
2589
|
}
|
|
1939
|
-
return "completed";
|
|
1940
2590
|
}
|
|
1941
2591
|
}
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
2592
|
+
#logObserverFailure(error) {
|
|
2593
|
+
const errorInfo = chunkJ76M7XTS_cjs.toErrorInfo(error);
|
|
2594
|
+
this.#logger.warn(
|
|
2595
|
+
`[Notifier](logObserverFailure) relaybus observer threw | ${errorInfo.name}: ${errorInfo.message}`
|
|
2596
|
+
);
|
|
1946
2597
|
}
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
transaction
|
|
1956
|
-
};
|
|
1957
|
-
const scope = transaction === void 0 ? { event } : { event, transaction };
|
|
1958
|
-
if (controller.signal.aborted) {
|
|
1959
|
-
throw controller.signal.reason;
|
|
1960
|
-
}
|
|
1961
|
-
const handle = () => runInIsolatedScope(scope, () => registration.handle(event, context));
|
|
1962
|
-
const { observation } = this.#dependencies;
|
|
1963
|
-
const work = Promise.resolve().then(
|
|
1964
|
-
observation === void 0 ? handle : () => observation.process({ registration, delivery }, handle)
|
|
2598
|
+
};
|
|
2599
|
+
function hookNames(observer) {
|
|
2600
|
+
const names = [];
|
|
2601
|
+
for (let target = observer; target !== null && target !== Object.prototype; target = Object.getPrototypeOf(target)) {
|
|
2602
|
+
names.push(
|
|
2603
|
+
...Object.getOwnPropertyNames(target).filter(
|
|
2604
|
+
(name) => typeof observer[name] === "function"
|
|
2605
|
+
)
|
|
1965
2606
|
);
|
|
1966
|
-
work.catch(() => void 0);
|
|
1967
|
-
let cancelTimeout;
|
|
1968
|
-
if (registration.timeoutMs !== null) {
|
|
1969
|
-
const timeoutMs = registration.timeoutMs;
|
|
1970
|
-
cancelTimeout = this.#timers.start({
|
|
1971
|
-
run: () => {
|
|
1972
|
-
controller.abort(
|
|
1973
|
-
new chunk3OHMEMZA_cjs.HandlerTimeoutError(
|
|
1974
|
-
`Handler '${registration.id}' did not settle within ${timeoutMs}ms`
|
|
1975
|
-
)
|
|
1976
|
-
);
|
|
1977
|
-
},
|
|
1978
|
-
delayMs: timeoutMs
|
|
1979
|
-
});
|
|
1980
|
-
}
|
|
1981
|
-
try {
|
|
1982
|
-
const settled = Promise.race([work, rejectOnAbort(controller.signal)]);
|
|
1983
|
-
await (observation === void 0 ? settled : observation.observe({ registration, delivery }, settled));
|
|
1984
|
-
} catch (error) {
|
|
1985
|
-
await this.#awaitAbandonedHandler(work, {
|
|
1986
|
-
reason: error,
|
|
1987
|
-
transactional: transaction !== void 0
|
|
1988
|
-
});
|
|
1989
|
-
throw error;
|
|
1990
|
-
} finally {
|
|
1991
|
-
cancelTimeout?.();
|
|
1992
|
-
}
|
|
1993
2607
|
}
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2608
|
+
return names;
|
|
2609
|
+
}
|
|
2610
|
+
var DEFAULTS = {
|
|
2611
|
+
concurrency: 10,
|
|
2612
|
+
pollInterval: "1s",
|
|
2613
|
+
leaseDuration: "30s",
|
|
2614
|
+
shutdownTimeout: "30s",
|
|
2615
|
+
completionBatchWindow: "2ms",
|
|
2616
|
+
completionBatchSize: 200,
|
|
2617
|
+
completedRetention: "7d",
|
|
2618
|
+
retentionInterval: "1h",
|
|
2619
|
+
routeTimeoutMs: 3e4,
|
|
2620
|
+
transactionTimeout: "5s"
|
|
2621
|
+
};
|
|
2622
|
+
function oneOf(value, { allowed, name }) {
|
|
2623
|
+
if (!allowed.includes(value)) {
|
|
2624
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(
|
|
2625
|
+
`${name} must be one of ${allowed.map((option) => `'${option}'`).join(", ")}, received '${String(value)}'`
|
|
2005
2626
|
);
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2627
|
+
}
|
|
2628
|
+
return value;
|
|
2629
|
+
}
|
|
2630
|
+
function isDispatcherEnabled(options) {
|
|
2631
|
+
return options.dispatcher?.enabled ?? true;
|
|
2632
|
+
}
|
|
2633
|
+
function resolveBacklogInterval(options) {
|
|
2634
|
+
return options.backlogInterval === void 0 ? void 0 : chunkJ76M7XTS_cjs.Milliseconds.forTimer(options.backlogInterval, "backlogInterval");
|
|
2635
|
+
}
|
|
2636
|
+
function resolveTransactionTimeout(options) {
|
|
2637
|
+
return chunkJ76M7XTS_cjs.Milliseconds.forTimer(
|
|
2638
|
+
options.transactionTimeout ?? DEFAULTS.transactionTimeout,
|
|
2639
|
+
"transactionTimeout"
|
|
2640
|
+
);
|
|
2641
|
+
}
|
|
2642
|
+
function createHandlerDefaults(options) {
|
|
2643
|
+
return {
|
|
2644
|
+
retry: createRetryPolicy(options.retry),
|
|
2645
|
+
timeoutMs: options.handlerTimeout === void 0 || options.handlerTimeout === false ? null : chunkJ76M7XTS_cjs.Milliseconds.forTimer(options.handlerTimeout, "handlerTimeout")
|
|
2646
|
+
};
|
|
2647
|
+
}
|
|
2648
|
+
function createDispatcherSettings(options, generateIdentifier) {
|
|
2649
|
+
const dispatcher = options ?? {};
|
|
2650
|
+
const concurrency = chunkJ76M7XTS_cjs.PositiveInteger.of(
|
|
2651
|
+
dispatcher.concurrency ?? DEFAULTS.concurrency,
|
|
2652
|
+
"dispatcher.concurrency"
|
|
2653
|
+
);
|
|
2654
|
+
return {
|
|
2655
|
+
owner: `${os.hostname()}:${process.pid}:${generateIdentifier()}`,
|
|
2656
|
+
concurrency,
|
|
2657
|
+
batchSize: chunkJ76M7XTS_cjs.PositiveInteger.of(dispatcher.batchSize ?? concurrency, "dispatcher.batchSize"),
|
|
2658
|
+
...createDispatcherTimings(dispatcher)
|
|
2659
|
+
};
|
|
2660
|
+
}
|
|
2661
|
+
function createDispatcherTimings(dispatcher) {
|
|
2662
|
+
return {
|
|
2663
|
+
pollIntervalMs: chunkJ76M7XTS_cjs.Milliseconds.forTimer(
|
|
2664
|
+
dispatcher.pollInterval ?? DEFAULTS.pollInterval,
|
|
2665
|
+
"dispatcher.pollInterval"
|
|
2666
|
+
),
|
|
2667
|
+
leaseDurationMs: chunkJ76M7XTS_cjs.Milliseconds.forTimer(
|
|
2668
|
+
dispatcher.leaseDuration ?? DEFAULTS.leaseDuration,
|
|
2669
|
+
"dispatcher.leaseDuration"
|
|
2670
|
+
),
|
|
2671
|
+
shutdownTimeoutMs: chunkJ76M7XTS_cjs.Milliseconds.forTimer(
|
|
2672
|
+
dispatcher.shutdownTimeout ?? DEFAULTS.shutdownTimeout,
|
|
2673
|
+
"dispatcher.shutdownTimeout"
|
|
2674
|
+
),
|
|
2675
|
+
completionBatchWindowMs: chunkJ76M7XTS_cjs.Milliseconds.of(
|
|
2676
|
+
dispatcher.completionBatchWindow ?? DEFAULTS.completionBatchWindow,
|
|
2677
|
+
"dispatcher.completionBatchWindow"
|
|
2678
|
+
),
|
|
2679
|
+
completionBatchSize: chunkJ76M7XTS_cjs.PositiveInteger.of(
|
|
2680
|
+
dispatcher.completionBatchSize ?? DEFAULTS.completionBatchSize,
|
|
2681
|
+
"dispatcher.completionBatchSize"
|
|
2682
|
+
)
|
|
2683
|
+
};
|
|
2684
|
+
}
|
|
2685
|
+
function createRetentionSettings(options) {
|
|
2686
|
+
const completed = options?.completed ?? DEFAULTS.completedRetention;
|
|
2687
|
+
const dead = options?.dead ?? false;
|
|
2688
|
+
return {
|
|
2689
|
+
completedMs: completed === false ? null : chunkJ76M7XTS_cjs.Milliseconds.of(completed, "retention.completed"),
|
|
2690
|
+
deadMs: dead === false ? null : chunkJ76M7XTS_cjs.Milliseconds.of(dead, "retention.dead"),
|
|
2691
|
+
intervalMs: chunkJ76M7XTS_cjs.Milliseconds.forTimer(
|
|
2692
|
+
options?.interval ?? DEFAULTS.retentionInterval,
|
|
2693
|
+
"retention.interval"
|
|
2694
|
+
)
|
|
2695
|
+
};
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
// src/route-groups.ts
|
|
2699
|
+
var TRANSPORT_HANDLER_PREFIX = "relaybus.transport";
|
|
2700
|
+
function groupRoutes(routes) {
|
|
2701
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2702
|
+
for (const route of routes) {
|
|
2703
|
+
const type = routedType(route.event);
|
|
2704
|
+
const destination = route.destination ?? type;
|
|
2705
|
+
const id = `${TRANSPORT_HANDLER_PREFIX}:${route.transport}:${destination}`;
|
|
2706
|
+
const group = groups.get(id);
|
|
2707
|
+
if (group === void 0) {
|
|
2708
|
+
groups.set(id, createGroup(route, { id, destination, type }));
|
|
2709
|
+
} else {
|
|
2710
|
+
addRoute(group, { route, type, destination });
|
|
2012
2711
|
}
|
|
2013
|
-
const deadline = Date.now() + this.#dependencies.settings.leaseDurationMs;
|
|
2014
|
-
await this.#waitUntil(settledWork, deadline);
|
|
2015
2712
|
}
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2713
|
+
return [...groups.values()];
|
|
2714
|
+
}
|
|
2715
|
+
function createGroup(route, {
|
|
2716
|
+
id,
|
|
2717
|
+
destination,
|
|
2718
|
+
type
|
|
2719
|
+
}) {
|
|
2720
|
+
return {
|
|
2721
|
+
transport: route.transport,
|
|
2722
|
+
destination,
|
|
2723
|
+
id,
|
|
2724
|
+
eventTypes: [type],
|
|
2725
|
+
legacyIds: [`${id}:${type}`],
|
|
2726
|
+
route,
|
|
2727
|
+
...resolveSettings(route, id)
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
function addRoute(group, {
|
|
2731
|
+
route,
|
|
2732
|
+
type,
|
|
2733
|
+
destination
|
|
2734
|
+
}) {
|
|
2735
|
+
const target = `'${group.destination}' of transport '${group.transport}'`;
|
|
2736
|
+
if (route.transport !== group.transport) {
|
|
2737
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(
|
|
2738
|
+
`Routes to ${target} and to '${destination}' of transport '${route.transport}' would share the handler id '${group.id}'; rename the transport or the destination`
|
|
2739
|
+
);
|
|
2740
|
+
}
|
|
2741
|
+
if (group.eventTypes.includes(type)) {
|
|
2742
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(`Event type ${type} is routed to ${target} twice`);
|
|
2743
|
+
}
|
|
2744
|
+
const { retry, timeoutMs } = resolveSettings(route, group.id);
|
|
2745
|
+
if (timeoutMs !== group.timeoutMs || !sameRetryPolicy(retry, group.retry)) {
|
|
2746
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(
|
|
2747
|
+
`Routes to ${target} must share retry and timeout, because their events are sent in one queue per key | types=${group.eventTypes[0]},${type}`
|
|
2748
|
+
);
|
|
2749
|
+
}
|
|
2750
|
+
group.eventTypes.push(type);
|
|
2751
|
+
group.legacyIds.push(`${group.id}:${type}`);
|
|
2752
|
+
}
|
|
2753
|
+
function resolveSettings(route, id) {
|
|
2754
|
+
return {
|
|
2755
|
+
retry: createRetryPolicy(route.retry, DEFAULT_ROUTE_RETRY_POLICY),
|
|
2756
|
+
timeoutMs: route.timeout === void 0 ? DEFAULTS.routeTimeoutMs : chunkJ76M7XTS_cjs.Milliseconds.forTimer(route.timeout, `Timeout of handler '${id}'`)
|
|
2757
|
+
};
|
|
2758
|
+
}
|
|
2759
|
+
function sameRetryPolicy(left, right) {
|
|
2760
|
+
return Object.keys(left).every(
|
|
2761
|
+
(field) => left[field] === right[field]
|
|
2762
|
+
);
|
|
2763
|
+
}
|
|
2764
|
+
function routedType(event) {
|
|
2765
|
+
const type = typeof event === "string" ? event : event.type;
|
|
2766
|
+
chunkJ76M7XTS_cjs.assertEventType(type);
|
|
2767
|
+
return type;
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
// src/bus/bus-connections.ts
|
|
2771
|
+
var BusConnections = class {
|
|
2772
|
+
#parts;
|
|
2773
|
+
#transports = /* @__PURE__ */ new Map();
|
|
2774
|
+
#subscriptions;
|
|
2775
|
+
constructor(parts, subscriptions) {
|
|
2776
|
+
this.#parts = parts;
|
|
2777
|
+
this.#subscriptions = subscriptions;
|
|
2778
|
+
}
|
|
2779
|
+
/** Every transport, in the order it was configured. */
|
|
2780
|
+
get transports() {
|
|
2781
|
+
return this.#transports.values();
|
|
2782
|
+
}
|
|
2783
|
+
/** Registers the transports, then the routes, and checks the subscriptions. */
|
|
2784
|
+
register({ transports, routes }) {
|
|
2785
|
+
for (const transport of transports) {
|
|
2786
|
+
if (this.#transports.has(transport.name)) {
|
|
2787
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(`Transport name '${transport.name}' is used twice`);
|
|
2788
|
+
}
|
|
2789
|
+
this.#transports.set(transport.name, transport);
|
|
2790
|
+
}
|
|
2791
|
+
this.#registerRoutes(routes);
|
|
2792
|
+
for (const subscription of this.#subscriptions) {
|
|
2793
|
+
const transport = this.#requireTransport(subscription.transport);
|
|
2794
|
+
if (transport.subscribe === void 0) {
|
|
2795
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(`Transport '${transport.name}' cannot subscribe`);
|
|
2036
2796
|
}
|
|
2037
2797
|
}
|
|
2038
|
-
return chunk3OHMEMZA_cjs.schedule(clock, {
|
|
2039
|
-
run: () => {
|
|
2040
|
-
controller.abort(new chunk3OHMEMZA_cjs.LeaseLostError(`Lease on delivery ${delivery.deliveryId} expired`));
|
|
2041
|
-
},
|
|
2042
|
-
delayMs: Math.max(0, expiry - clock.now().getTime()),
|
|
2043
|
-
unref: true
|
|
2044
|
-
});
|
|
2045
2798
|
}
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2799
|
+
/** True while every transport reports itself ready. */
|
|
2800
|
+
allReady() {
|
|
2801
|
+
for (const transport of this.#transports.values()) {
|
|
2802
|
+
if (transport.isReady?.() === false) {
|
|
2803
|
+
return false;
|
|
2804
|
+
}
|
|
2051
2805
|
}
|
|
2052
|
-
|
|
2806
|
+
return true;
|
|
2053
2807
|
}
|
|
2054
|
-
/**
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
const
|
|
2061
|
-
|
|
2808
|
+
/** The inbound subscriptions, each with a transport that can subscribe. */
|
|
2809
|
+
get subscriptions() {
|
|
2810
|
+
return this.#subscriptions;
|
|
2811
|
+
}
|
|
2812
|
+
/** Subscribes one subscription, handing its messages to `receive`; the transport's own promise. */
|
|
2813
|
+
subscribe(subscription, receive) {
|
|
2814
|
+
const transport = this.#transports.get(subscription.transport);
|
|
2815
|
+
return transport.subscribe(
|
|
2816
|
+
subscription.destinations,
|
|
2817
|
+
async (envelope, message) => {
|
|
2818
|
+
await receive(envelope, { transport, destination: message?.destination });
|
|
2819
|
+
},
|
|
2820
|
+
this.#subscriptionHooks(transport)
|
|
2062
2821
|
);
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
}
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
delivery,
|
|
2070
|
-
settle: () => store.reschedule(delivery, availableAt, errorInfo, now),
|
|
2071
|
-
applied: () => notifier.notify("onDeliveryRetryScheduled", { delivery, error: errorInfo, availableAt })
|
|
2072
|
-
});
|
|
2073
|
-
return;
|
|
2074
|
-
}
|
|
2075
|
-
if (rescheduled) {
|
|
2076
|
-
notifier.notify("onDeliveryRetryScheduled", { delivery, error: errorInfo, availableAt });
|
|
2077
|
-
} else {
|
|
2078
|
-
notifier.notify("onLeaseLost", { delivery });
|
|
2822
|
+
}
|
|
2823
|
+
/** What a transport may report back while subscribed; undefined when no observer wants it. */
|
|
2824
|
+
#subscriptionHooks(transport) {
|
|
2825
|
+
const { notifier } = this.#parts;
|
|
2826
|
+
if (!notifier.wants("onInvalidMessage")) {
|
|
2827
|
+
return void 0;
|
|
2079
2828
|
}
|
|
2829
|
+
return {
|
|
2830
|
+
onInvalidMessage: ({ destination, error }) => {
|
|
2831
|
+
const errorInfo = chunkJ76M7XTS_cjs.toErrorInfo(error);
|
|
2832
|
+
notifier.notify("onInvalidMessage", {
|
|
2833
|
+
transport: transport.name,
|
|
2834
|
+
system: transport.system ?? transport.name,
|
|
2835
|
+
destination,
|
|
2836
|
+
reason: errorInfo.message,
|
|
2837
|
+
error: errorInfo
|
|
2838
|
+
});
|
|
2839
|
+
}
|
|
2840
|
+
};
|
|
2080
2841
|
}
|
|
2081
|
-
|
|
2082
|
-
const
|
|
2083
|
-
|
|
2084
|
-
let buried;
|
|
2085
|
-
try {
|
|
2086
|
-
buried = await store.bury(delivery, errorInfo, clock.now());
|
|
2087
|
-
} catch (storeError) {
|
|
2088
|
-
notifier.failure("bury", storeError);
|
|
2089
|
-
this.#retain({
|
|
2090
|
-
delivery,
|
|
2091
|
-
settle: () => store.bury(delivery, errorInfo, clock.now()),
|
|
2092
|
-
applied: () => notifier.notify("onDeliveryDead", { delivery, error: errorInfo })
|
|
2093
|
-
});
|
|
2094
|
-
return;
|
|
2842
|
+
#registerRoutes(routes) {
|
|
2843
|
+
for (const route of routes) {
|
|
2844
|
+
this.#requireTransport(route.transport);
|
|
2095
2845
|
}
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
} else {
|
|
2099
|
-
notifier.notify("onLeaseLost", { delivery });
|
|
2846
|
+
for (const group of groupRoutes(routes)) {
|
|
2847
|
+
this.#registerRoute(group);
|
|
2100
2848
|
}
|
|
2101
2849
|
}
|
|
2102
|
-
/**
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2850
|
+
/** Registers one handler for every route to a destination, so a key's events reach it in commit order. */
|
|
2851
|
+
#registerRoute(group) {
|
|
2852
|
+
const transport = this.#transports.get(group.transport);
|
|
2853
|
+
const { route, destination } = group;
|
|
2854
|
+
this.#parts.registry.register(
|
|
2855
|
+
{
|
|
2856
|
+
eventTypes: group.eventTypes,
|
|
2857
|
+
legacyIds: group.legacyIds,
|
|
2858
|
+
schema: void 0,
|
|
2859
|
+
handle: this.#parts.observation?.sender(transport, destination) ?? ((envelope) => transport.publish(envelope, destination)),
|
|
2860
|
+
options: {
|
|
2861
|
+
id: group.id,
|
|
2862
|
+
...route.retry === void 0 ? {} : { retry: route.retry },
|
|
2863
|
+
...route.timeout === void 0 ? {} : { timeout: route.timeout }
|
|
2864
|
+
},
|
|
2865
|
+
forwardsToTransport: true
|
|
2866
|
+
},
|
|
2867
|
+
{ retry: DEFAULT_ROUTE_RETRY_POLICY, timeoutMs: DEFAULTS.routeTimeoutMs }
|
|
2868
|
+
);
|
|
2116
2869
|
}
|
|
2117
|
-
|
|
2118
|
-
const
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
released = await store.release(delivery, clock.now());
|
|
2122
|
-
} catch (storeError) {
|
|
2123
|
-
notifier.failure("release", storeError);
|
|
2124
|
-
this.#retain({
|
|
2125
|
-
delivery,
|
|
2126
|
-
settle: () => store.release(delivery, clock.now()),
|
|
2127
|
-
applied: () => notifier.notify("onDeliveryReleased", { delivery })
|
|
2128
|
-
});
|
|
2129
|
-
return;
|
|
2130
|
-
}
|
|
2131
|
-
if (released) {
|
|
2132
|
-
notifier.notify("onDeliveryReleased", { delivery });
|
|
2133
|
-
} else {
|
|
2134
|
-
notifier.notify("onLeaseLost", { delivery });
|
|
2870
|
+
#requireTransport(name) {
|
|
2871
|
+
const transport = this.#transports.get(name);
|
|
2872
|
+
if (transport === void 0) {
|
|
2873
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(`No transport named '${name}' is configured`);
|
|
2135
2874
|
}
|
|
2875
|
+
return transport;
|
|
2136
2876
|
}
|
|
2137
2877
|
};
|
|
2138
2878
|
|
|
2139
|
-
// src/
|
|
2140
|
-
var
|
|
2141
|
-
|
|
2142
|
-
#
|
|
2143
|
-
#
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
const { options } = input;
|
|
2147
|
-
if (!HANDLER_ID_PATTERN.test(options.id)) {
|
|
2148
|
-
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
2149
|
-
`Handler id '${options.id}' must be 1-255 characters of letters, digits, '.', '_', ':', '/' or '-'`
|
|
2150
|
-
);
|
|
2151
|
-
}
|
|
2152
|
-
const claimIds = [options.id, ...input.legacyIds ?? []];
|
|
2153
|
-
this.#assertUnregistered(claimIds);
|
|
2154
|
-
if (options.concurrency !== void 0) {
|
|
2155
|
-
chunk3OHMEMZA_cjs.assertPositiveInteger(options.concurrency, `Concurrency of handler '${options.id}'`);
|
|
2156
|
-
}
|
|
2157
|
-
const registration = {
|
|
2158
|
-
id: options.id,
|
|
2159
|
-
eventTypes: input.eventTypes,
|
|
2160
|
-
claimIds,
|
|
2161
|
-
schema: input.schema,
|
|
2162
|
-
handle: input.handle,
|
|
2163
|
-
retry: createRetryPolicy(options.retry, defaults.retry),
|
|
2164
|
-
timeoutMs: resolveTimeout(options.timeout, {
|
|
2165
|
-
fallback: defaults.timeoutMs,
|
|
2166
|
-
handlerId: options.id
|
|
2167
|
-
}),
|
|
2168
|
-
concurrency: options.concurrency ?? null,
|
|
2169
|
-
transactional: options.transactional ?? false,
|
|
2170
|
-
forwardsToTransport: input.forwardsToTransport
|
|
2171
|
-
};
|
|
2172
|
-
this.#index(registration);
|
|
2173
|
-
return registration;
|
|
2879
|
+
// src/bus/bus-maintenance.ts
|
|
2880
|
+
var BusMaintenance = class {
|
|
2881
|
+
#parts;
|
|
2882
|
+
#retentionTimer;
|
|
2883
|
+
#backlogTimer;
|
|
2884
|
+
constructor(parts) {
|
|
2885
|
+
this.#parts = parts;
|
|
2174
2886
|
}
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2887
|
+
/** Applies the retention policy once. */
|
|
2888
|
+
async purgeExpired() {
|
|
2889
|
+
const { store, clock, notifier, retention } = this.#parts;
|
|
2890
|
+
const now = clock.now().getTime();
|
|
2891
|
+
const { completedMs, deadMs } = retention;
|
|
2892
|
+
try {
|
|
2893
|
+
const result = await store.purge({
|
|
2894
|
+
...completedMs === null ? {} : { completedBefore: new Date(now - completedMs) },
|
|
2895
|
+
...deadMs === null ? {} : { deadBefore: new Date(now - deadMs) }
|
|
2896
|
+
});
|
|
2897
|
+
notifier.notify("onPurged", { result });
|
|
2898
|
+
return result;
|
|
2899
|
+
} catch (error) {
|
|
2900
|
+
notifier.failure("purge", error);
|
|
2901
|
+
throw error;
|
|
2180
2902
|
}
|
|
2181
2903
|
}
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
}
|
|
2187
|
-
for (const eventType of registration.eventTypes) {
|
|
2188
|
-
const forType = this.#byEventType.get(eventType) ?? [];
|
|
2189
|
-
forType.push(registration);
|
|
2190
|
-
this.#byEventType.set(eventType, forType);
|
|
2191
|
-
}
|
|
2904
|
+
/** Starts the retention purge and the backlog report, each when it has anything to do. */
|
|
2905
|
+
start() {
|
|
2906
|
+
this.#scheduleRetention();
|
|
2907
|
+
this.#scheduleBacklog();
|
|
2192
2908
|
}
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2909
|
+
stop() {
|
|
2910
|
+
this.#retentionTimer?.();
|
|
2911
|
+
this.#backlogTimer?.();
|
|
2196
2912
|
}
|
|
2197
|
-
|
|
2198
|
-
|
|
2913
|
+
/** Applies the retention policy every `retention.interval` while the bus runs, when it deletes anything. */
|
|
2914
|
+
#scheduleRetention() {
|
|
2915
|
+
const { clock, retention } = this.#parts;
|
|
2916
|
+
if (retention.completedMs !== null || retention.deadMs !== null) {
|
|
2917
|
+
this.#retentionTimer = chunkJ76M7XTS_cjs.schedule(clock, {
|
|
2918
|
+
run: () => {
|
|
2919
|
+
this.purgeExpired().catch(() => void 0);
|
|
2920
|
+
},
|
|
2921
|
+
delayMs: retention.intervalMs,
|
|
2922
|
+
repeat: true,
|
|
2923
|
+
unref: true
|
|
2924
|
+
});
|
|
2925
|
+
}
|
|
2199
2926
|
}
|
|
2200
|
-
|
|
2201
|
-
|
|
2927
|
+
/** Reports the backlog every `backlogInterval` while the bus runs, when an observer wants it. */
|
|
2928
|
+
#scheduleBacklog() {
|
|
2929
|
+
const { observation, backlogIntervalMs, statistics } = this.#parts;
|
|
2930
|
+
if (backlogIntervalMs !== void 0) {
|
|
2931
|
+
this.#backlogTimer = observation?.startBacklog({ intervalMs: backlogIntervalMs, statistics });
|
|
2932
|
+
}
|
|
2202
2933
|
}
|
|
2203
2934
|
};
|
|
2204
|
-
function resolveTimeout(timeout, { fallback, handlerId }) {
|
|
2205
|
-
if (timeout === void 0) {
|
|
2206
|
-
return fallback;
|
|
2207
|
-
}
|
|
2208
|
-
if (timeout === false) {
|
|
2209
|
-
return null;
|
|
2210
|
-
}
|
|
2211
|
-
return toTimerMilliseconds(timeout, `Timeout of handler '${handlerId}'`);
|
|
2212
|
-
}
|
|
2213
2935
|
|
|
2214
|
-
// src/
|
|
2215
|
-
var
|
|
2216
|
-
#
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
#implemented;
|
|
2220
|
-
constructor(observers, logger) {
|
|
2221
|
-
this.#observers = observers;
|
|
2222
|
-
this.#logger = logger;
|
|
2223
|
-
this.#implemented = new Set(
|
|
2224
|
-
observers.flatMap((observer) => hookNames(observer))
|
|
2225
|
-
);
|
|
2936
|
+
// src/bus/event-publisher.ts
|
|
2937
|
+
var EventPublisher = class {
|
|
2938
|
+
#parts;
|
|
2939
|
+
constructor(parts) {
|
|
2940
|
+
this.#parts = parts;
|
|
2226
2941
|
}
|
|
2227
|
-
/**
|
|
2228
|
-
|
|
2229
|
-
|
|
2942
|
+
/** Publishes inside the tracer's span, with its trace headers, when the bus is observed. */
|
|
2943
|
+
publish(events, call) {
|
|
2944
|
+
const { observation } = this.#parts;
|
|
2945
|
+
if (observation === void 0) {
|
|
2946
|
+
return this.#publish(events, call);
|
|
2947
|
+
}
|
|
2948
|
+
return observation.publish(
|
|
2949
|
+
{ eventTypes: events.map((event) => String(event.type)) },
|
|
2950
|
+
(headers) => this.#publish(events, { ...call, options: withTraceHeaders(call.options, headers) })
|
|
2951
|
+
);
|
|
2230
2952
|
}
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2953
|
+
async #publish(events, { options, id }) {
|
|
2954
|
+
if (events.length === 0) {
|
|
2955
|
+
return [];
|
|
2956
|
+
}
|
|
2957
|
+
const now = this.#parts.clock.now();
|
|
2958
|
+
const delayMs = options.delay === void 0 ? 0 : chunkJ76M7XTS_cjs.Milliseconds.of(options.delay, "delay");
|
|
2959
|
+
const availableAt = new Date(now.getTime() + delayMs);
|
|
2960
|
+
const context = { options, id, scope: getCurrentScope(), now };
|
|
2961
|
+
const entries = await this.#createEntries(events, { context, availableAt });
|
|
2962
|
+
const deliverable = entries.filter((entry) => entry.deliveries.length > 0);
|
|
2963
|
+
if (deliverable.length > 0) {
|
|
2964
|
+
await this.#appendDeliverable(deliverable, { context, delayMs });
|
|
2241
2965
|
}
|
|
2966
|
+
return entries.map((entry) => entry.event);
|
|
2242
2967
|
}
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2968
|
+
/** One entry per event, in order: its envelope and a delivery for every registration of its type. */
|
|
2969
|
+
async #createEntries(events, {
|
|
2970
|
+
context,
|
|
2971
|
+
availableAt
|
|
2972
|
+
}) {
|
|
2973
|
+
const { registry, createDelivery, reportUnrouted } = this.#parts;
|
|
2974
|
+
const entries = [];
|
|
2975
|
+
const { options, scope } = context;
|
|
2976
|
+
let correlationId = options.correlationId ?? scope?.event?.correlationId ?? scope?.event?.id;
|
|
2977
|
+
for (const event of events) {
|
|
2978
|
+
const envelope = await this.#createEnvelope({ ...context, event, correlationId });
|
|
2979
|
+
correlationId = envelope.correlationId;
|
|
2980
|
+
const registrations = registry.forEventType(envelope.type);
|
|
2981
|
+
if (registrations.length === 0) {
|
|
2982
|
+
reportUnrouted(envelope);
|
|
2253
2983
|
}
|
|
2984
|
+
entries.push({
|
|
2985
|
+
event: envelope,
|
|
2986
|
+
deliveries: registrations.map((registration) => createDelivery(registration, availableAt))
|
|
2987
|
+
});
|
|
2254
2988
|
}
|
|
2989
|
+
return entries;
|
|
2255
2990
|
}
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2991
|
+
/** Appends the entries that have deliveries, reports what was new and wakes the dispatcher when it is due now. */
|
|
2992
|
+
async #appendDeliverable(entries, {
|
|
2993
|
+
context,
|
|
2994
|
+
delayMs
|
|
2995
|
+
}) {
|
|
2996
|
+
const { result, committed } = await this.#appendInTransaction(entries, context);
|
|
2997
|
+
const appended = new Set(result.appendedEventIds);
|
|
2998
|
+
const published = entries.map((entry) => entry.event).filter((envelope) => appended.has(envelope.id));
|
|
2999
|
+
if (published.length > 0) {
|
|
3000
|
+
this.#parts.notifier.notify("onPublished", {
|
|
3001
|
+
envelopes: published,
|
|
3002
|
+
deliveries: result.appendedDeliveryCount
|
|
3003
|
+
});
|
|
3004
|
+
if (delayMs === 0) {
|
|
3005
|
+
this.#parts.scheduleWake(committed);
|
|
2267
3006
|
}
|
|
2268
3007
|
}
|
|
2269
3008
|
}
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
this.#
|
|
2273
|
-
|
|
3009
|
+
/** Appends in the caller's transaction, else in one of the store's; `committed` tells which. */
|
|
3010
|
+
async #appendInTransaction(entries, { options, scope, now }) {
|
|
3011
|
+
const { store, transactionMode, transactionResolver } = this.#parts;
|
|
3012
|
+
const transaction = options.transaction ?? scope?.transaction ?? transactionResolver?.();
|
|
3013
|
+
if (transaction !== void 0) {
|
|
3014
|
+
return { result: await store.append(transaction, entries, now), committed: false };
|
|
3015
|
+
}
|
|
3016
|
+
if (transactionMode === "required") {
|
|
3017
|
+
throw new chunkJ76M7XTS_cjs.TransactionRequiredError(
|
|
3018
|
+
"publish found no transaction. Pass { transaction }, publish inside bus.transaction(), configure transactionResolver, or set transactionMode to optional"
|
|
3019
|
+
);
|
|
3020
|
+
}
|
|
3021
|
+
const result = await store.transaction(
|
|
3022
|
+
(ownTransaction) => store.append(ownTransaction, entries, now)
|
|
2274
3023
|
);
|
|
3024
|
+
return { result, committed: true };
|
|
2275
3025
|
}
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
3026
|
+
async #createEnvelope(input) {
|
|
3027
|
+
const { event, options, id, scope, now, correlationId } = input;
|
|
3028
|
+
if (typeof event.type !== "string") {
|
|
3029
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(
|
|
3030
|
+
"publish expects an instance of a class created with defineEvent"
|
|
3031
|
+
);
|
|
3032
|
+
}
|
|
3033
|
+
chunkJ76M7XTS_cjs.assertEventType(event.type);
|
|
3034
|
+
const schema = event.constructor.schema;
|
|
3035
|
+
const payload = await chunkJ76M7XTS_cjs.validatePayload(event.type, schema, event.payload);
|
|
3036
|
+
if (options.key !== void 0 && options.key.length === 0) {
|
|
3037
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError("key must not be empty");
|
|
3038
|
+
}
|
|
3039
|
+
const eventId = id ?? this.#parts.generateIdentifier();
|
|
3040
|
+
return {
|
|
3041
|
+
id: eventId,
|
|
3042
|
+
type: event.type,
|
|
3043
|
+
source: this.#parts.source,
|
|
3044
|
+
key: options.key ?? chunkJ76M7XTS_cjs.keyFromDefinition(event, payload),
|
|
3045
|
+
payload,
|
|
3046
|
+
headers: { ...options.headers },
|
|
3047
|
+
correlationId: correlationId ?? eventId,
|
|
3048
|
+
causationId: scope?.event?.id ?? null,
|
|
3049
|
+
occurredAt: now
|
|
3050
|
+
};
|
|
2285
3051
|
}
|
|
2286
|
-
|
|
2287
|
-
}
|
|
3052
|
+
};
|
|
2288
3053
|
|
|
2289
3054
|
// src/received-envelope.ts
|
|
2290
3055
|
function normalizeEnvelope(envelope) {
|
|
2291
3056
|
if (typeof envelope.id !== "string" || envelope.id.length === 0) {
|
|
2292
|
-
throw new
|
|
3057
|
+
throw new chunkJ76M7XTS_cjs.InvalidEnvelopeError("Received event has no id");
|
|
2293
3058
|
}
|
|
2294
3059
|
if (typeof envelope.type !== "string") {
|
|
2295
|
-
throw new
|
|
3060
|
+
throw new chunkJ76M7XTS_cjs.InvalidEnvelopeError(`Received event ${envelope.id} has no type`);
|
|
2296
3061
|
}
|
|
2297
3062
|
try {
|
|
2298
|
-
|
|
3063
|
+
chunkJ76M7XTS_cjs.assertEventType(envelope.type);
|
|
2299
3064
|
} catch (error) {
|
|
2300
|
-
throw new
|
|
3065
|
+
throw new chunkJ76M7XTS_cjs.InvalidEnvelopeError(chunkJ76M7XTS_cjs.toErrorInfo(error).message);
|
|
2301
3066
|
}
|
|
2302
3067
|
const occurredAt = toDate(envelope.occurredAt);
|
|
2303
3068
|
if (occurredAt === void 0) {
|
|
2304
|
-
throw new
|
|
3069
|
+
throw new chunkJ76M7XTS_cjs.InvalidEnvelopeError(`Received event ${envelope.id} has an invalid occurredAt`);
|
|
2305
3070
|
}
|
|
2306
3071
|
return {
|
|
2307
3072
|
id: envelope.id,
|
|
@@ -2322,165 +3087,178 @@ function toDate(value) {
|
|
|
2322
3087
|
function optionalText(value) {
|
|
2323
3088
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
2324
3089
|
}
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
}
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
3090
|
+
|
|
3091
|
+
// src/bus/event-receiver.ts
|
|
3092
|
+
var EventReceiver = class {
|
|
3093
|
+
#parts;
|
|
3094
|
+
constructor(parts) {
|
|
3095
|
+
this.#parts = parts;
|
|
3096
|
+
}
|
|
3097
|
+
/** Stores `envelope` for local handlers; observed when the bus is, with where it came from. */
|
|
3098
|
+
receive(envelope, origin) {
|
|
3099
|
+
const { observation } = this.#parts;
|
|
3100
|
+
return observation === void 0 ? this.#receive(envelope) : observation.receive({ envelope, origin }, () => this.#receive(envelope));
|
|
3101
|
+
}
|
|
3102
|
+
async #receive(envelope) {
|
|
3103
|
+
const { store, registry, clock, notifier, createDelivery, scheduleWake } = this.#parts;
|
|
3104
|
+
const normalized = normalizeEnvelope(envelope);
|
|
3105
|
+
const registrations = registry.forEventType(normalized.type).filter((registration) => !registration.forwardsToTransport);
|
|
3106
|
+
if (registrations.length === 0) {
|
|
3107
|
+
notifier.notify("onReceived", { envelope: normalized, status: "ignored" });
|
|
3108
|
+
return "ignored";
|
|
3109
|
+
}
|
|
3110
|
+
const now = clock.now();
|
|
3111
|
+
const entry = {
|
|
3112
|
+
event: normalized,
|
|
3113
|
+
deliveries: registrations.map((registration) => createDelivery(registration, now))
|
|
3114
|
+
};
|
|
3115
|
+
const result = await store.transaction(
|
|
3116
|
+
(transaction) => store.append(transaction, [entry], now)
|
|
2341
3117
|
);
|
|
3118
|
+
const status = result.duplicateEventIds.length > 0 ? "duplicate" : "accepted";
|
|
3119
|
+
notifier.notify("onReceived", { envelope: normalized, status });
|
|
3120
|
+
if (result.appendedDeliveryCount > 0) {
|
|
3121
|
+
scheduleWake(true);
|
|
3122
|
+
}
|
|
3123
|
+
return status;
|
|
2342
3124
|
}
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
);
|
|
2356
|
-
}
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
);
|
|
3125
|
+
};
|
|
3126
|
+
|
|
3127
|
+
// src/bus/relay-bus-assembly.ts
|
|
3128
|
+
function assembleRelayBus(options, hooks) {
|
|
3129
|
+
const core = resolveCore(options);
|
|
3130
|
+
const backlogIntervalMs = resolveBacklogInterval(options);
|
|
3131
|
+
const maintenance = new BusMaintenance({
|
|
3132
|
+
...core,
|
|
3133
|
+
retention: createRetentionSettings(options.retention),
|
|
3134
|
+
backlogIntervalMs,
|
|
3135
|
+
statistics: hooks.statistics
|
|
3136
|
+
});
|
|
3137
|
+
const connections = new BusConnections(core, options.subscriptions ?? []);
|
|
3138
|
+
connections.register({ transports: options.transports ?? [], routes: options.routes ?? [] });
|
|
3139
|
+
const catalogRequirements = [];
|
|
3140
|
+
for (const catalog of options.catalogs ?? []) {
|
|
3141
|
+
catalogRequirements.push(toCatalogRequirement(catalog));
|
|
3142
|
+
}
|
|
3143
|
+
const dispatcherEnabled = isDispatcherEnabled(options);
|
|
3144
|
+
const settings = createDispatcherSettings(options.dispatcher, core.generateIdentifier);
|
|
3145
|
+
const dispatcher = new Dispatcher({
|
|
3146
|
+
...core,
|
|
3147
|
+
random: options.random ?? Math.random,
|
|
3148
|
+
settings,
|
|
3149
|
+
observation: DispatchObservation.create({ ...core, tracer: options.tracer })
|
|
3150
|
+
});
|
|
2369
3151
|
return {
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
3152
|
+
...core,
|
|
3153
|
+
maintenance,
|
|
3154
|
+
connections,
|
|
3155
|
+
catalogRequirements,
|
|
3156
|
+
dispatcherEnabled,
|
|
3157
|
+
shutdownTimeoutMs: settings.shutdownTimeoutMs,
|
|
3158
|
+
dispatcher,
|
|
3159
|
+
...createInboxAndOutbox(core, hooks)
|
|
2374
3160
|
};
|
|
2375
3161
|
}
|
|
2376
|
-
function
|
|
3162
|
+
function resolveCore(options) {
|
|
3163
|
+
const source = options.source ?? "relaybus";
|
|
3164
|
+
const transactionMode = oneOf(options.transactionMode ?? "required", {
|
|
3165
|
+
allowed: ["required", "optional"],
|
|
3166
|
+
name: "transactionMode"
|
|
3167
|
+
});
|
|
3168
|
+
const unroutedEvents = oneOf(options.unroutedEvents ?? "warn", {
|
|
3169
|
+
allowed: ["warn", "throw"],
|
|
3170
|
+
name: "unroutedEvents"
|
|
3171
|
+
});
|
|
3172
|
+
const transactionTimeoutMs = resolveTransactionTimeout(options);
|
|
3173
|
+
const defaults = createHandlerDefaults(options);
|
|
3174
|
+
const logger = options.logger ?? console;
|
|
3175
|
+
const notifier = new Notifier(options.observers ?? [], logger);
|
|
3176
|
+
const clock = options.clock ?? systemClock;
|
|
2377
3177
|
return {
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
),
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
"dispatcher.completionBatchWindow"
|
|
2393
|
-
),
|
|
2394
|
-
completionBatchSize: chunk3OHMEMZA_cjs.assertPositiveInteger(
|
|
2395
|
-
dispatcher.completionBatchSize ?? DEFAULTS.completionBatchSize,
|
|
2396
|
-
"dispatcher.completionBatchSize"
|
|
2397
|
-
)
|
|
3178
|
+
store: options.store,
|
|
3179
|
+
source,
|
|
3180
|
+
transactionMode,
|
|
3181
|
+
unroutedEvents,
|
|
3182
|
+
transactionResolver: options.transactionResolver,
|
|
3183
|
+
transactionTimeoutMs,
|
|
3184
|
+
registry: new HandlerRegistry(),
|
|
3185
|
+
defaults,
|
|
3186
|
+
logger,
|
|
3187
|
+
notifier,
|
|
3188
|
+
clock,
|
|
3189
|
+
observation: BusObservation.create({ notifier, tracer: options.tracer, clock }),
|
|
3190
|
+
generateIdentifier: options.generateIdentifier ?? (() => createUuidV7()),
|
|
3191
|
+
setupStore: options.setupStore ?? true
|
|
2398
3192
|
};
|
|
2399
3193
|
}
|
|
2400
|
-
function
|
|
2401
|
-
const
|
|
2402
|
-
|
|
3194
|
+
function createInboxAndOutbox(core, hooks) {
|
|
3195
|
+
const createDelivery = (registration, availableAt) => ({
|
|
3196
|
+
id: core.generateIdentifier(),
|
|
3197
|
+
handler: registration.id,
|
|
3198
|
+
availableAt
|
|
3199
|
+
});
|
|
2403
3200
|
return {
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
)
|
|
3201
|
+
publisher: new EventPublisher({
|
|
3202
|
+
...core,
|
|
3203
|
+
createDelivery,
|
|
3204
|
+
reportUnrouted: hooks.reportUnrouted,
|
|
3205
|
+
scheduleWake: hooks.scheduleWake
|
|
3206
|
+
}),
|
|
3207
|
+
receiver: new EventReceiver({ ...core, createDelivery, scheduleWake: hooks.scheduleWake })
|
|
2410
3208
|
};
|
|
2411
3209
|
}
|
|
2412
3210
|
|
|
2413
|
-
// src/
|
|
2414
|
-
var
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
3211
|
+
// src/bus/publish-wake-ups.ts
|
|
3212
|
+
var FIRST_WAKE_MS = 25;
|
|
3213
|
+
var SECOND_WAKE_MS = 100;
|
|
3214
|
+
var LAST_WAKE_MS = 250;
|
|
3215
|
+
var WAKE_DELAYS_MS = [FIRST_WAKE_MS, SECOND_WAKE_MS, LAST_WAKE_MS];
|
|
3216
|
+
var PublishWakeUps = class {
|
|
3217
|
+
#dependencies;
|
|
3218
|
+
#lastPublishAt = 0;
|
|
3219
|
+
#cancel;
|
|
3220
|
+
/** When the pending timer fires. */
|
|
3221
|
+
#dueAt = 0;
|
|
3222
|
+
constructor(dependencies) {
|
|
3223
|
+
this.#dependencies = dependencies;
|
|
3224
|
+
}
|
|
3225
|
+
/**
|
|
3226
|
+
* Wakes the dispatcher for a publish: at once when its transaction has `committed` (one of relaybus'
|
|
3227
|
+
* own), else at the wake-ups after it.
|
|
3228
|
+
*/
|
|
3229
|
+
afterPublish(committed) {
|
|
3230
|
+
if (committed) {
|
|
3231
|
+
this.#dependencies.wake();
|
|
3232
|
+
return;
|
|
3233
|
+
}
|
|
3234
|
+
this.#lastPublishAt = this.#dependencies.clock.now().getTime();
|
|
3235
|
+
if (this.#cancel === void 0 || this.#dueAt > this.#lastPublishAt + FIRST_WAKE_MS) {
|
|
3236
|
+
this.cancel();
|
|
3237
|
+
this.#cancel = this.#arm(FIRST_WAKE_MS);
|
|
2426
3238
|
}
|
|
2427
3239
|
}
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
id,
|
|
2432
|
-
destination,
|
|
2433
|
-
type
|
|
2434
|
-
}) {
|
|
2435
|
-
return {
|
|
2436
|
-
transport: route.transport,
|
|
2437
|
-
destination,
|
|
2438
|
-
id,
|
|
2439
|
-
eventTypes: [type],
|
|
2440
|
-
legacyIds: [`${id}:${type}`],
|
|
2441
|
-
route,
|
|
2442
|
-
...resolveSettings(route, id)
|
|
2443
|
-
};
|
|
2444
|
-
}
|
|
2445
|
-
function addRoute(group, {
|
|
2446
|
-
route,
|
|
2447
|
-
type,
|
|
2448
|
-
destination
|
|
2449
|
-
}) {
|
|
2450
|
-
const target = `'${group.destination}' of transport '${group.transport}'`;
|
|
2451
|
-
if (route.transport !== group.transport) {
|
|
2452
|
-
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
2453
|
-
`Routes to ${target} and to '${destination}' of transport '${route.transport}' would share the handler id '${group.id}'; rename the transport or the destination`
|
|
2454
|
-
);
|
|
3240
|
+
cancel() {
|
|
3241
|
+
this.#cancel?.();
|
|
3242
|
+
this.#cancel = void 0;
|
|
2455
3243
|
}
|
|
2456
|
-
|
|
2457
|
-
|
|
3244
|
+
#fire() {
|
|
3245
|
+
this.#cancel = void 0;
|
|
3246
|
+
this.#dependencies.wake();
|
|
3247
|
+
const elapsed = this.#dependencies.clock.now().getTime() - this.#lastPublishAt;
|
|
3248
|
+
const next = WAKE_DELAYS_MS.find((delay) => delay > elapsed);
|
|
3249
|
+
if (next !== void 0) {
|
|
3250
|
+
this.#cancel = this.#arm(next - elapsed);
|
|
3251
|
+
}
|
|
2458
3252
|
}
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
3253
|
+
#arm(delayMs) {
|
|
3254
|
+
this.#dueAt = this.#dependencies.clock.now().getTime() + delayMs;
|
|
3255
|
+
return chunkJ76M7XTS_cjs.schedule(this.#dependencies.clock, {
|
|
3256
|
+
run: () => this.#fire(),
|
|
3257
|
+
delayMs,
|
|
3258
|
+
unref: true
|
|
3259
|
+
});
|
|
2464
3260
|
}
|
|
2465
|
-
|
|
2466
|
-
group.legacyIds.push(`${group.id}:${type}`);
|
|
2467
|
-
}
|
|
2468
|
-
function resolveSettings(route, id) {
|
|
2469
|
-
return {
|
|
2470
|
-
retry: createRetryPolicy(route.retry, DEFAULT_ROUTE_RETRY_POLICY),
|
|
2471
|
-
timeoutMs: route.timeout === void 0 ? DEFAULTS.routeTimeoutMs : toTimerMilliseconds(route.timeout, `Timeout of handler '${id}'`)
|
|
2472
|
-
};
|
|
2473
|
-
}
|
|
2474
|
-
function sameRetryPolicy(left, right) {
|
|
2475
|
-
return Object.keys(left).every(
|
|
2476
|
-
(field) => left[field] === right[field]
|
|
2477
|
-
);
|
|
2478
|
-
}
|
|
2479
|
-
function routedType(event) {
|
|
2480
|
-
const type = typeof event === "string" ? event : event.type;
|
|
2481
|
-
chunk3OHMEMZA_cjs.assertEventType(type);
|
|
2482
|
-
return type;
|
|
2483
|
-
}
|
|
3261
|
+
};
|
|
2484
3262
|
|
|
2485
3263
|
// src/transaction-budget.ts
|
|
2486
3264
|
async function runBudgetedTransaction(store, { clock, timeoutMs, work }) {
|
|
@@ -2501,7 +3279,7 @@ async function runBudgetedTransaction(store, { clock, timeoutMs, work }) {
|
|
|
2501
3279
|
{ timeoutMs }
|
|
2502
3280
|
);
|
|
2503
3281
|
} catch (error) {
|
|
2504
|
-
if (error instanceof
|
|
3282
|
+
if (error instanceof chunkJ76M7XTS_cjs.TransactionTimeoutError || committing || !overrun()) {
|
|
2505
3283
|
throw error;
|
|
2506
3284
|
}
|
|
2507
3285
|
throw createTimeoutError(timeoutMs, error);
|
|
@@ -2509,129 +3287,44 @@ async function runBudgetedTransaction(store, { clock, timeoutMs, work }) {
|
|
|
2509
3287
|
}
|
|
2510
3288
|
function createTimeoutError(timeoutMs, cause) {
|
|
2511
3289
|
const message = `The transaction ran past its ${timeoutMs} ms budget (transactionTimeout) and was rolled back. The usual cause is a slow call to another service (HTTP, gRPC, a broker, a lock) inside the transaction: make external calls before the transaction starts or after it commits, or raise transactionTimeout`;
|
|
2512
|
-
return cause === void 0 ? new
|
|
3290
|
+
return cause === void 0 ? new chunkJ76M7XTS_cjs.TransactionTimeoutError(message) : new chunkJ76M7XTS_cjs.TransactionTimeoutError(`${message} | cause=${chunkJ76M7XTS_cjs.toErrorInfo(cause).message}`, { cause });
|
|
2513
3291
|
}
|
|
2514
3292
|
|
|
2515
|
-
// src/relay-bus.ts
|
|
2516
|
-
var DEFAULT_DRAIN_BATCHES = 1e3;
|
|
2517
|
-
var
|
|
2518
|
-
|
|
2519
|
-
#
|
|
2520
|
-
#
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
#catalogRequirements = [];
|
|
2536
|
-
#setupStore;
|
|
2537
|
-
#retention;
|
|
2538
|
-
#retentionTimer;
|
|
2539
|
-
#observation;
|
|
2540
|
-
#backlogIntervalMs;
|
|
2541
|
-
#backlogTimer;
|
|
2542
|
-
#shutdownTimeoutMs;
|
|
2543
|
-
#state = "idle";
|
|
2544
|
-
#starting;
|
|
2545
|
-
#stopping;
|
|
2546
|
-
constructor(options) {
|
|
2547
|
-
this.#store = options.store;
|
|
2548
|
-
this.#source = options.source ?? "relaybus";
|
|
2549
|
-
this.#transactionMode = oneOf(options.transactionMode ?? "required", {
|
|
2550
|
-
allowed: ["required", "optional"],
|
|
2551
|
-
name: "transactionMode"
|
|
2552
|
-
});
|
|
2553
|
-
this.#unroutedEvents = oneOf(options.unroutedEvents ?? "warn", {
|
|
2554
|
-
allowed: ["warn", "throw"],
|
|
2555
|
-
name: "unroutedEvents"
|
|
2556
|
-
});
|
|
2557
|
-
this.#transactionResolver = options.transactionResolver;
|
|
2558
|
-
this.#transactionTimeoutMs = resolveTransactionTimeout(options);
|
|
2559
|
-
this.#defaults = createHandlerDefaults(options);
|
|
2560
|
-
this.#logger = options.logger ?? console;
|
|
2561
|
-
this.#notifier = new Notifier(options.observers ?? [], this.#logger);
|
|
2562
|
-
this.#clock = options.clock ?? systemClock;
|
|
2563
|
-
this.#observation = BusObservation.create({
|
|
2564
|
-
notifier: this.#notifier,
|
|
2565
|
-
tracer: options.tracer,
|
|
2566
|
-
clock: this.#clock
|
|
2567
|
-
});
|
|
2568
|
-
this.#backlogIntervalMs = resolveBacklogInterval(options);
|
|
2569
|
-
this.#generateIdentifier = options.generateIdentifier ?? (() => createUuidV7());
|
|
2570
|
-
this.#setupStore = options.setupStore ?? true;
|
|
2571
|
-
this.#retention = createRetentionSettings(options.retention);
|
|
2572
|
-
this.#subscriptions = options.subscriptions ?? [];
|
|
2573
|
-
this.#registerConnections(options);
|
|
2574
|
-
this.#dispatcherEnabled = isDispatcherEnabled(options);
|
|
2575
|
-
const settings = createDispatcherSettings(options.dispatcher, this.#generateIdentifier);
|
|
2576
|
-
this.#shutdownTimeoutMs = settings.shutdownTimeoutMs;
|
|
2577
|
-
this.#dispatcher = this.#createDispatcher(settings, options);
|
|
2578
|
-
}
|
|
2579
|
-
#createDispatcher(settings, options) {
|
|
2580
|
-
return new Dispatcher({
|
|
2581
|
-
store: this.#store,
|
|
2582
|
-
registry: this.#registry,
|
|
2583
|
-
clock: this.#clock,
|
|
2584
|
-
random: options.random ?? Math.random,
|
|
2585
|
-
notifier: this.#notifier,
|
|
2586
|
-
settings,
|
|
2587
|
-
observation: DispatchObservation.create({
|
|
2588
|
-
notifier: this.#notifier,
|
|
2589
|
-
tracer: options.tracer,
|
|
2590
|
-
clock: this.#clock
|
|
2591
|
-
})
|
|
2592
|
-
});
|
|
2593
|
-
}
|
|
2594
|
-
/** Registers the transports, then the routes, checks the subscriptions and registers the catalogs. */
|
|
2595
|
-
#registerConnections(options) {
|
|
2596
|
-
for (const transport of options.transports ?? []) {
|
|
2597
|
-
if (this.#transports.has(transport.name)) {
|
|
2598
|
-
throw new chunk3OHMEMZA_cjs.ConfigurationError(`Transport name '${transport.name}' is used twice`);
|
|
2599
|
-
}
|
|
2600
|
-
this.#transports.set(transport.name, transport);
|
|
2601
|
-
}
|
|
2602
|
-
this.#registerRoutes(options.routes ?? []);
|
|
2603
|
-
for (const subscription of this.#subscriptions) {
|
|
2604
|
-
const transport = this.#requireTransport(subscription.transport);
|
|
2605
|
-
if (transport.subscribe === void 0) {
|
|
2606
|
-
throw new chunk3OHMEMZA_cjs.ConfigurationError(`Transport '${transport.name}' cannot subscribe`);
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2609
|
-
for (const catalog of options.catalogs ?? []) {
|
|
2610
|
-
this.registerCatalog(catalog);
|
|
2611
|
-
}
|
|
3293
|
+
// src/relay-bus.ts
|
|
3294
|
+
var DEFAULT_DRAIN_BATCHES = 1e3;
|
|
3295
|
+
var RelayBus = class {
|
|
3296
|
+
#parts;
|
|
3297
|
+
#lifecycle;
|
|
3298
|
+
#wakeUps;
|
|
3299
|
+
constructor(options) {
|
|
3300
|
+
this.#parts = assembleRelayBus(options, {
|
|
3301
|
+
reportUnrouted: (envelope) => this.#reportUnrouted(envelope),
|
|
3302
|
+
scheduleWake: (committed) => this.#scheduleWake(committed),
|
|
3303
|
+
statistics: () => this.statistics()
|
|
3304
|
+
});
|
|
3305
|
+
this.#lifecycle = new BusLifecycle({
|
|
3306
|
+
...this.#parts,
|
|
3307
|
+
closeTransports: (deadline) => this.#closeTransports(deadline)
|
|
3308
|
+
});
|
|
3309
|
+
this.#wakeUps = new PublishWakeUps({
|
|
3310
|
+
clock: this.#parts.clock,
|
|
3311
|
+
wake: () => this.#parts.dispatcher.wake()
|
|
3312
|
+
});
|
|
2612
3313
|
}
|
|
2613
3314
|
get store() {
|
|
2614
|
-
return this.#store;
|
|
3315
|
+
return this.#parts.store;
|
|
2615
3316
|
}
|
|
2616
3317
|
get isRunning() {
|
|
2617
|
-
return this.#state === "running";
|
|
3318
|
+
return this.#lifecycle.state === "running";
|
|
2618
3319
|
}
|
|
2619
3320
|
/** True while the bus runs and every transport reports itself ready. */
|
|
2620
3321
|
isReady() {
|
|
2621
|
-
|
|
2622
|
-
return false;
|
|
2623
|
-
}
|
|
2624
|
-
for (const transport of this.#transports.values()) {
|
|
2625
|
-
if (transport.isReady?.() === false) {
|
|
2626
|
-
return false;
|
|
2627
|
-
}
|
|
2628
|
-
}
|
|
2629
|
-
return true;
|
|
3322
|
+
return this.#lifecycle.state === "running" && this.#parts.connections.allReady();
|
|
2630
3323
|
}
|
|
2631
3324
|
// eslint-disable-next-line relaybus/max-two-params -- public RelayBus.subscribe signature
|
|
2632
3325
|
subscribe(event, handler, options) {
|
|
2633
3326
|
const { type, schema } = describeEvent(event);
|
|
2634
|
-
this.#registry.register(
|
|
3327
|
+
this.#parts.registry.register(
|
|
2635
3328
|
{
|
|
2636
3329
|
eventTypes: [type],
|
|
2637
3330
|
schema,
|
|
@@ -2639,7 +3332,7 @@ var RelayBus = class {
|
|
|
2639
3332
|
options,
|
|
2640
3333
|
forwardsToTransport: false
|
|
2641
3334
|
},
|
|
2642
|
-
this.#defaults
|
|
3335
|
+
this.#parts.defaults
|
|
2643
3336
|
);
|
|
2644
3337
|
}
|
|
2645
3338
|
// eslint-disable-next-line relaybus/max-two-params -- public RelayBus.subscribeCatalog signature
|
|
@@ -2655,7 +3348,7 @@ var RelayBus = class {
|
|
|
2655
3348
|
* option.
|
|
2656
3349
|
*/
|
|
2657
3350
|
registerCatalog(catalog) {
|
|
2658
|
-
this.#catalogRequirements.push(toCatalogRequirement(catalog));
|
|
3351
|
+
this.#parts.catalogRequirements.push(toCatalogRequirement(catalog));
|
|
2659
3352
|
}
|
|
2660
3353
|
/**
|
|
2661
3354
|
* A view of this bus that publishes only the events of `catalog`, or only
|
|
@@ -2667,7 +3360,7 @@ var RelayBus = class {
|
|
|
2667
3360
|
}
|
|
2668
3361
|
/** Registered handlers and routes, for diagnostics. */
|
|
2669
3362
|
handlers() {
|
|
2670
|
-
return this.#registry.all().flatMap(
|
|
3363
|
+
return this.#parts.registry.all().flatMap(
|
|
2671
3364
|
(registration) => registration.eventTypes.map((eventType) => toDescriptor(registration, eventType))
|
|
2672
3365
|
);
|
|
2673
3366
|
}
|
|
@@ -2677,12 +3370,12 @@ var RelayBus = class {
|
|
|
2677
3370
|
* have been written when the id already exists.
|
|
2678
3371
|
*/
|
|
2679
3372
|
async publish(event, options = {}) {
|
|
2680
|
-
const [envelope] = await this.#
|
|
3373
|
+
const [envelope] = await this.#parts.publisher.publish([event], { options, id: options.id });
|
|
2681
3374
|
return envelope;
|
|
2682
3375
|
}
|
|
2683
3376
|
/** Stores several events atomically, in order, sharing one correlation id. */
|
|
2684
3377
|
async publishAll(events, options = {}) {
|
|
2685
|
-
return this.#
|
|
3378
|
+
return this.#parts.publisher.publish(events, { options, id: void 0 });
|
|
2686
3379
|
}
|
|
2687
3380
|
/**
|
|
2688
3381
|
* Runs `work` in a new store transaction. `publish` calls inside it join the
|
|
@@ -2690,9 +3383,9 @@ var RelayBus = class {
|
|
|
2690
3383
|
* and rolled back with a {@link TransactionTimeoutError} when it runs past it.
|
|
2691
3384
|
*/
|
|
2692
3385
|
async transaction(work) {
|
|
2693
|
-
return runBudgetedTransaction(this.#store, {
|
|
2694
|
-
clock: this.#clock,
|
|
2695
|
-
timeoutMs: this.#transactionTimeoutMs,
|
|
3386
|
+
return runBudgetedTransaction(this.#parts.store, {
|
|
3387
|
+
clock: this.#parts.clock,
|
|
3388
|
+
timeoutMs: this.#parts.transactionTimeoutMs,
|
|
2696
3389
|
work: (transaction) => runInScope({ transaction }, () => work(transaction))
|
|
2697
3390
|
});
|
|
2698
3391
|
}
|
|
@@ -2702,7 +3395,7 @@ var RelayBus = class {
|
|
|
2702
3395
|
* `transactionResolver` finds. Repositories use it to write in the same transaction.
|
|
2703
3396
|
*/
|
|
2704
3397
|
currentTransaction() {
|
|
2705
|
-
return getCurrentScope()?.transaction ?? this.#transactionResolver?.();
|
|
3398
|
+
return getCurrentScope()?.transaction ?? this.#parts.transactionResolver?.();
|
|
2706
3399
|
}
|
|
2707
3400
|
/**
|
|
2708
3401
|
* Stores an event received from a broker and schedules it for local handlers.
|
|
@@ -2711,32 +3404,7 @@ var RelayBus = class {
|
|
|
2711
3404
|
* events, which transports should dead-letter instead of redelivering.
|
|
2712
3405
|
*/
|
|
2713
3406
|
receive(envelope) {
|
|
2714
|
-
return this.#
|
|
2715
|
-
}
|
|
2716
|
-
#observedReceive(envelope, origin) {
|
|
2717
|
-
return this.#observation.receive({ envelope, origin }, () => this.#receive(envelope));
|
|
2718
|
-
}
|
|
2719
|
-
async #receive(envelope) {
|
|
2720
|
-
const normalized = normalizeEnvelope(envelope);
|
|
2721
|
-
const registrations = this.#registry.forEventType(normalized.type).filter((registration) => !registration.forwardsToTransport);
|
|
2722
|
-
if (registrations.length === 0) {
|
|
2723
|
-
this.#notifier.notify("onReceived", { envelope: normalized, status: "ignored" });
|
|
2724
|
-
return "ignored";
|
|
2725
|
-
}
|
|
2726
|
-
const now = this.#clock.now();
|
|
2727
|
-
const entry = {
|
|
2728
|
-
event: normalized,
|
|
2729
|
-
deliveries: registrations.map((registration) => this.#createDelivery(registration, now))
|
|
2730
|
-
};
|
|
2731
|
-
const result = await this.#store.transaction(
|
|
2732
|
-
(transaction) => this.#store.append(transaction, [entry], now)
|
|
2733
|
-
);
|
|
2734
|
-
const status = result.duplicateEventIds.length > 0 ? "duplicate" : "accepted";
|
|
2735
|
-
this.#notifier.notify("onReceived", { envelope: normalized, status });
|
|
2736
|
-
if (result.appendedDeliveryCount > 0) {
|
|
2737
|
-
this.#scheduleWake();
|
|
2738
|
-
}
|
|
2739
|
-
return status;
|
|
3407
|
+
return this.#parts.receiver.receive(envelope);
|
|
2740
3408
|
}
|
|
2741
3409
|
/**
|
|
2742
3410
|
* Creates the store schema, connects transports, subscribes and starts
|
|
@@ -2744,18 +3412,13 @@ var RelayBus = class {
|
|
|
2744
3412
|
* stopped for good, because its transports are closed; create a new instance.
|
|
2745
3413
|
*/
|
|
2746
3414
|
async start() {
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
);
|
|
2751
|
-
}
|
|
2752
|
-
assertCatalogsCovered(this.#catalogRequirements, (type) => this.#registrationsOf(type));
|
|
2753
|
-
this.#state = "starting";
|
|
2754
|
-
this.#starting = this.#startComponents();
|
|
3415
|
+
this.#lifecycle.assertStartable();
|
|
3416
|
+
assertCatalogsCovered(this.#parts.catalogRequirements, (type) => this.#registrationsOf(type));
|
|
3417
|
+
const starting = this.#lifecycle.beginStart(() => this.#startComponents());
|
|
2755
3418
|
try {
|
|
2756
|
-
await
|
|
3419
|
+
await starting;
|
|
2757
3420
|
} finally {
|
|
2758
|
-
this.#
|
|
3421
|
+
this.#lifecycle.endStart();
|
|
2759
3422
|
}
|
|
2760
3423
|
}
|
|
2761
3424
|
/**
|
|
@@ -2768,41 +3431,26 @@ var RelayBus = class {
|
|
|
2768
3431
|
* get the result of the first.
|
|
2769
3432
|
*/
|
|
2770
3433
|
async stop(options = {}) {
|
|
2771
|
-
const timeoutMs = options.timeout === void 0 ? this.#shutdownTimeoutMs :
|
|
2772
|
-
|
|
2773
|
-
|
|
3434
|
+
const timeoutMs = options.timeout === void 0 ? this.#parts.shutdownTimeoutMs : chunkJ76M7XTS_cjs.Milliseconds.forTimer(options.timeout, "timeout");
|
|
3435
|
+
const { stopping, first } = this.#lifecycle.stop(timeoutMs);
|
|
3436
|
+
if (!first) {
|
|
3437
|
+
return stopping;
|
|
2774
3438
|
}
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
}
|
|
2778
|
-
this.#stopping = this.#stopComponents(timeoutMs);
|
|
2779
|
-
const result = await this.#stopping;
|
|
3439
|
+
this.#wakeUps.cancel();
|
|
3440
|
+
const result = await stopping;
|
|
2780
3441
|
if (!result.graceful) {
|
|
2781
|
-
this.#logger.warn(
|
|
3442
|
+
this.#parts.logger.warn(
|
|
2782
3443
|
`[RelayBus](stop) Shutdown deadline passed, forced close | abandoned=${result.abandonedDeliveries} | timeoutMs=${timeoutMs}`
|
|
2783
3444
|
);
|
|
2784
3445
|
}
|
|
2785
3446
|
return result;
|
|
2786
3447
|
}
|
|
2787
|
-
/**
|
|
2788
|
-
* A bus that is not running: an idle one only settles what batches run by hand left behind (and
|
|
2789
|
-
* stays startable); a stopped one has nothing left to do.
|
|
2790
|
-
*/
|
|
2791
|
-
async #stopOutsideRun(timeoutMs) {
|
|
2792
|
-
if (this.#state !== "idle") {
|
|
2793
|
-
return { graceful: true, abandonedDeliveries: 0, durationMs: 0 };
|
|
2794
|
-
}
|
|
2795
|
-
return withinDeadline(
|
|
2796
|
-
{ clock: this.#clock, timeoutMs },
|
|
2797
|
-
(deadline) => this.#dispatcher.stop(deadline)
|
|
2798
|
-
);
|
|
2799
|
-
}
|
|
2800
3448
|
/**
|
|
2801
3449
|
* Claims and runs one batch of due deliveries, resolving when they settle.
|
|
2802
3450
|
* Returns how many ran. Intended for tests and scripts.
|
|
2803
3451
|
*/
|
|
2804
3452
|
async runOnce() {
|
|
2805
|
-
return this.#dispatcher.processBatch();
|
|
3453
|
+
return this.#parts.dispatcher.processBatch();
|
|
2806
3454
|
}
|
|
2807
3455
|
/**
|
|
2808
3456
|
* Runs batches until nothing is due. Deliveries scheduled in the future
|
|
@@ -2817,433 +3465,185 @@ var RelayBus = class {
|
|
|
2817
3465
|
}
|
|
2818
3466
|
total += processed;
|
|
2819
3467
|
}
|
|
2820
|
-
throw new
|
|
3468
|
+
throw new chunkJ76M7XTS_cjs.DrainIncompleteError(`drain did not finish within ${maxBatches} batches`);
|
|
2821
3469
|
}
|
|
2822
3470
|
listDeliveries(query = {}) {
|
|
2823
|
-
return this.#store.listDeliveries(query);
|
|
3471
|
+
return this.#parts.store.listDeliveries(query);
|
|
3472
|
+
}
|
|
3473
|
+
/**
|
|
3474
|
+
* Asks the dispatcher to claim now. Call it right after committing your own transaction that published,
|
|
3475
|
+
* when the store cannot report the commit (see Tuning, "Wake-up after your transaction commits").
|
|
3476
|
+
*/
|
|
3477
|
+
wake() {
|
|
3478
|
+
this.#parts.dispatcher.wake();
|
|
2824
3479
|
}
|
|
2825
3480
|
/** Moves dead deliveries back to pending with a fresh attempt budget. */
|
|
2826
3481
|
async requeue(deliveryIds) {
|
|
2827
|
-
const moved = await this.#store.requeue(deliveryIds, this.#clock.now());
|
|
3482
|
+
const moved = await this.#parts.store.requeue(deliveryIds, this.#parts.clock.now());
|
|
2828
3483
|
if (moved > 0) {
|
|
2829
|
-
this.#dispatcher.wake();
|
|
3484
|
+
this.#parts.dispatcher.wake();
|
|
2830
3485
|
}
|
|
2831
3486
|
return moved;
|
|
2832
3487
|
}
|
|
2833
3488
|
statistics() {
|
|
2834
|
-
return this.#store.statistics(this.#clock.now());
|
|
3489
|
+
return this.#parts.store.statistics(this.#parts.clock.now());
|
|
2835
3490
|
}
|
|
2836
3491
|
/** Applies the retention policy once. Runs automatically while the bus is running. */
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
const { completedMs, deadMs } = this.#retention;
|
|
2840
|
-
try {
|
|
2841
|
-
const result = await this.#store.purge({
|
|
2842
|
-
...completedMs === null ? {} : { completedBefore: new Date(now - completedMs) },
|
|
2843
|
-
...deadMs === null ? {} : { deadBefore: new Date(now - deadMs) }
|
|
2844
|
-
});
|
|
2845
|
-
this.#notifier.notify("onPurged", { result });
|
|
2846
|
-
return result;
|
|
2847
|
-
} catch (error) {
|
|
2848
|
-
this.#notifier.failure("purge", error);
|
|
2849
|
-
throw error;
|
|
2850
|
-
}
|
|
3492
|
+
purgeExpired() {
|
|
3493
|
+
return this.#parts.maintenance.purgeExpired();
|
|
2851
3494
|
}
|
|
3495
|
+
// The awaits stay in this method, as many as before: with no store setup and no transport a
|
|
3496
|
+
// start completes synchronously, so the bus is running as soon as start() returns.
|
|
2852
3497
|
async #startComponents() {
|
|
3498
|
+
const { setupStore, store, connections } = this.#parts;
|
|
2853
3499
|
try {
|
|
2854
|
-
if (
|
|
2855
|
-
await
|
|
3500
|
+
if (setupStore) {
|
|
3501
|
+
await store.setup?.();
|
|
2856
3502
|
}
|
|
2857
|
-
for (const transport of
|
|
3503
|
+
for (const transport of connections.transports) {
|
|
2858
3504
|
await transport.connect?.();
|
|
2859
3505
|
}
|
|
2860
|
-
for (const subscription of
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
async (envelope, message) => {
|
|
2865
|
-
await (this.#observation === void 0 ? this.#receive(envelope) : this.#observedReceive(envelope, { transport, destination: message?.destination }));
|
|
2866
|
-
},
|
|
2867
|
-
this.#subscriptionHooks(transport)
|
|
3506
|
+
for (const subscription of connections.subscriptions) {
|
|
3507
|
+
await connections.subscribe(
|
|
3508
|
+
subscription,
|
|
3509
|
+
(envelope, origin) => this.#parts.receiver.receive(envelope, origin)
|
|
2868
3510
|
);
|
|
2869
3511
|
}
|
|
2870
3512
|
} catch (error) {
|
|
2871
|
-
await
|
|
2872
|
-
|
|
2873
|
-
async (deadline) => {
|
|
2874
|
-
await this.#closeTransports(deadline);
|
|
2875
|
-
return 0;
|
|
2876
|
-
}
|
|
2877
|
-
);
|
|
2878
|
-
this.#state = "stopped";
|
|
3513
|
+
await this.#closeAfterFailedStart();
|
|
3514
|
+
this.#lifecycle.markStopped();
|
|
2879
3515
|
throw error;
|
|
2880
3516
|
}
|
|
2881
|
-
if (this.#state !== "starting") {
|
|
2882
|
-
throw new
|
|
3517
|
+
if (this.#lifecycle.state !== "starting") {
|
|
3518
|
+
throw new chunkJ76M7XTS_cjs.LifecycleError("relaybus was stopped before it finished starting");
|
|
2883
3519
|
}
|
|
2884
|
-
if (this.#dispatcherEnabled) {
|
|
2885
|
-
this.#dispatcher.start();
|
|
3520
|
+
if (this.#parts.dispatcherEnabled) {
|
|
3521
|
+
this.#parts.dispatcher.start();
|
|
2886
3522
|
}
|
|
2887
|
-
this.#
|
|
2888
|
-
this.#
|
|
2889
|
-
this.#
|
|
2890
|
-
|
|
2891
|
-
`[RelayBus](startComponents) relaybus started | handlers=${this.#registry.all().length} dispatcher=${this.#dispatcherEnabled}`
|
|
3523
|
+
this.#parts.maintenance.start();
|
|
3524
|
+
this.#lifecycle.markRunning();
|
|
3525
|
+
this.#parts.logger.log(
|
|
3526
|
+
`[RelayBus](startComponents) relaybus started | handlers=${this.#parts.registry.all().length} dispatcher=${this.#parts.dispatcherEnabled}`
|
|
2892
3527
|
);
|
|
2893
3528
|
}
|
|
2894
|
-
/**
|
|
2895
|
-
#
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
const errorInfo = chunk3OHMEMZA_cjs.toErrorInfo(error);
|
|
2902
|
-
this.#notifier.notify("onInvalidMessage", {
|
|
2903
|
-
transport: transport.name,
|
|
2904
|
-
system: transport.system ?? transport.name,
|
|
2905
|
-
destination,
|
|
2906
|
-
reason: errorInfo.message,
|
|
2907
|
-
error: errorInfo
|
|
2908
|
-
});
|
|
3529
|
+
/** Closes the transports a failed start may have connected, within the shutdown timeout. */
|
|
3530
|
+
#closeAfterFailedStart() {
|
|
3531
|
+
return withinDeadline(
|
|
3532
|
+
{ clock: this.#parts.clock, timeoutMs: this.#parts.shutdownTimeoutMs },
|
|
3533
|
+
async (deadline) => {
|
|
3534
|
+
await this.#closeTransports(deadline);
|
|
3535
|
+
return 0;
|
|
2909
3536
|
}
|
|
2910
|
-
};
|
|
2911
|
-
}
|
|
2912
|
-
/** Applies the retention policy every `retention.interval` while the bus runs, when it deletes anything. */
|
|
2913
|
-
#scheduleRetention() {
|
|
2914
|
-
if (this.#retention.completedMs !== null || this.#retention.deadMs !== null) {
|
|
2915
|
-
this.#retentionTimer = chunk3OHMEMZA_cjs.schedule(this.#clock, {
|
|
2916
|
-
run: () => {
|
|
2917
|
-
this.purgeExpired().catch(() => void 0);
|
|
2918
|
-
},
|
|
2919
|
-
delayMs: this.#retention.intervalMs,
|
|
2920
|
-
repeat: true,
|
|
2921
|
-
unref: true
|
|
2922
|
-
});
|
|
2923
|
-
}
|
|
2924
|
-
}
|
|
2925
|
-
/** Reports the backlog every `backlogInterval` while the bus runs, when an observer wants it. */
|
|
2926
|
-
#scheduleBacklog() {
|
|
2927
|
-
if (this.#backlogIntervalMs !== void 0) {
|
|
2928
|
-
this.#backlogTimer = this.#observation?.startBacklog({
|
|
2929
|
-
intervalMs: this.#backlogIntervalMs,
|
|
2930
|
-
statistics: () => this.statistics()
|
|
2931
|
-
});
|
|
2932
|
-
}
|
|
2933
|
-
}
|
|
2934
|
-
/**
|
|
2935
|
-
* Waits for a start in progress (a failed start leaves nothing to stop), then stops the dispatcher
|
|
2936
|
-
* and closes the transports and the store. A start still running at the deadline is given up: the
|
|
2937
|
-
* transports and the store are force-closed and the start rejects when it resumes.
|
|
2938
|
-
*/
|
|
2939
|
-
async #stopComponents(timeoutMs) {
|
|
2940
|
-
try {
|
|
2941
|
-
return await withinDeadline({ clock: this.#clock, timeoutMs }, async (deadline) => {
|
|
2942
|
-
if (this.#state === "starting") {
|
|
2943
|
-
const started = await deadline.race(this.#starting.catch(() => void 0));
|
|
2944
|
-
if (started === "timeout") {
|
|
2945
|
-
this.#state = "stopping";
|
|
2946
|
-
await this.#closeConnections(deadline);
|
|
2947
|
-
return 0;
|
|
2948
|
-
}
|
|
2949
|
-
}
|
|
2950
|
-
if (this.#state !== "running") {
|
|
2951
|
-
return 0;
|
|
2952
|
-
}
|
|
2953
|
-
this.#state = "stopping";
|
|
2954
|
-
this.#retentionTimer?.();
|
|
2955
|
-
this.#backlogTimer?.();
|
|
2956
|
-
const abandoned = await this.#dispatcher.stop(deadline);
|
|
2957
|
-
await this.#closeConnections(deadline);
|
|
2958
|
-
return abandoned;
|
|
2959
|
-
});
|
|
2960
|
-
} finally {
|
|
2961
|
-
this.#state = "stopped";
|
|
2962
|
-
}
|
|
2963
|
-
}
|
|
2964
|
-
async #closeConnections(deadline) {
|
|
2965
|
-
await this.#closeTransports(deadline);
|
|
2966
|
-
const options = { signal: deadline.signal };
|
|
2967
|
-
await deadline.race(Promise.resolve(this.#store.close?.(options)));
|
|
2968
|
-
}
|
|
2969
|
-
/** Publishes inside the tracer's span, with its trace headers, when the bus is observed. */
|
|
2970
|
-
#observedPublish(events, input) {
|
|
2971
|
-
const observation = this.#observation;
|
|
2972
|
-
if (observation === void 0) {
|
|
2973
|
-
return this.#publish(events, input);
|
|
2974
|
-
}
|
|
2975
|
-
return observation.publish(
|
|
2976
|
-
{ eventTypes: events.map((event) => String(event.type)) },
|
|
2977
|
-
(headers) => this.#publish(events, { ...input, options: withTraceHeaders(input.options, headers) })
|
|
2978
3537
|
);
|
|
2979
3538
|
}
|
|
2980
|
-
async #publish(events, {
|
|
2981
|
-
options,
|
|
2982
|
-
id
|
|
2983
|
-
}) {
|
|
2984
|
-
if (events.length === 0) {
|
|
2985
|
-
return [];
|
|
2986
|
-
}
|
|
2987
|
-
const now = this.#clock.now();
|
|
2988
|
-
const delayMs = options.delay === void 0 ? 0 : toMilliseconds(options.delay, "delay");
|
|
2989
|
-
const availableAt = new Date(now.getTime() + delayMs);
|
|
2990
|
-
const scope = getCurrentScope();
|
|
2991
|
-
const entries = await this.#createEntries({ events, options, id, scope, now, availableAt });
|
|
2992
|
-
const deliverable = entries.filter((entry) => entry.deliveries.length > 0);
|
|
2993
|
-
if (deliverable.length > 0) {
|
|
2994
|
-
await this.#appendDeliverable({ entries: deliverable, options, scope, now, delayMs });
|
|
2995
|
-
}
|
|
2996
|
-
return entries.map((entry) => entry.event);
|
|
2997
|
-
}
|
|
2998
|
-
/** One entry per event, in order: its envelope and a delivery for every registration of its type. */
|
|
2999
|
-
async #createEntries(input) {
|
|
3000
|
-
const { events, options, id, scope, now, availableAt } = input;
|
|
3001
|
-
const entries = [];
|
|
3002
|
-
let correlationId = options.correlationId ?? scope?.event?.correlationId ?? scope?.event?.id;
|
|
3003
|
-
for (const event of events) {
|
|
3004
|
-
const envelope = await this.#createEnvelope({
|
|
3005
|
-
event,
|
|
3006
|
-
options,
|
|
3007
|
-
id,
|
|
3008
|
-
scope,
|
|
3009
|
-
now,
|
|
3010
|
-
correlationId
|
|
3011
|
-
});
|
|
3012
|
-
correlationId = envelope.correlationId;
|
|
3013
|
-
const registrations = this.#registry.forEventType(envelope.type);
|
|
3014
|
-
if (registrations.length === 0) {
|
|
3015
|
-
this.#reportUnrouted(envelope);
|
|
3016
|
-
}
|
|
3017
|
-
entries.push({
|
|
3018
|
-
event: envelope,
|
|
3019
|
-
deliveries: registrations.map(
|
|
3020
|
-
(registration) => this.#createDelivery(registration, availableAt)
|
|
3021
|
-
)
|
|
3022
|
-
});
|
|
3023
|
-
}
|
|
3024
|
-
return entries;
|
|
3025
|
-
}
|
|
3026
|
-
/** Appends the entries that have deliveries, reports what was new and wakes the dispatcher when it is due now. */
|
|
3027
|
-
async #appendDeliverable(input) {
|
|
3028
|
-
const { entries, options, scope, now, delayMs } = input;
|
|
3029
|
-
const result = await this.#appendInTransaction(entries, { options, scope, now });
|
|
3030
|
-
const appended = new Set(result.appendedEventIds);
|
|
3031
|
-
const published = entries.map((entry) => entry.event).filter((envelope) => appended.has(envelope.id));
|
|
3032
|
-
if (published.length > 0) {
|
|
3033
|
-
this.#notifier.notify("onPublished", {
|
|
3034
|
-
envelopes: published,
|
|
3035
|
-
deliveries: result.appendedDeliveryCount
|
|
3036
|
-
});
|
|
3037
|
-
if (delayMs === 0) {
|
|
3038
|
-
this.#scheduleWake();
|
|
3039
|
-
}
|
|
3040
|
-
}
|
|
3041
|
-
}
|
|
3042
3539
|
#reportUnrouted(envelope) {
|
|
3043
3540
|
const message = `No handler or route for event type, event not stored | type=${envelope.type} id=${envelope.id}`;
|
|
3044
|
-
if (this.#unroutedEvents === "throw") {
|
|
3045
|
-
throw new
|
|
3046
|
-
}
|
|
3047
|
-
this.#logger.warn(`[RelayBus](reportUnrouted) ${message}`);
|
|
3048
|
-
}
|
|
3049
|
-
async #appendInTransaction(entries, {
|
|
3050
|
-
options,
|
|
3051
|
-
scope,
|
|
3052
|
-
now
|
|
3053
|
-
}) {
|
|
3054
|
-
const transaction = options.transaction ?? scope?.transaction ?? this.#transactionResolver?.();
|
|
3055
|
-
if (transaction !== void 0) {
|
|
3056
|
-
return this.#store.append(transaction, entries, now);
|
|
3541
|
+
if (this.#parts.unroutedEvents === "throw") {
|
|
3542
|
+
throw new chunkJ76M7XTS_cjs.ConfigurationError(message);
|
|
3057
3543
|
}
|
|
3058
|
-
|
|
3059
|
-
throw new chunk3OHMEMZA_cjs.TransactionRequiredError(
|
|
3060
|
-
"publish found no transaction. Pass { transaction }, publish inside bus.transaction(), configure transactionResolver, or set transactionMode to optional"
|
|
3061
|
-
);
|
|
3062
|
-
}
|
|
3063
|
-
return this.#store.transaction(
|
|
3064
|
-
(ownTransaction) => this.#store.append(ownTransaction, entries, now)
|
|
3065
|
-
);
|
|
3066
|
-
}
|
|
3067
|
-
async #createEnvelope(input) {
|
|
3068
|
-
const { event, options, id, scope, now, correlationId } = input;
|
|
3069
|
-
if (typeof event.type !== "string") {
|
|
3070
|
-
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
3071
|
-
"publish expects an instance of a class created with defineEvent"
|
|
3072
|
-
);
|
|
3073
|
-
}
|
|
3074
|
-
chunk3OHMEMZA_cjs.assertEventType(event.type);
|
|
3075
|
-
const schema = event.constructor.schema;
|
|
3076
|
-
const payload = await chunk3OHMEMZA_cjs.validatePayload(event.type, schema, event.payload);
|
|
3077
|
-
if (options.key !== void 0 && options.key.length === 0) {
|
|
3078
|
-
throw new chunk3OHMEMZA_cjs.ConfigurationError("key must not be empty");
|
|
3079
|
-
}
|
|
3080
|
-
const eventId = id ?? this.#generateIdentifier();
|
|
3081
|
-
return {
|
|
3082
|
-
id: eventId,
|
|
3083
|
-
type: event.type,
|
|
3084
|
-
source: this.#source,
|
|
3085
|
-
key: options.key ?? chunk3OHMEMZA_cjs.keyFromDefinition(event, payload),
|
|
3086
|
-
payload,
|
|
3087
|
-
headers: { ...options.headers },
|
|
3088
|
-
correlationId: correlationId ?? eventId,
|
|
3089
|
-
causationId: scope?.event?.id ?? null,
|
|
3090
|
-
occurredAt: now
|
|
3091
|
-
};
|
|
3092
|
-
}
|
|
3093
|
-
#createDelivery(registration, availableAt) {
|
|
3094
|
-
return { id: this.#generateIdentifier(), handler: registration.id, availableAt };
|
|
3544
|
+
this.#parts.logger.warn(`[RelayBus](reportUnrouted) ${message}`);
|
|
3095
3545
|
}
|
|
3096
3546
|
#registrationsOf(eventType) {
|
|
3097
|
-
const registrations = this.#registry.forEventType(eventType);
|
|
3547
|
+
const registrations = this.#parts.registry.forEventType(eventType);
|
|
3098
3548
|
const routes = registrations.filter((registration) => registration.forwardsToTransport).length;
|
|
3099
3549
|
return { handlers: registrations.length - routes, routes };
|
|
3100
3550
|
}
|
|
3101
|
-
#
|
|
3102
|
-
|
|
3103
|
-
this.#requireTransport(route.transport);
|
|
3104
|
-
}
|
|
3105
|
-
for (const group of groupRoutes(routes)) {
|
|
3106
|
-
this.#registerRoute(group);
|
|
3107
|
-
}
|
|
3108
|
-
}
|
|
3109
|
-
/** Registers one handler for every route to a destination, so a key's events reach it in commit order. */
|
|
3110
|
-
#registerRoute(group) {
|
|
3111
|
-
const transport = this.#transports.get(group.transport);
|
|
3112
|
-
const { route, destination } = group;
|
|
3113
|
-
this.#registry.register(
|
|
3114
|
-
{
|
|
3115
|
-
eventTypes: group.eventTypes,
|
|
3116
|
-
legacyIds: group.legacyIds,
|
|
3117
|
-
schema: void 0,
|
|
3118
|
-
handle: this.#observation?.sender(transport, destination) ?? ((envelope) => transport.publish(envelope, destination)),
|
|
3119
|
-
options: {
|
|
3120
|
-
id: group.id,
|
|
3121
|
-
...route.retry === void 0 ? {} : { retry: route.retry },
|
|
3122
|
-
...route.timeout === void 0 ? {} : { timeout: route.timeout }
|
|
3123
|
-
},
|
|
3124
|
-
forwardsToTransport: true
|
|
3125
|
-
},
|
|
3126
|
-
{ retry: DEFAULT_ROUTE_RETRY_POLICY, timeoutMs: DEFAULTS.routeTimeoutMs }
|
|
3127
|
-
);
|
|
3128
|
-
}
|
|
3129
|
-
#requireTransport(name) {
|
|
3130
|
-
const transport = this.#transports.get(name);
|
|
3131
|
-
if (transport === void 0) {
|
|
3132
|
-
throw new chunk3OHMEMZA_cjs.ConfigurationError(`No transport named '${name}' is configured`);
|
|
3133
|
-
}
|
|
3134
|
-
return transport;
|
|
3135
|
-
}
|
|
3136
|
-
#scheduleWake() {
|
|
3137
|
-
if (this.#state !== "running" || this.#dispatcher.watchesNewDeliveries) {
|
|
3551
|
+
#scheduleWake(committed) {
|
|
3552
|
+
if (this.#lifecycle.state !== "running" || this.#parts.dispatcher.watchesNewDeliveries) {
|
|
3138
3553
|
return;
|
|
3139
3554
|
}
|
|
3140
|
-
|
|
3141
|
-
run: () => this.#dispatcher.wake(),
|
|
3142
|
-
delayMs: WAKE_DELAY_MS,
|
|
3143
|
-
unref: true
|
|
3144
|
-
});
|
|
3555
|
+
this.#wakeUps.afterPublish(committed);
|
|
3145
3556
|
}
|
|
3146
3557
|
/** Closes every transport, each at most until the deadline; past it they are asked to force. */
|
|
3147
3558
|
async #closeTransports(deadline) {
|
|
3148
3559
|
const options = { signal: deadline.signal };
|
|
3149
|
-
for (const transport of this.#transports
|
|
3560
|
+
for (const transport of this.#parts.connections.transports) {
|
|
3150
3561
|
try {
|
|
3151
3562
|
await deadline.race(transport.close(options));
|
|
3152
3563
|
} catch (error) {
|
|
3153
|
-
this.#logger.error(
|
|
3154
|
-
`[RelayBus](closeTransports) Failed to close transport | transport=${transport.name} error=${
|
|
3564
|
+
this.#parts.logger.error(
|
|
3565
|
+
`[RelayBus](closeTransports) Failed to close transport | transport=${transport.name} error=${chunkJ76M7XTS_cjs.toErrorInfo(error).message}`
|
|
3155
3566
|
);
|
|
3156
3567
|
}
|
|
3157
3568
|
}
|
|
3158
3569
|
}
|
|
3159
3570
|
};
|
|
3160
|
-
function toDescriptor(registration, eventType) {
|
|
3161
|
-
return {
|
|
3162
|
-
id: registration.id,
|
|
3163
|
-
eventType,
|
|
3164
|
-
retry: registration.retry,
|
|
3165
|
-
timeoutMs: registration.timeoutMs,
|
|
3166
|
-
concurrency: registration.concurrency,
|
|
3167
|
-
transactional: registration.transactional,
|
|
3168
|
-
forwardsToTransport: registration.forwardsToTransport
|
|
3169
|
-
};
|
|
3170
|
-
}
|
|
3171
|
-
function describeEvent(event) {
|
|
3172
|
-
if (typeof event === "string") {
|
|
3173
|
-
chunk3OHMEMZA_cjs.assertEventType(event);
|
|
3174
|
-
return { type: event, schema: void 0 };
|
|
3175
|
-
}
|
|
3176
|
-
chunk3OHMEMZA_cjs.assertEventType(event.type);
|
|
3177
|
-
return { type: event.type, schema: event.schema };
|
|
3178
|
-
}
|
|
3179
3571
|
|
|
3180
3572
|
Object.defineProperty(exports, "AttemptsExhaustedError", {
|
|
3181
3573
|
enumerable: true,
|
|
3182
|
-
get: function () { return
|
|
3574
|
+
get: function () { return chunkJ76M7XTS_cjs.AttemptsExhaustedError; }
|
|
3183
3575
|
});
|
|
3184
3576
|
Object.defineProperty(exports, "ConfigurationError", {
|
|
3185
3577
|
enumerable: true,
|
|
3186
|
-
get: function () { return
|
|
3578
|
+
get: function () { return chunkJ76M7XTS_cjs.ConfigurationError; }
|
|
3187
3579
|
});
|
|
3188
3580
|
Object.defineProperty(exports, "DispatcherStoppedError", {
|
|
3189
3581
|
enumerable: true,
|
|
3190
|
-
get: function () { return
|
|
3582
|
+
get: function () { return chunkJ76M7XTS_cjs.DispatcherStoppedError; }
|
|
3191
3583
|
});
|
|
3192
3584
|
Object.defineProperty(exports, "DrainIncompleteError", {
|
|
3193
3585
|
enumerable: true,
|
|
3194
|
-
get: function () { return
|
|
3586
|
+
get: function () { return chunkJ76M7XTS_cjs.DrainIncompleteError; }
|
|
3195
3587
|
});
|
|
3196
3588
|
Object.defineProperty(exports, "EventValidationError", {
|
|
3197
3589
|
enumerable: true,
|
|
3198
|
-
get: function () { return
|
|
3590
|
+
get: function () { return chunkJ76M7XTS_cjs.EventValidationError; }
|
|
3199
3591
|
});
|
|
3200
3592
|
Object.defineProperty(exports, "HandlerTimeoutError", {
|
|
3201
3593
|
enumerable: true,
|
|
3202
|
-
get: function () { return
|
|
3594
|
+
get: function () { return chunkJ76M7XTS_cjs.HandlerTimeoutError; }
|
|
3203
3595
|
});
|
|
3204
3596
|
Object.defineProperty(exports, "InvalidEnvelopeError", {
|
|
3205
3597
|
enumerable: true,
|
|
3206
|
-
get: function () { return
|
|
3598
|
+
get: function () { return chunkJ76M7XTS_cjs.InvalidEnvelopeError; }
|
|
3207
3599
|
});
|
|
3208
3600
|
Object.defineProperty(exports, "LeaseLostError", {
|
|
3209
3601
|
enumerable: true,
|
|
3210
|
-
get: function () { return
|
|
3602
|
+
get: function () { return chunkJ76M7XTS_cjs.LeaseLostError; }
|
|
3211
3603
|
});
|
|
3212
3604
|
Object.defineProperty(exports, "LifecycleError", {
|
|
3213
3605
|
enumerable: true,
|
|
3214
|
-
get: function () { return
|
|
3606
|
+
get: function () { return chunkJ76M7XTS_cjs.LifecycleError; }
|
|
3607
|
+
});
|
|
3608
|
+
Object.defineProperty(exports, "MAX_TIMER_DELAY_MS", {
|
|
3609
|
+
enumerable: true,
|
|
3610
|
+
get: function () { return chunkJ76M7XTS_cjs.MAX_TIMER_DELAY_MS; }
|
|
3215
3611
|
});
|
|
3216
3612
|
Object.defineProperty(exports, "NonRetryableError", {
|
|
3217
3613
|
enumerable: true,
|
|
3218
|
-
get: function () { return
|
|
3614
|
+
get: function () { return chunkJ76M7XTS_cjs.NonRetryableError; }
|
|
3219
3615
|
});
|
|
3220
3616
|
Object.defineProperty(exports, "RelayBusError", {
|
|
3221
3617
|
enumerable: true,
|
|
3222
|
-
get: function () { return
|
|
3618
|
+
get: function () { return chunkJ76M7XTS_cjs.RelayBusError; }
|
|
3223
3619
|
});
|
|
3224
3620
|
Object.defineProperty(exports, "TransactionRequiredError", {
|
|
3225
3621
|
enumerable: true,
|
|
3226
|
-
get: function () { return
|
|
3622
|
+
get: function () { return chunkJ76M7XTS_cjs.TransactionRequiredError; }
|
|
3227
3623
|
});
|
|
3228
3624
|
Object.defineProperty(exports, "TransactionTimeoutError", {
|
|
3229
3625
|
enumerable: true,
|
|
3230
|
-
get: function () { return
|
|
3626
|
+
get: function () { return chunkJ76M7XTS_cjs.TransactionTimeoutError; }
|
|
3231
3627
|
});
|
|
3232
3628
|
Object.defineProperty(exports, "assertEventType", {
|
|
3233
3629
|
enumerable: true,
|
|
3234
|
-
get: function () { return
|
|
3630
|
+
get: function () { return chunkJ76M7XTS_cjs.assertEventType; }
|
|
3235
3631
|
});
|
|
3236
3632
|
Object.defineProperty(exports, "defineEvent", {
|
|
3237
3633
|
enumerable: true,
|
|
3238
|
-
get: function () { return
|
|
3634
|
+
get: function () { return chunkJ76M7XTS_cjs.defineEvent; }
|
|
3239
3635
|
});
|
|
3240
3636
|
Object.defineProperty(exports, "toErrorInfo", {
|
|
3241
3637
|
enumerable: true,
|
|
3242
|
-
get: function () { return
|
|
3638
|
+
get: function () { return chunkJ76M7XTS_cjs.toErrorInfo; }
|
|
3639
|
+
});
|
|
3640
|
+
Object.defineProperty(exports, "toMilliseconds", {
|
|
3641
|
+
enumerable: true,
|
|
3642
|
+
get: function () { return chunkJ76M7XTS_cjs.toMilliseconds; }
|
|
3243
3643
|
});
|
|
3244
3644
|
Object.defineProperty(exports, "validatePayload", {
|
|
3245
3645
|
enumerable: true,
|
|
3246
|
-
get: function () { return
|
|
3646
|
+
get: function () { return chunkJ76M7XTS_cjs.validatePayload; }
|
|
3247
3647
|
});
|
|
3248
3648
|
exports.CatalogPublisher = CatalogPublisher;
|
|
3249
3649
|
exports.DEFAULT_RETRY_POLICY = DEFAULT_RETRY_POLICY;
|
|
@@ -3251,7 +3651,6 @@ exports.DEFAULT_ROUTE_RETRY_POLICY = DEFAULT_ROUTE_RETRY_POLICY;
|
|
|
3251
3651
|
exports.DELIVERY_STATUSES = DELIVERY_STATUSES;
|
|
3252
3652
|
exports.InMemoryEventStore = InMemoryEventStore;
|
|
3253
3653
|
exports.InMemoryTransaction = InMemoryTransaction;
|
|
3254
|
-
exports.MAX_TIMER_DELAY_MS = MAX_TIMER_DELAY_MS;
|
|
3255
3654
|
exports.RelayBus = RelayBus;
|
|
3256
3655
|
exports.TRACEPARENT_HEADER = TRACEPARENT_HEADER;
|
|
3257
3656
|
exports.TRACESTATE_HEADER = TRACESTATE_HEADER;
|
|
@@ -3265,7 +3664,6 @@ exports.describeCatalog = describeCatalog;
|
|
|
3265
3664
|
exports.isRetryableError = isRetryableError;
|
|
3266
3665
|
exports.produce = produce;
|
|
3267
3666
|
exports.systemClock = systemClock;
|
|
3268
|
-
exports.toMilliseconds = toMilliseconds;
|
|
3269
3667
|
exports.traceContextOf = traceContextOf;
|
|
3270
3668
|
//# sourceMappingURL=index.cjs.map
|
|
3271
3669
|
//# sourceMappingURL=index.cjs.map
|