@relaybus/core 0.2.0 → 0.3.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 +120 -8
- package/dist/chunk-3OHMEMZA.cjs +205 -0
- package/dist/chunk-3OHMEMZA.cjs.map +1 -0
- package/dist/chunk-WS274DL3.js +183 -0
- package/dist/chunk-WS274DL3.js.map +1 -0
- package/dist/event-DCRTuYKI.d.cts +142 -0
- package/dist/event-DCRTuYKI.d.ts +142 -0
- package/dist/index.cjs +1405 -589
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +372 -129
- package/dist/index.d.ts +372 -129
- package/dist/index.js +1283 -524
- package/dist/index.js.map +1 -1
- package/dist/internal/index.cjs +444 -0
- package/dist/internal/index.cjs.map +1 -0
- package/dist/internal/index.d.cts +171 -0
- package/dist/internal/index.d.ts +171 -0
- package/dist/internal/index.js +425 -0
- package/dist/internal/index.js.map +1 -0
- package/package.json +18 -1
package/dist/index.js
CHANGED
|
@@ -1,72 +1,14 @@
|
|
|
1
|
+
import { ConfigurationError, assertEventType, NonRetryableError, LifecycleError, DrainIncompleteError, toErrorInfo, schedule, TransactionRequiredError, validatePayload, keyFromDefinition, assertPositiveInteger, DispatcherStoppedError, AttemptsExhaustedError, LeaseLostError, HandlerTimeoutError, TransactionTimeoutError, InvalidEnvelopeError } from './chunk-WS274DL3.js';
|
|
2
|
+
export { AttemptsExhaustedError, ConfigurationError, DispatcherStoppedError, DrainIncompleteError, EventValidationError, HandlerTimeoutError, InvalidEnvelopeError, LeaseLostError, LifecycleError, NonRetryableError, RelayBusError, TransactionRequiredError, TransactionTimeoutError, assertEventType, defineEvent, toErrorInfo, validatePayload } from './chunk-WS274DL3.js';
|
|
1
3
|
import { randomBytes } from 'crypto';
|
|
2
|
-
import { hostname } from 'os';
|
|
3
4
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
5
|
+
import { hostname } from 'os';
|
|
4
6
|
|
|
5
7
|
// src/clock.ts
|
|
6
8
|
var systemClock = {
|
|
7
9
|
now: () => /* @__PURE__ */ new Date()
|
|
8
10
|
};
|
|
9
11
|
|
|
10
|
-
// src/errors.ts
|
|
11
|
-
var RelayBusError = class extends Error {
|
|
12
|
-
// Explicit names survive minification, unlike `new.target.name`.
|
|
13
|
-
name = "RelayBusError";
|
|
14
|
-
};
|
|
15
|
-
var ConfigurationError = class extends RelayBusError {
|
|
16
|
-
name = "ConfigurationError";
|
|
17
|
-
};
|
|
18
|
-
var TransactionRequiredError = class extends RelayBusError {
|
|
19
|
-
name = "TransactionRequiredError";
|
|
20
|
-
};
|
|
21
|
-
var NonRetryableError = class extends RelayBusError {
|
|
22
|
-
name = "NonRetryableError";
|
|
23
|
-
};
|
|
24
|
-
var EventValidationError = class extends NonRetryableError {
|
|
25
|
-
constructor(message, issues) {
|
|
26
|
-
super(message);
|
|
27
|
-
this.issues = issues;
|
|
28
|
-
}
|
|
29
|
-
issues;
|
|
30
|
-
name = "EventValidationError";
|
|
31
|
-
};
|
|
32
|
-
var InvalidEnvelopeError = class extends NonRetryableError {
|
|
33
|
-
name = "InvalidEnvelopeError";
|
|
34
|
-
};
|
|
35
|
-
var LeaseLostError = class extends RelayBusError {
|
|
36
|
-
name = "LeaseLostError";
|
|
37
|
-
/**
|
|
38
|
-
* Whether the store said so (a fenced write returned false). A worker that only noticed
|
|
39
|
-
* locally that its lease ran out may still own the delivery in the store and has to give it back.
|
|
40
|
-
*/
|
|
41
|
-
storeConfirmed;
|
|
42
|
-
constructor(message, options = {}) {
|
|
43
|
-
super(message);
|
|
44
|
-
this.storeConfirmed = options.storeConfirmed ?? false;
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
var HandlerTimeoutError = class extends RelayBusError {
|
|
48
|
-
name = "HandlerTimeoutError";
|
|
49
|
-
};
|
|
50
|
-
var DispatcherStoppedError = class extends RelayBusError {
|
|
51
|
-
name = "DispatcherStoppedError";
|
|
52
|
-
};
|
|
53
|
-
function toErrorInfo(error) {
|
|
54
|
-
if (error instanceof Error) {
|
|
55
|
-
return { name: error.name, message: error.message, stack: error.stack };
|
|
56
|
-
}
|
|
57
|
-
return { name: "NonError", message: safeStringify(error) };
|
|
58
|
-
}
|
|
59
|
-
function safeStringify(value) {
|
|
60
|
-
if (typeof value === "string") {
|
|
61
|
-
return value;
|
|
62
|
-
}
|
|
63
|
-
try {
|
|
64
|
-
return JSON.stringify(value) ?? String(value);
|
|
65
|
-
} catch {
|
|
66
|
-
return String(value);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
12
|
// src/duration.ts
|
|
71
13
|
var MILLISECONDS_PER_UNIT = {
|
|
72
14
|
ms: 1,
|
|
@@ -108,46 +50,6 @@ function toTimerMilliseconds(value, name) {
|
|
|
108
50
|
return milliseconds;
|
|
109
51
|
}
|
|
110
52
|
|
|
111
|
-
// src/event.ts
|
|
112
|
-
var EVENT_TYPE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$/;
|
|
113
|
-
function assertEventType(type) {
|
|
114
|
-
if (!EVENT_TYPE_PATTERN.test(type)) {
|
|
115
|
-
throw new ConfigurationError(
|
|
116
|
-
`Event type '${type}' must be 1-255 characters of letters, digits, '.', '_', ':', '/' or '-', starting with a letter or digit`
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
function defineEvent(type, options) {
|
|
121
|
-
assertEventType(type);
|
|
122
|
-
const schema = options?.schema;
|
|
123
|
-
return class DefinedEvent {
|
|
124
|
-
constructor(payload) {
|
|
125
|
-
this.payload = payload;
|
|
126
|
-
}
|
|
127
|
-
payload;
|
|
128
|
-
static type = type;
|
|
129
|
-
static schema = schema;
|
|
130
|
-
type = type;
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
async function validatePayload(type, schema, payload) {
|
|
134
|
-
if (schema === void 0) {
|
|
135
|
-
return payload;
|
|
136
|
-
}
|
|
137
|
-
const result = await schema["~standard"].validate(payload);
|
|
138
|
-
if (result.issues !== void 0) {
|
|
139
|
-
const issues = result.issues.map((issue) => ({
|
|
140
|
-
message: issue.message,
|
|
141
|
-
path: issue.path?.map((segment) => typeof segment === "object" ? segment.key : segment)
|
|
142
|
-
}));
|
|
143
|
-
throw new EventValidationError(
|
|
144
|
-
`Payload of '${type}' failed validation: ${issues.map((issue) => issue.message).join("; ")}`,
|
|
145
|
-
issues
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
return result.value;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
53
|
// src/catalog.ts
|
|
152
54
|
var EVENT_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
153
55
|
var descriptions = /* @__PURE__ */ new WeakMap();
|
|
@@ -232,7 +134,10 @@ function requireCatalogEntry(catalog, event) {
|
|
|
232
134
|
}
|
|
233
135
|
return entry;
|
|
234
136
|
}
|
|
235
|
-
function resolveCatalogHandlers(catalog,
|
|
137
|
+
function resolveCatalogHandlers(catalog, {
|
|
138
|
+
handlers,
|
|
139
|
+
options
|
|
140
|
+
}) {
|
|
236
141
|
const { name } = describeCatalog(catalog);
|
|
237
142
|
const { idPrefix, events, ...subscribeOptions } = options;
|
|
238
143
|
const entries = selectCatalogEntries(catalog, events);
|
|
@@ -262,7 +167,7 @@ function classNameOf(handlers) {
|
|
|
262
167
|
const className = handlers.constructor.name;
|
|
263
168
|
return className === "Object" || className === "" ? void 0 : className;
|
|
264
169
|
}
|
|
265
|
-
function assertExactHandlers(catalog, entries, handlers) {
|
|
170
|
+
function assertExactHandlers(catalog, { entries, handlers }) {
|
|
266
171
|
const expected = new Set(entries.map((entry) => entry.handlerName));
|
|
267
172
|
const present = Object.keys(handlers);
|
|
268
173
|
const missing = [...expected].filter((handlerName) => !present.includes(handlerName));
|
|
@@ -282,7 +187,10 @@ function assertExactHandlers(catalog, entries, handlers) {
|
|
|
282
187
|
}
|
|
283
188
|
}
|
|
284
189
|
function defineHandlers(catalog, handlers, options = {}) {
|
|
285
|
-
assertExactHandlers(catalog,
|
|
190
|
+
assertExactHandlers(catalog, {
|
|
191
|
+
entries: selectCatalogEntries(catalog, options.events),
|
|
192
|
+
handlers
|
|
193
|
+
});
|
|
286
194
|
return handlers;
|
|
287
195
|
}
|
|
288
196
|
|
|
@@ -336,12 +244,12 @@ var CatalogPublisher = class {
|
|
|
336
244
|
// src/catalog-coverage.ts
|
|
337
245
|
var declarations = /* @__PURE__ */ new WeakSet();
|
|
338
246
|
function consume(catalog, options = {}) {
|
|
339
|
-
return declare(catalog, "consume", options);
|
|
247
|
+
return declare(catalog, { role: "consume", options });
|
|
340
248
|
}
|
|
341
249
|
function produce(catalog, options = {}) {
|
|
342
|
-
return declare(catalog, "produce", options);
|
|
250
|
+
return declare(catalog, { role: "produce", options });
|
|
343
251
|
}
|
|
344
|
-
function declare(catalog, role, options) {
|
|
252
|
+
function declare(catalog, { role, options }) {
|
|
345
253
|
const entries = selectCatalogEntries(catalog, options.events);
|
|
346
254
|
const declaration = Object.freeze({
|
|
347
255
|
catalog,
|
|
@@ -369,7 +277,7 @@ function assertCatalogsCovered(requirements, registrationsOf) {
|
|
|
369
277
|
let onlyBareCatalogs = true;
|
|
370
278
|
for (const { catalogName, entries, need } of requirements) {
|
|
371
279
|
for (const entry of entries) {
|
|
372
|
-
const problem = describeGap(entry, need, registrationsOf(entry.type));
|
|
280
|
+
const problem = describeGap(entry, { need, registrations: registrationsOf(entry.type) });
|
|
373
281
|
if (problem !== void 0) {
|
|
374
282
|
problems.add(`${catalogName === void 0 ? "" : `${catalogName}.`}${problem}`);
|
|
375
283
|
onlyBareCatalogs &&= need === "handler-or-route";
|
|
@@ -384,7 +292,10 @@ ${[...problems].map((line) => ` - ${line}`).join("\n")}`
|
|
|
384
292
|
);
|
|
385
293
|
}
|
|
386
294
|
}
|
|
387
|
-
function describeGap(entry,
|
|
295
|
+
function describeGap(entry, {
|
|
296
|
+
need,
|
|
297
|
+
registrations: { handlers, routes }
|
|
298
|
+
}) {
|
|
388
299
|
const subject = `${entry.name} (${entry.type})`;
|
|
389
300
|
if (need === "handler-or-route") {
|
|
390
301
|
return handlers + routes > 0 ? void 0 : `${subject}: expected handler method ${entry.handlerName}`;
|
|
@@ -410,20 +321,26 @@ var DELIVERY_STATUSES = [
|
|
|
410
321
|
"completed",
|
|
411
322
|
"dead"
|
|
412
323
|
];
|
|
324
|
+
var UUID_BYTES = 16;
|
|
325
|
+
var TIMESTAMP_BYTES = 6;
|
|
326
|
+
var BYTE_VALUES = 256;
|
|
327
|
+
var LOW_NIBBLE = 15;
|
|
328
|
+
var VERSION_7 = 112;
|
|
329
|
+
var VARIANT_PAYLOAD = 63;
|
|
330
|
+
var RFC_9562_VARIANT = 128;
|
|
413
331
|
function createUuidV7(timestamp = Date.now()) {
|
|
414
|
-
const bytes = randomBytes(
|
|
332
|
+
const bytes = randomBytes(UUID_BYTES);
|
|
415
333
|
let remaining = timestamp;
|
|
416
|
-
for (let index =
|
|
417
|
-
bytes[index] = remaining %
|
|
418
|
-
remaining = Math.floor(remaining /
|
|
334
|
+
for (let index = TIMESTAMP_BYTES - 1; index >= 0; index -= 1) {
|
|
335
|
+
bytes[index] = remaining % BYTE_VALUES;
|
|
336
|
+
remaining = Math.floor(remaining / BYTE_VALUES);
|
|
419
337
|
}
|
|
420
|
-
bytes[6] = bytes[6] &
|
|
421
|
-
bytes[8] = bytes[8] &
|
|
422
|
-
|
|
423
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
338
|
+
bytes[6] = bytes[6] & LOW_NIBBLE | VERSION_7;
|
|
339
|
+
bytes[8] = bytes[8] & VARIANT_PAYLOAD | RFC_9562_VARIANT;
|
|
340
|
+
return bytes.toString("hex").replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, "$1-$2-$3-$4-$5");
|
|
424
341
|
}
|
|
425
342
|
|
|
426
|
-
// src/in-memory-
|
|
343
|
+
// src/in-memory-transaction.ts
|
|
427
344
|
var InMemoryTransaction = class {
|
|
428
345
|
#operations = [];
|
|
429
346
|
#finishers = [];
|
|
@@ -458,6 +375,9 @@ var InMemoryTransaction = class {
|
|
|
458
375
|
}
|
|
459
376
|
}
|
|
460
377
|
};
|
|
378
|
+
|
|
379
|
+
// src/in-memory-event-store.ts
|
|
380
|
+
var DEFAULT_LIST_LIMIT = 100;
|
|
461
381
|
var InMemoryEventStore = class {
|
|
462
382
|
#events = /* @__PURE__ */ new Map();
|
|
463
383
|
#deliveries = /* @__PURE__ */ new Map();
|
|
@@ -487,6 +407,7 @@ var InMemoryEventStore = class {
|
|
|
487
407
|
throw error;
|
|
488
408
|
}
|
|
489
409
|
}
|
|
410
|
+
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
490
411
|
async append(transaction, entries, now) {
|
|
491
412
|
await this.#lock(
|
|
492
413
|
transaction,
|
|
@@ -511,50 +432,63 @@ var InMemoryEventStore = class {
|
|
|
511
432
|
appendedEventIds.push(eventId);
|
|
512
433
|
pendingEventIds.add(eventId);
|
|
513
434
|
}
|
|
514
|
-
const newDeliveries = entry
|
|
515
|
-
const deliveryKey = `${eventId}\0${delivery.handler}`;
|
|
516
|
-
if (pendingDeliveryKeys.has(deliveryKey) || this.#events.get(eventId)?.handlers.has(delivery.handler) === true) {
|
|
517
|
-
return false;
|
|
518
|
-
}
|
|
519
|
-
pendingDeliveryKeys.add(deliveryKey);
|
|
520
|
-
return true;
|
|
521
|
-
});
|
|
435
|
+
const newDeliveries = this.#newDeliveries(entry, pendingDeliveryKeys);
|
|
522
436
|
appendedDeliveryCount += newDeliveries.length;
|
|
523
437
|
transaction.enqueue(() => {
|
|
524
|
-
|
|
525
|
-
this.#events.set(eventId, {
|
|
526
|
-
envelope: serialized,
|
|
527
|
-
sequence: this.#nextSequence(serialized.key),
|
|
528
|
-
createdAt: now,
|
|
529
|
-
handlers: /* @__PURE__ */ new Set(),
|
|
530
|
-
completedAt: null
|
|
531
|
-
});
|
|
532
|
-
}
|
|
533
|
-
const stored = this.#events.get(eventId);
|
|
534
|
-
for (const delivery of newDeliveries) {
|
|
535
|
-
stored.handlers.add(delivery.handler);
|
|
536
|
-
this.#deliveries.set(delivery.id, {
|
|
537
|
-
id: delivery.id,
|
|
538
|
-
eventId,
|
|
539
|
-
handler: delivery.handler,
|
|
540
|
-
key: stored.envelope.key,
|
|
541
|
-
sequence: stored.sequence,
|
|
542
|
-
status: "pending",
|
|
543
|
-
attempts: 0,
|
|
544
|
-
availableAt: delivery.availableAt,
|
|
545
|
-
leaseOwner: null,
|
|
546
|
-
leaseToken: null,
|
|
547
|
-
leaseExpiresAt: null,
|
|
548
|
-
lastError: null,
|
|
549
|
-
createdAt: now,
|
|
550
|
-
updatedAt: now,
|
|
551
|
-
completedAt: null
|
|
552
|
-
});
|
|
553
|
-
}
|
|
438
|
+
this.#applyAppend({ eventId, serialized, isDuplicate, newDeliveries, now });
|
|
554
439
|
});
|
|
555
440
|
}
|
|
556
441
|
return { appendedEventIds, duplicateEventIds, appendedDeliveryCount };
|
|
557
442
|
}
|
|
443
|
+
/**
|
|
444
|
+
* The deliveries of `entry` whose handler the event neither has nor gets earlier in the
|
|
445
|
+
* transaction; remembers them in `pendingDeliveryKeys`.
|
|
446
|
+
*/
|
|
447
|
+
#newDeliveries(entry, pendingDeliveryKeys) {
|
|
448
|
+
const eventId = entry.event.id;
|
|
449
|
+
return entry.deliveries.filter((delivery) => {
|
|
450
|
+
const deliveryKey = `${eventId}\0${delivery.handler}`;
|
|
451
|
+
if (pendingDeliveryKeys.has(deliveryKey) || this.#events.get(eventId)?.handlers.has(delivery.handler) === true) {
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
pendingDeliveryKeys.add(deliveryKey);
|
|
455
|
+
return true;
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
/** Applies one entry of an append when its transaction commits. */
|
|
459
|
+
#applyAppend(input) {
|
|
460
|
+
const { eventId, serialized, isDuplicate, newDeliveries, now } = input;
|
|
461
|
+
if (!isDuplicate) {
|
|
462
|
+
this.#events.set(eventId, {
|
|
463
|
+
envelope: serialized,
|
|
464
|
+
sequence: this.#nextSequence(serialized.key),
|
|
465
|
+
createdAt: now,
|
|
466
|
+
handlers: /* @__PURE__ */ new Set(),
|
|
467
|
+
completedAt: null
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
const stored = this.#events.get(eventId);
|
|
471
|
+
for (const delivery of newDeliveries) {
|
|
472
|
+
stored.handlers.add(delivery.handler);
|
|
473
|
+
this.#deliveries.set(delivery.id, {
|
|
474
|
+
id: delivery.id,
|
|
475
|
+
eventId,
|
|
476
|
+
handler: delivery.handler,
|
|
477
|
+
key: stored.envelope.key,
|
|
478
|
+
sequence: stored.sequence,
|
|
479
|
+
status: "pending",
|
|
480
|
+
attempts: 0,
|
|
481
|
+
availableAt: delivery.availableAt,
|
|
482
|
+
leaseOwner: null,
|
|
483
|
+
leaseToken: null,
|
|
484
|
+
leaseExpiresAt: null,
|
|
485
|
+
lastError: null,
|
|
486
|
+
createdAt: now,
|
|
487
|
+
updatedAt: now,
|
|
488
|
+
completedAt: null
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
558
492
|
async claim(request) {
|
|
559
493
|
const handlers = new Set(request.handlers);
|
|
560
494
|
const due = [...this.#deliveries.values()].filter((delivery) => handlers.has(delivery.handler) && isDue(delivery, request.now)).sort(compareDue);
|
|
@@ -583,12 +517,14 @@ var InMemoryEventStore = class {
|
|
|
583
517
|
}
|
|
584
518
|
return claimed;
|
|
585
519
|
}
|
|
520
|
+
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
586
521
|
async extendLease(lease, leaseExpiresAt, now) {
|
|
587
522
|
return this.#updateLeased(lease, (delivery) => {
|
|
588
523
|
delivery.leaseExpiresAt = leaseExpiresAt;
|
|
589
524
|
delivery.updatedAt = now;
|
|
590
525
|
});
|
|
591
526
|
}
|
|
527
|
+
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
592
528
|
async complete(lease, now, transaction) {
|
|
593
529
|
if (transaction === void 0) {
|
|
594
530
|
return this.#updateLeased(lease, (delivery) => this.#finish(delivery, now));
|
|
@@ -607,6 +543,7 @@ var InMemoryEventStore = class {
|
|
|
607
543
|
async completeMany(leases, now) {
|
|
608
544
|
return Promise.all(leases.map((lease) => this.complete(lease, now)));
|
|
609
545
|
}
|
|
546
|
+
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
610
547
|
async reschedule(lease, availableAt, error, now) {
|
|
611
548
|
return this.#updateLeased(lease, (delivery) => {
|
|
612
549
|
delivery.status = "pending";
|
|
@@ -625,6 +562,7 @@ var InMemoryEventStore = class {
|
|
|
625
562
|
clearLease(delivery);
|
|
626
563
|
});
|
|
627
564
|
}
|
|
565
|
+
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
628
566
|
async bury(lease, error, now) {
|
|
629
567
|
return this.#updateLeased(lease, (delivery) => {
|
|
630
568
|
delivery.status = "dead";
|
|
@@ -634,8 +572,10 @@ var InMemoryEventStore = class {
|
|
|
634
572
|
});
|
|
635
573
|
}
|
|
636
574
|
async listDeliveries(query) {
|
|
637
|
-
const limit = query.limit ??
|
|
638
|
-
return [...this.#deliveries.values()].filter(
|
|
575
|
+
const limit = query.limit ?? DEFAULT_LIST_LIMIT;
|
|
576
|
+
return [...this.#deliveries.values()].filter(
|
|
577
|
+
(delivery) => matchesQuery(delivery, { event: this.#events.get(delivery.eventId), query })
|
|
578
|
+
).sort(
|
|
639
579
|
(left, right) => left.createdAt.getTime() - right.createdAt.getTime() || compareIds(left.id, right.id)
|
|
640
580
|
).slice(0, limit).map((delivery) => ({
|
|
641
581
|
id: delivery.id,
|
|
@@ -677,12 +617,14 @@ var InMemoryEventStore = class {
|
|
|
677
617
|
deletedDeliveries += 1;
|
|
678
618
|
}
|
|
679
619
|
}
|
|
620
|
+
return { deletedDeliveries, deletedEvents: this.#purgeEvents(request, touchedEventIds) };
|
|
621
|
+
}
|
|
622
|
+
/** Deletes the events no delivery references whose retention ran out; returns how many. */
|
|
623
|
+
#purgeEvents(request, touchedEventIds) {
|
|
680
624
|
const referencedEventIds = new Set(
|
|
681
625
|
[...this.#deliveries.values()].map((delivery) => delivery.eventId)
|
|
682
626
|
);
|
|
683
|
-
const
|
|
684
|
-
const orphanCutoff = cutoffs.length === 0 ? Number.NEGATIVE_INFINITY : Math.min(...cutoffs);
|
|
685
|
-
const completedCutoff = request.completedBefore?.getTime() ?? Number.NEGATIVE_INFINITY;
|
|
627
|
+
const { orphanCutoff, completedCutoff } = eventCutoffs(request);
|
|
686
628
|
let deletedEvents = 0;
|
|
687
629
|
for (const [eventId, event] of [...this.#events]) {
|
|
688
630
|
if (referencedEventIds.has(eventId) || this.#locks.has(`event:${eventId}`)) {
|
|
@@ -694,7 +636,7 @@ var InMemoryEventStore = class {
|
|
|
694
636
|
deletedEvents += 1;
|
|
695
637
|
}
|
|
696
638
|
}
|
|
697
|
-
return
|
|
639
|
+
return deletedEvents;
|
|
698
640
|
}
|
|
699
641
|
async statistics(now) {
|
|
700
642
|
const counts = {
|
|
@@ -769,12 +711,7 @@ var InMemoryEventStore = class {
|
|
|
769
711
|
return false;
|
|
770
712
|
}
|
|
771
713
|
for (const other of this.#deliveries.values()) {
|
|
772
|
-
if (other
|
|
773
|
-
continue;
|
|
774
|
-
}
|
|
775
|
-
const unfinishedPredecessor = other.sequence < delivery.sequence && (other.status === "pending" || other.status === "processing");
|
|
776
|
-
const runningUnderLiveLease = other.status === "processing" && other.leaseExpiresAt > now;
|
|
777
|
-
if (unfinishedPredecessor || runningUnderLiveLease) {
|
|
714
|
+
if (blocksKey(other, { delivery, now })) {
|
|
778
715
|
return true;
|
|
779
716
|
}
|
|
780
717
|
}
|
|
@@ -816,6 +753,21 @@ var InMemoryEventStore = class {
|
|
|
816
753
|
return { ...copy, occurredAt: new Date(copy.occurredAt) };
|
|
817
754
|
}
|
|
818
755
|
};
|
|
756
|
+
function blocksKey(other, { delivery, now }) {
|
|
757
|
+
if (other === delivery || other.handler !== delivery.handler || other.key !== delivery.key) {
|
|
758
|
+
return false;
|
|
759
|
+
}
|
|
760
|
+
const unfinishedPredecessor = other.sequence < delivery.sequence && (other.status === "pending" || other.status === "processing");
|
|
761
|
+
const runningUnderLiveLease = other.status === "processing" && other.leaseExpiresAt > now;
|
|
762
|
+
return unfinishedPredecessor || runningUnderLiveLease;
|
|
763
|
+
}
|
|
764
|
+
function eventCutoffs(request) {
|
|
765
|
+
const cutoffs = [request.completedBefore, request.deadBefore].filter((cutoff) => cutoff !== void 0).map((cutoff) => cutoff.getTime());
|
|
766
|
+
return {
|
|
767
|
+
orphanCutoff: cutoffs.length === 0 ? Number.NEGATIVE_INFINITY : Math.min(...cutoffs),
|
|
768
|
+
completedCutoff: request.completedBefore?.getTime() ?? Number.NEGATIVE_INFINITY
|
|
769
|
+
};
|
|
770
|
+
}
|
|
819
771
|
function serializeEnvelope(envelope) {
|
|
820
772
|
return JSON.parse(JSON.stringify(envelope));
|
|
821
773
|
}
|
|
@@ -840,7 +792,7 @@ function clearLease(delivery) {
|
|
|
840
792
|
delivery.leaseToken = null;
|
|
841
793
|
delivery.leaseExpiresAt = null;
|
|
842
794
|
}
|
|
843
|
-
function matchesQuery(delivery, event, query) {
|
|
795
|
+
function matchesQuery(delivery, { event, query }) {
|
|
844
796
|
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);
|
|
845
797
|
}
|
|
846
798
|
function isPurgeable(delivery, request) {
|
|
@@ -853,6 +805,276 @@ function isPurgeable(delivery, request) {
|
|
|
853
805
|
return false;
|
|
854
806
|
}
|
|
855
807
|
|
|
808
|
+
// src/tracing.ts
|
|
809
|
+
var TRACEPARENT_HEADER = "traceparent";
|
|
810
|
+
var TRACESTATE_HEADER = "tracestate";
|
|
811
|
+
var TRACEPARENT_FIELDS = "(?!0{32})[\\da-f]{32}-(?!0{16})[\\da-f]{16}-[\\da-f]{2}";
|
|
812
|
+
var TRACEPARENT = new RegExp(
|
|
813
|
+
`^(?:00-${TRACEPARENT_FIELDS}|(?!00|ff)[\\da-f]{2}-${TRACEPARENT_FIELDS}(?:-.*)?)$`
|
|
814
|
+
);
|
|
815
|
+
function traceContextOf(headers) {
|
|
816
|
+
const traceparent = headers[TRACEPARENT_HEADER];
|
|
817
|
+
if (traceparent === void 0 || !TRACEPARENT.test(traceparent)) {
|
|
818
|
+
return void 0;
|
|
819
|
+
}
|
|
820
|
+
const tracestate = headers[TRACESTATE_HEADER];
|
|
821
|
+
return tracestate === void 0 ? { traceparent } : { traceparent, tracestate };
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// src/bus-observation.ts
|
|
825
|
+
function withTraceHeaders(options, headers) {
|
|
826
|
+
return Object.keys(headers).length === 0 ? options : { ...options, headers: { ...headers, ...options.headers } };
|
|
827
|
+
}
|
|
828
|
+
var BusObservation = class _BusObservation {
|
|
829
|
+
#notifier;
|
|
830
|
+
#tracer;
|
|
831
|
+
#clock;
|
|
832
|
+
#publishes;
|
|
833
|
+
#receives;
|
|
834
|
+
#sends;
|
|
835
|
+
constructor(input) {
|
|
836
|
+
this.#notifier = input.notifier;
|
|
837
|
+
this.#tracer = input.tracer;
|
|
838
|
+
this.#clock = input.clock;
|
|
839
|
+
this.#publishes = input.notifier.wants("onPublishFinished");
|
|
840
|
+
this.#receives = input.notifier.wants("onReceiveFinished");
|
|
841
|
+
this.#sends = input.notifier.wants("onTransportSent");
|
|
842
|
+
}
|
|
843
|
+
/** An observation, or undefined when no observer hook and no tracer would see anything. */
|
|
844
|
+
static create(input) {
|
|
845
|
+
const observed = input.tracer !== void 0 || ["onPublishFinished", "onReceiveFinished", "onTransportSent", "onBacklog"].some(
|
|
846
|
+
(method) => input.notifier.wants(method)
|
|
847
|
+
);
|
|
848
|
+
return observed ? new _BusObservation(input) : void 0;
|
|
849
|
+
}
|
|
850
|
+
/** Runs a publish call inside the tracer's publish span, reporting its duration and outcome. */
|
|
851
|
+
async publish(input, run) {
|
|
852
|
+
const startedAt = this.#publishes ? this.#now() : 0;
|
|
853
|
+
const tracer = this.#tracer;
|
|
854
|
+
const traced = tracer?.publish === void 0 ? run({}) : tracer.publish(input, run);
|
|
855
|
+
if (!this.#publishes) {
|
|
856
|
+
return traced;
|
|
857
|
+
}
|
|
858
|
+
const report = (outcome, error) => {
|
|
859
|
+
this.#notifier.notify("onPublishFinished", {
|
|
860
|
+
eventTypes: input.eventTypes,
|
|
861
|
+
durationMs: this.#now() - startedAt,
|
|
862
|
+
outcome,
|
|
863
|
+
...error === void 0 ? {} : { error: toErrorInfo(error) }
|
|
864
|
+
});
|
|
865
|
+
};
|
|
866
|
+
try {
|
|
867
|
+
const result = await traced;
|
|
868
|
+
report("success");
|
|
869
|
+
return result;
|
|
870
|
+
} catch (error) {
|
|
871
|
+
report("error", error);
|
|
872
|
+
throw error;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
/** Runs `receive` for one event, reporting where it came from and what became of it. */
|
|
876
|
+
async receive(input, run) {
|
|
877
|
+
if (!this.#receives) {
|
|
878
|
+
return run();
|
|
879
|
+
}
|
|
880
|
+
const startedAt = this.#now();
|
|
881
|
+
try {
|
|
882
|
+
const outcome = await run();
|
|
883
|
+
this.#reportReceive(input, { outcome, startedAt, failure: void 0 });
|
|
884
|
+
return outcome;
|
|
885
|
+
} catch (error) {
|
|
886
|
+
const outcome = error instanceof InvalidEnvelopeError ? "invalid" : "error";
|
|
887
|
+
this.#reportReceive(input, { outcome, startedAt, failure: error });
|
|
888
|
+
throw error;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
#reportReceive({ envelope, origin }, report) {
|
|
892
|
+
const { headers } = envelope;
|
|
893
|
+
this.#notifier.notify("onReceiveFinished", {
|
|
894
|
+
transport: origin?.transport.name,
|
|
895
|
+
system: origin === void 0 ? void 0 : systemOf(origin.transport),
|
|
896
|
+
destination: origin?.destination,
|
|
897
|
+
eventType: textOrUndefined(envelope.type),
|
|
898
|
+
source: textOrUndefined(envelope.source),
|
|
899
|
+
traceContext: isObject(headers) ? traceContextOf(headers) : void 0,
|
|
900
|
+
outcome: report.outcome,
|
|
901
|
+
durationMs: this.#now() - report.startedAt,
|
|
902
|
+
...report.failure === void 0 ? {} : { error: toErrorInfo(report.failure) }
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
/** The handler of a route: sends inside the tracer's send span and reports each send. */
|
|
906
|
+
sender(transport, destination) {
|
|
907
|
+
if (this.#tracer?.send === void 0 && !this.#sends) {
|
|
908
|
+
return (envelope) => transport.publish(envelope, destination);
|
|
909
|
+
}
|
|
910
|
+
return (envelope) => this.#send({ envelope, transport, destination });
|
|
911
|
+
}
|
|
912
|
+
async #send({ envelope, transport, destination }) {
|
|
913
|
+
const startedAt = this.#sends ? this.#now() : 0;
|
|
914
|
+
const system = systemOf(transport);
|
|
915
|
+
const run = (headers) => transport.publish(
|
|
916
|
+
Object.keys(headers).length === 0 ? envelope : { ...envelope, headers: { ...envelope.headers, ...headers } },
|
|
917
|
+
destination
|
|
918
|
+
);
|
|
919
|
+
const tracer = this.#tracer;
|
|
920
|
+
const sent = tracer?.send === void 0 ? run({}) : tracer.send({ envelope, transport: transport.name, system, destination }, run);
|
|
921
|
+
if (!this.#sends) {
|
|
922
|
+
return sent;
|
|
923
|
+
}
|
|
924
|
+
const report = (outcome, error) => {
|
|
925
|
+
this.#notifier.notify("onTransportSent", {
|
|
926
|
+
envelope,
|
|
927
|
+
transport: transport.name,
|
|
928
|
+
system,
|
|
929
|
+
destination,
|
|
930
|
+
durationMs: this.#now() - startedAt,
|
|
931
|
+
outcome,
|
|
932
|
+
...error === void 0 ? {} : { error: toErrorInfo(error) }
|
|
933
|
+
});
|
|
934
|
+
};
|
|
935
|
+
try {
|
|
936
|
+
await sent;
|
|
937
|
+
report("success");
|
|
938
|
+
} catch (error) {
|
|
939
|
+
report("error", error);
|
|
940
|
+
throw error;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Reads the backlog every `intervalMs` and reports it, when an observer wants it. A reading that
|
|
945
|
+
* is still running when the next one is due is not overlapped. Returns the cancel of the timer.
|
|
946
|
+
*/
|
|
947
|
+
startBacklog({ intervalMs, statistics }) {
|
|
948
|
+
if (!this.#notifier.wants("onBacklog")) {
|
|
949
|
+
return void 0;
|
|
950
|
+
}
|
|
951
|
+
let reading = false;
|
|
952
|
+
return schedule(this.#clock, {
|
|
953
|
+
run: () => {
|
|
954
|
+
if (reading) {
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
reading = true;
|
|
958
|
+
statistics().then((result) => this.#reportBacklog(result)).catch((error) => this.#notifier.failure("backlog", error)).finally(() => {
|
|
959
|
+
reading = false;
|
|
960
|
+
});
|
|
961
|
+
},
|
|
962
|
+
delayMs: intervalMs,
|
|
963
|
+
repeat: true,
|
|
964
|
+
unref: true
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
#reportBacklog(statistics) {
|
|
968
|
+
const oldestDueAt = statistics.oldestDueAt;
|
|
969
|
+
this.#notifier.notify("onBacklog", {
|
|
970
|
+
pending: statistics.pending,
|
|
971
|
+
oldestPendingAgeMs: oldestDueAt === null ? 0 : Math.max(0, this.#now() - oldestDueAt.getTime()),
|
|
972
|
+
statistics
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
#now() {
|
|
976
|
+
return this.#clock.now().getTime();
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
function systemOf(transport) {
|
|
980
|
+
return transport.system ?? transport.name;
|
|
981
|
+
}
|
|
982
|
+
function textOrUndefined(value) {
|
|
983
|
+
return typeof value === "string" ? value : void 0;
|
|
984
|
+
}
|
|
985
|
+
function isObject(value) {
|
|
986
|
+
return typeof value === "object" && value !== null;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// src/dispatch-observation.ts
|
|
990
|
+
var DispatchObservation = class _DispatchObservation {
|
|
991
|
+
#notifier;
|
|
992
|
+
#tracer;
|
|
993
|
+
#clock;
|
|
994
|
+
#claims;
|
|
995
|
+
#runs;
|
|
996
|
+
constructor(input) {
|
|
997
|
+
this.#notifier = input.notifier;
|
|
998
|
+
this.#tracer = input.tracer;
|
|
999
|
+
this.#clock = input.clock;
|
|
1000
|
+
this.#claims = input.notifier.wants("onClaimFinished");
|
|
1001
|
+
this.#runs = input.notifier.wants("onHandlerStarted") || input.notifier.wants("onHandlerFinished");
|
|
1002
|
+
}
|
|
1003
|
+
/** An observation, or undefined when no observer hook and no tracer would see anything. */
|
|
1004
|
+
static create(input) {
|
|
1005
|
+
const observation = new _DispatchObservation(input);
|
|
1006
|
+
return observation.#claims || observation.#runs || input.tracer?.process !== void 0 ? observation : void 0;
|
|
1007
|
+
}
|
|
1008
|
+
/** Claims through `store`, reporting the query's size, lanes and duration. */
|
|
1009
|
+
async claim(store, request) {
|
|
1010
|
+
if (!this.#claims) {
|
|
1011
|
+
return store.claim(request);
|
|
1012
|
+
}
|
|
1013
|
+
const startedAt = this.#clock.now().getTime();
|
|
1014
|
+
const report = { handlers: request.handlers, requested: request.limit };
|
|
1015
|
+
try {
|
|
1016
|
+
const claimed = await store.claim(request);
|
|
1017
|
+
this.#notifier.notify("onClaimFinished", {
|
|
1018
|
+
...report,
|
|
1019
|
+
claimed: claimed.length,
|
|
1020
|
+
lanes: new Set(claimed.map((delivery) => delivery.leaseToken)).size,
|
|
1021
|
+
durationMs: this.#clock.now().getTime() - startedAt,
|
|
1022
|
+
outcome: "success"
|
|
1023
|
+
});
|
|
1024
|
+
return claimed;
|
|
1025
|
+
} catch (error) {
|
|
1026
|
+
this.#notifier.notify("onClaimFinished", {
|
|
1027
|
+
...report,
|
|
1028
|
+
claimed: 0,
|
|
1029
|
+
lanes: 0,
|
|
1030
|
+
durationMs: this.#clock.now().getTime() - startedAt,
|
|
1031
|
+
outcome: "error",
|
|
1032
|
+
error: toErrorInfo(error)
|
|
1033
|
+
});
|
|
1034
|
+
throw error;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
/** Runs a local handler inside the tracer's process span; routes are traced as sends instead. */
|
|
1038
|
+
process(run, handle) {
|
|
1039
|
+
const tracer = this.#tracer;
|
|
1040
|
+
if (tracer?.process === void 0 || run.registration.forwardsToTransport) {
|
|
1041
|
+
return handle();
|
|
1042
|
+
}
|
|
1043
|
+
return tracer.process({ delivery: run.delivery }, async () => handle());
|
|
1044
|
+
}
|
|
1045
|
+
/** Reports the start of a handler run now and its end when `settled` settles, passing it through. */
|
|
1046
|
+
async observe(run, settled) {
|
|
1047
|
+
if (!this.#runs) {
|
|
1048
|
+
await settled;
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
const { delivery } = run;
|
|
1052
|
+
const kind = run.registration.forwardsToTransport ? "route" : "handler";
|
|
1053
|
+
const startedAt = this.#clock.now().getTime();
|
|
1054
|
+
this.#notifier.notify("onHandlerStarted", {
|
|
1055
|
+
delivery,
|
|
1056
|
+
kind,
|
|
1057
|
+
endToEndLatencyMs: startedAt - delivery.event.occurredAt.getTime()
|
|
1058
|
+
});
|
|
1059
|
+
try {
|
|
1060
|
+
await settled;
|
|
1061
|
+
} catch (error) {
|
|
1062
|
+
const durationMs2 = this.#clock.now().getTime() - startedAt;
|
|
1063
|
+
const errorInfo = toErrorInfo(error);
|
|
1064
|
+
this.#notifier.notify("onHandlerFinished", {
|
|
1065
|
+
delivery,
|
|
1066
|
+
kind,
|
|
1067
|
+
durationMs: durationMs2,
|
|
1068
|
+
outcome: "error",
|
|
1069
|
+
error: errorInfo
|
|
1070
|
+
});
|
|
1071
|
+
throw error;
|
|
1072
|
+
}
|
|
1073
|
+
const durationMs = this.#clock.now().getTime() - startedAt;
|
|
1074
|
+
this.#notifier.notify("onHandlerFinished", { delivery, kind, durationMs, outcome: "success" });
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
|
|
856
1078
|
// src/completion-batcher.ts
|
|
857
1079
|
var CompletionBatcher = class {
|
|
858
1080
|
#store;
|
|
@@ -882,7 +1104,10 @@ var CompletionBatcher = class {
|
|
|
882
1104
|
if (this.#closing || this.#pending.length >= this.#settings.maxBatchSize || this.#pending.length >= this.#runningCount()) {
|
|
883
1105
|
this.#flush();
|
|
884
1106
|
} else {
|
|
885
|
-
this.#timer ??=
|
|
1107
|
+
this.#timer ??= schedule(this.#clock, {
|
|
1108
|
+
run: () => this.#flush(),
|
|
1109
|
+
delayMs: this.#settings.windowMs
|
|
1110
|
+
});
|
|
886
1111
|
}
|
|
887
1112
|
});
|
|
888
1113
|
}
|
|
@@ -897,7 +1122,7 @@ var CompletionBatcher = class {
|
|
|
897
1122
|
}
|
|
898
1123
|
}
|
|
899
1124
|
#flush() {
|
|
900
|
-
|
|
1125
|
+
this.#timer?.();
|
|
901
1126
|
this.#timer = void 0;
|
|
902
1127
|
const batch = this.#pending;
|
|
903
1128
|
this.#pending = [];
|
|
@@ -912,6 +1137,57 @@ var CompletionBatcher = class {
|
|
|
912
1137
|
);
|
|
913
1138
|
}
|
|
914
1139
|
};
|
|
1140
|
+
|
|
1141
|
+
// src/delivery-timers.ts
|
|
1142
|
+
var DeliveryTimers = class {
|
|
1143
|
+
#clock;
|
|
1144
|
+
/** Cancel of every timer still waiting, with what its cancellation must also do. */
|
|
1145
|
+
#waiting = /* @__PURE__ */ new Map();
|
|
1146
|
+
constructor(clock) {
|
|
1147
|
+
this.#clock = clock;
|
|
1148
|
+
}
|
|
1149
|
+
/** Runs `task.run` after `task.delayMs`; returns what cancels it. */
|
|
1150
|
+
start(task) {
|
|
1151
|
+
return this.#start(task, () => void 0);
|
|
1152
|
+
}
|
|
1153
|
+
/** Resolves after `milliseconds`, or at once when {@link DeliveryTimers.cancelAll} runs. */
|
|
1154
|
+
sleep(milliseconds) {
|
|
1155
|
+
return new Promise((resolve) => {
|
|
1156
|
+
this.#start({ run: resolve, delayMs: milliseconds }, resolve);
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
cancelAll() {
|
|
1160
|
+
for (const [cancel, onCancel] of [...this.#waiting]) {
|
|
1161
|
+
cancel();
|
|
1162
|
+
onCancel();
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
#start(task, onCancel) {
|
|
1166
|
+
const cancel = () => {
|
|
1167
|
+
cancelTimer();
|
|
1168
|
+
this.#waiting.delete(cancel);
|
|
1169
|
+
};
|
|
1170
|
+
const cancelTimer = schedule(this.#clock, {
|
|
1171
|
+
run: () => {
|
|
1172
|
+
this.#waiting.delete(cancel);
|
|
1173
|
+
task.run();
|
|
1174
|
+
},
|
|
1175
|
+
delayMs: task.delayMs
|
|
1176
|
+
});
|
|
1177
|
+
this.#waiting.set(cancel, onCancel);
|
|
1178
|
+
return cancel;
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
|
|
1182
|
+
// src/dispatcher-support.ts
|
|
1183
|
+
function pendingKey(delivery) {
|
|
1184
|
+
return `${delivery.deliveryId}\0${delivery.leaseToken}`;
|
|
1185
|
+
}
|
|
1186
|
+
function rejectOnAbort(signal) {
|
|
1187
|
+
return new Promise((_, reject) => {
|
|
1188
|
+
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
915
1191
|
var storage = new AsyncLocalStorage();
|
|
916
1192
|
function runInScope(scope, work) {
|
|
917
1193
|
return storage.run({ ...storage.getStore(), ...scope }, work);
|
|
@@ -923,6 +1199,102 @@ function getCurrentScope() {
|
|
|
923
1199
|
return storage.getStore();
|
|
924
1200
|
}
|
|
925
1201
|
|
|
1202
|
+
// src/shutdown-deadline.ts
|
|
1203
|
+
var DRAIN_SHARE = 0.8;
|
|
1204
|
+
var ShutdownDeadline = class {
|
|
1205
|
+
#controller = new AbortController();
|
|
1206
|
+
#drained;
|
|
1207
|
+
#expired;
|
|
1208
|
+
#cancelTimers;
|
|
1209
|
+
constructor(clock, timeoutMs) {
|
|
1210
|
+
let drain;
|
|
1211
|
+
this.#drained = new Promise((resolve) => {
|
|
1212
|
+
drain = () => resolve("timeout");
|
|
1213
|
+
});
|
|
1214
|
+
const signal = this.#controller.signal;
|
|
1215
|
+
this.#expired = new Promise((resolve) => {
|
|
1216
|
+
signal.addEventListener("abort", () => resolve("timeout"), { once: true });
|
|
1217
|
+
});
|
|
1218
|
+
this.#cancelTimers = [
|
|
1219
|
+
schedule(clock, { run: drain, delayMs: Math.floor(timeoutMs * DRAIN_SHARE) }),
|
|
1220
|
+
schedule(clock, { run: () => this.#controller.abort(), delayMs: timeoutMs })
|
|
1221
|
+
];
|
|
1222
|
+
}
|
|
1223
|
+
/** Aborted when the deadline passes. */
|
|
1224
|
+
get signal() {
|
|
1225
|
+
return this.#controller.signal;
|
|
1226
|
+
}
|
|
1227
|
+
get passed() {
|
|
1228
|
+
return this.#controller.signal.aborted;
|
|
1229
|
+
}
|
|
1230
|
+
/** Waits for `work` until the deadline. Rejects when `work` rejects before it. */
|
|
1231
|
+
race(work) {
|
|
1232
|
+
return Promise.race([work.then(() => "done"), this.#expired]);
|
|
1233
|
+
}
|
|
1234
|
+
/** Waits for running handlers until the drain point, when they are aborted. */
|
|
1235
|
+
raceDrain(work) {
|
|
1236
|
+
return Promise.race([work.then(() => "done"), this.#drained]);
|
|
1237
|
+
}
|
|
1238
|
+
/** Cancels the timers once the shutdown is over. */
|
|
1239
|
+
dispose() {
|
|
1240
|
+
for (const cancel of this.#cancelTimers) {
|
|
1241
|
+
cancel();
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1245
|
+
async function withinDeadline({ clock, timeoutMs }, work) {
|
|
1246
|
+
const startedAt = clock.now().getTime();
|
|
1247
|
+
const deadline = new ShutdownDeadline(clock, timeoutMs);
|
|
1248
|
+
try {
|
|
1249
|
+
const abandonedDeliveries = await work(deadline);
|
|
1250
|
+
return {
|
|
1251
|
+
graceful: !deadline.passed,
|
|
1252
|
+
abandonedDeliveries,
|
|
1253
|
+
durationMs: clock.now().getTime() - startedAt
|
|
1254
|
+
};
|
|
1255
|
+
} finally {
|
|
1256
|
+
deadline.dispose();
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// src/lease-heartbeat.ts
|
|
1261
|
+
var RENEWALS_PER_LEASE = 3;
|
|
1262
|
+
function startLeaseHeartbeat({ store, clock, notifier, settings }, { delivery, controller }) {
|
|
1263
|
+
let confirmedExpiry = delivery.leaseExpiresAt.getTime();
|
|
1264
|
+
let renewing = false;
|
|
1265
|
+
const abortLeaseLost = (storeConfirmed = false) => {
|
|
1266
|
+
controller.abort(
|
|
1267
|
+
new LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost`, { storeConfirmed })
|
|
1268
|
+
);
|
|
1269
|
+
};
|
|
1270
|
+
return schedule(clock, {
|
|
1271
|
+
run: () => {
|
|
1272
|
+
const now = clock.now();
|
|
1273
|
+
if (now.getTime() >= confirmedExpiry) {
|
|
1274
|
+
abortLeaseLost();
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
if (renewing) {
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
renewing = true;
|
|
1281
|
+
const expiry = now.getTime() + settings.leaseDurationMs;
|
|
1282
|
+
store.extendLease(delivery, new Date(expiry), now).then((extended) => {
|
|
1283
|
+
if (extended) {
|
|
1284
|
+
confirmedExpiry = expiry;
|
|
1285
|
+
} else {
|
|
1286
|
+
abortLeaseLost(true);
|
|
1287
|
+
}
|
|
1288
|
+
}).catch((error) => notifier.failure("extend-lease", error)).finally(() => {
|
|
1289
|
+
renewing = false;
|
|
1290
|
+
});
|
|
1291
|
+
},
|
|
1292
|
+
delayMs: Math.max(1, Math.floor(settings.leaseDurationMs / RENEWALS_PER_LEASE)),
|
|
1293
|
+
repeat: true,
|
|
1294
|
+
unref: true
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
|
|
926
1298
|
// src/retry-policy.ts
|
|
927
1299
|
var DEFAULT_RETRY_POLICY = {
|
|
928
1300
|
attempts: 10,
|
|
@@ -942,18 +1314,26 @@ var DEFAULT_ROUTE_RETRY_POLICY = {
|
|
|
942
1314
|
};
|
|
943
1315
|
var JITTER_STRATEGIES = ["full", "equal", "none"];
|
|
944
1316
|
function createRetryPolicy(options, base = DEFAULT_RETRY_POLICY) {
|
|
945
|
-
const
|
|
946
|
-
|
|
947
|
-
|
|
1317
|
+
const policy = mergeRetryPolicy(options, base);
|
|
1318
|
+
assertValidRetryPolicy(policy);
|
|
1319
|
+
return policy;
|
|
1320
|
+
}
|
|
1321
|
+
function mergeRetryPolicy(options, base) {
|
|
1322
|
+
const overrides = options ?? {};
|
|
1323
|
+
const initialDelayMs = overrides.initialDelay === void 0 ? base.initialDelayMs : toMilliseconds(overrides.initialDelay, "retry.initialDelay");
|
|
1324
|
+
return {
|
|
1325
|
+
attempts: overrides.attempts ?? base.attempts,
|
|
948
1326
|
initialDelayMs,
|
|
949
|
-
factor:
|
|
950
|
-
maxDelayMs:
|
|
1327
|
+
factor: overrides.factor ?? base.factor,
|
|
1328
|
+
maxDelayMs: overrides.maxDelay === void 0 ? (
|
|
951
1329
|
// Raising only the initial delay must not trip the inherited cap.
|
|
952
1330
|
Math.max(base.maxDelayMs, initialDelayMs)
|
|
953
|
-
) : toMilliseconds(
|
|
954
|
-
jitter:
|
|
955
|
-
retryIf:
|
|
1331
|
+
) : toMilliseconds(overrides.maxDelay, "retry.maxDelay"),
|
|
1332
|
+
jitter: overrides.jitter ?? base.jitter,
|
|
1333
|
+
retryIf: overrides.retryIf ?? base.retryIf
|
|
956
1334
|
};
|
|
1335
|
+
}
|
|
1336
|
+
function assertValidRetryPolicy(policy) {
|
|
957
1337
|
if (policy.attempts !== Number.POSITIVE_INFINITY && (!Number.isInteger(policy.attempts) || policy.attempts < 1)) {
|
|
958
1338
|
throw new ConfigurationError(
|
|
959
1339
|
`retry.attempts must be a positive integer or Infinity, received ${policy.attempts}`
|
|
@@ -970,7 +1350,6 @@ function createRetryPolicy(options, base = DEFAULT_RETRY_POLICY) {
|
|
|
970
1350
|
`retry.jitter must be one of ${JITTER_STRATEGIES.join(", ")}, received '${String(policy.jitter)}'`
|
|
971
1351
|
);
|
|
972
1352
|
}
|
|
973
|
-
return policy;
|
|
974
1353
|
}
|
|
975
1354
|
function computeRetryDelay(policy, failedAttempts, random = Math.random) {
|
|
976
1355
|
const exponential = policy.initialDelayMs * policy.factor ** (failedAttempts - 1);
|
|
@@ -999,11 +1378,17 @@ function isRetryableError(policy, error) {
|
|
|
999
1378
|
}
|
|
1000
1379
|
|
|
1001
1380
|
// src/dispatcher.ts
|
|
1002
|
-
var
|
|
1381
|
+
var FIRST_COMPLETION_RETRY_MS = 50;
|
|
1382
|
+
var SECOND_COMPLETION_RETRY_MS = 250;
|
|
1383
|
+
var LAST_COMPLETION_RETRY_MS = 1e3;
|
|
1384
|
+
var COMPLETION_RETRY_DELAYS_MS = [
|
|
1385
|
+
FIRST_COMPLETION_RETRY_MS,
|
|
1386
|
+
SECOND_COMPLETION_RETRY_MS,
|
|
1387
|
+
LAST_COMPLETION_RETRY_MS
|
|
1388
|
+
];
|
|
1003
1389
|
var WAKE_COOLDOWN_MS = 5;
|
|
1004
1390
|
var QUIET_REFILL_MS = 10;
|
|
1005
1391
|
var REFILL_FRACTION = 0.7;
|
|
1006
|
-
var defaultSleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
1007
1392
|
var Dispatcher = class {
|
|
1008
1393
|
#dependencies;
|
|
1009
1394
|
#sleep;
|
|
@@ -1029,9 +1414,15 @@ var Dispatcher = class {
|
|
|
1029
1414
|
#quietRefillTimer;
|
|
1030
1415
|
#unwatch;
|
|
1031
1416
|
#watching;
|
|
1417
|
+
/** Set when a stop gave up at its deadline: the store may be closed, so nothing more is started or retried. */
|
|
1418
|
+
#abandoned = false;
|
|
1419
|
+
/** Stops the lease renewal (or transactional lease guard) of each delivery whose handler runs. */
|
|
1420
|
+
#leaseKeepers = /* @__PURE__ */ new Map();
|
|
1421
|
+
#timers;
|
|
1032
1422
|
constructor(dependencies) {
|
|
1033
1423
|
this.#dependencies = dependencies;
|
|
1034
|
-
this.#
|
|
1424
|
+
this.#timers = new DeliveryTimers(dependencies.clock);
|
|
1425
|
+
this.#sleep = dependencies.sleep ?? ((milliseconds) => this.#timers.sleep(milliseconds));
|
|
1035
1426
|
const { store, clock, settings } = dependencies;
|
|
1036
1427
|
this.#refillThreshold = Math.ceil(
|
|
1037
1428
|
Math.min(settings.concurrency, settings.batchSize) * REFILL_FRACTION
|
|
@@ -1080,10 +1471,13 @@ var Dispatcher = class {
|
|
|
1080
1471
|
this.#requestClaim();
|
|
1081
1472
|
return;
|
|
1082
1473
|
}
|
|
1083
|
-
this.#cooldownTimer ??=
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1474
|
+
this.#cooldownTimer ??= schedule(this.#dependencies.clock, {
|
|
1475
|
+
run: () => {
|
|
1476
|
+
this.#cooldownTimer = void 0;
|
|
1477
|
+
this.#requestClaim();
|
|
1478
|
+
},
|
|
1479
|
+
delayMs: wait
|
|
1480
|
+
});
|
|
1087
1481
|
}
|
|
1088
1482
|
/** Ends the current wait of the claim loop, or the next one at once, for a claim once `minimumFree` slots are free. */
|
|
1089
1483
|
#signal(minimumFree) {
|
|
@@ -1122,33 +1516,66 @@ var Dispatcher = class {
|
|
|
1122
1516
|
}
|
|
1123
1517
|
}
|
|
1124
1518
|
/**
|
|
1125
|
-
* Stops claiming
|
|
1126
|
-
* then aborts the rest and releases their deliveries for another
|
|
1519
|
+
* Stops claiming and waits for running handlers until the drain point of `deadline` (default: the
|
|
1520
|
+
* shutdown timeout from now), then aborts the rest and releases their deliveries for another
|
|
1521
|
+
* process. Deliveries still in flight when the deadline itself passes are abandoned: their leases
|
|
1522
|
+
* expire and they run again elsewhere. Resolves with how many were abandoned.
|
|
1127
1523
|
*/
|
|
1128
|
-
async stop() {
|
|
1524
|
+
async stop(deadline) {
|
|
1525
|
+
const { clock, settings } = this.#dependencies;
|
|
1526
|
+
const own = deadline ?? new ShutdownDeadline(clock, settings.shutdownTimeoutMs);
|
|
1527
|
+
try {
|
|
1528
|
+
return await this.#stopWithin(own);
|
|
1529
|
+
} finally {
|
|
1530
|
+
if (deadline === void 0) {
|
|
1531
|
+
own.dispose();
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
async #stopWithin(deadline) {
|
|
1129
1536
|
if (!this.#running) {
|
|
1130
|
-
await this.#closeRecovery(
|
|
1131
|
-
return;
|
|
1537
|
+
await this.#closeRecovery(deadline);
|
|
1538
|
+
return 0;
|
|
1132
1539
|
}
|
|
1133
1540
|
this.#running = false;
|
|
1134
1541
|
this.#completions?.setClosing(true);
|
|
1135
|
-
|
|
1542
|
+
this.#cooldownTimer?.();
|
|
1136
1543
|
this.#cooldownTimer = void 0;
|
|
1137
|
-
|
|
1544
|
+
this.#quietRefillTimer?.();
|
|
1138
1545
|
this.#quietRefillTimer = void 0;
|
|
1139
1546
|
this.#signal(this.#refillThreshold);
|
|
1140
|
-
|
|
1141
|
-
await
|
|
1142
|
-
await this.#waitUntil(this.#loop, deadline);
|
|
1547
|
+
await deadline.race(this.#stopWatching());
|
|
1548
|
+
await deadline.race(this.#loop);
|
|
1143
1549
|
const settled = Promise.allSettled([...this.#inFlight].map((delivery) => delivery.settled));
|
|
1144
|
-
if (await
|
|
1550
|
+
if (await deadline.raceDrain(settled) === "timeout") {
|
|
1145
1551
|
for (const delivery of this.#inFlight) {
|
|
1146
1552
|
delivery.controller.abort(new DispatcherStoppedError("relaybus dispatcher stopped"));
|
|
1147
1553
|
}
|
|
1148
|
-
await settled
|
|
1554
|
+
if (await deadline.race(settled) === "timeout") {
|
|
1555
|
+
return this.#abandon();
|
|
1556
|
+
}
|
|
1149
1557
|
}
|
|
1150
1558
|
await this.#closeRecovery(deadline);
|
|
1151
|
-
await
|
|
1559
|
+
await deadline.race(this.#relinquish());
|
|
1560
|
+
return 0;
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* The deadline passed with deliveries still in flight (aborted, their release not answered): forgets
|
|
1564
|
+
* them and every settlement still to retry. Whatever they release or complete from now on may reach a
|
|
1565
|
+
* closed store; a delivery whose state change is lost stays leased until the lease expires and then
|
|
1566
|
+
* runs again (at-least-once).
|
|
1567
|
+
*/
|
|
1568
|
+
#abandon() {
|
|
1569
|
+
this.#abandoned = true;
|
|
1570
|
+
for (const stopKeeping of this.#leaseKeepers.values()) {
|
|
1571
|
+
stopKeeping();
|
|
1572
|
+
}
|
|
1573
|
+
this.#leaseKeepers.clear();
|
|
1574
|
+
this.#timers.cancelAll();
|
|
1575
|
+
this.#recoveryTimer?.();
|
|
1576
|
+
this.#recoveryTimer = void 0;
|
|
1577
|
+
this.#pending.clear();
|
|
1578
|
+
return this.#inFlight.size;
|
|
1152
1579
|
}
|
|
1153
1580
|
/**
|
|
1154
1581
|
* Gives the settlements still pending one last try and forgets them. A pass of the timer may be inside the
|
|
@@ -1156,13 +1583,13 @@ var Dispatcher = class {
|
|
|
1156
1583
|
* further call, since it may be closed right after.
|
|
1157
1584
|
*/
|
|
1158
1585
|
async #closeRecovery(deadline) {
|
|
1159
|
-
|
|
1586
|
+
this.#recoveryTimer?.();
|
|
1160
1587
|
this.#recoveryTimer = void 0;
|
|
1161
1588
|
const lastTry = (async () => {
|
|
1162
1589
|
await this.#recovery;
|
|
1163
1590
|
await this.#recoverSettlements();
|
|
1164
1591
|
})();
|
|
1165
|
-
await
|
|
1592
|
+
await deadline.race(lastTry);
|
|
1166
1593
|
this.#pending.clear();
|
|
1167
1594
|
}
|
|
1168
1595
|
/**
|
|
@@ -1193,12 +1620,16 @@ var Dispatcher = class {
|
|
|
1193
1620
|
}
|
|
1194
1621
|
}
|
|
1195
1622
|
#retain(entry) {
|
|
1623
|
+
if (this.#abandoned) {
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1196
1626
|
this.#pending.set(pendingKey(entry.delivery), entry);
|
|
1197
|
-
this.#recoveryTimer ??=
|
|
1198
|
-
() => void this.#recoverSettlements(),
|
|
1199
|
-
this.#dependencies.settings.pollIntervalMs
|
|
1200
|
-
|
|
1201
|
-
|
|
1627
|
+
this.#recoveryTimer ??= schedule(this.#dependencies.clock, {
|
|
1628
|
+
run: () => void this.#recoverSettlements(),
|
|
1629
|
+
delayMs: this.#dependencies.settings.pollIntervalMs,
|
|
1630
|
+
repeat: true,
|
|
1631
|
+
unref: true
|
|
1632
|
+
});
|
|
1202
1633
|
}
|
|
1203
1634
|
/** Gives lease groups the owner no longer works on back to the store, so other workers need not wait for expiry. */
|
|
1204
1635
|
async #relinquish() {
|
|
@@ -1216,12 +1647,15 @@ var Dispatcher = class {
|
|
|
1216
1647
|
return started.length;
|
|
1217
1648
|
}
|
|
1218
1649
|
async #waitUntil(work, deadline) {
|
|
1219
|
-
let
|
|
1650
|
+
let cancel;
|
|
1220
1651
|
const timedOut = new Promise((resolve) => {
|
|
1221
|
-
|
|
1652
|
+
cancel = schedule(this.#dependencies.clock, {
|
|
1653
|
+
run: () => resolve("timeout"),
|
|
1654
|
+
delayMs: Math.max(0, deadline - Date.now())
|
|
1655
|
+
});
|
|
1222
1656
|
});
|
|
1223
1657
|
const outcome = await Promise.race([work.then(() => "done"), timedOut]);
|
|
1224
|
-
|
|
1658
|
+
cancel?.();
|
|
1225
1659
|
return outcome;
|
|
1226
1660
|
}
|
|
1227
1661
|
/**
|
|
@@ -1255,12 +1689,15 @@ var Dispatcher = class {
|
|
|
1255
1689
|
async #waitForWakeUp() {
|
|
1256
1690
|
if (this.#signalledMinimum === void 0) {
|
|
1257
1691
|
await new Promise((resolve) => {
|
|
1258
|
-
const
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1692
|
+
const cancel = schedule(this.#dependencies.clock, {
|
|
1693
|
+
run: () => {
|
|
1694
|
+
this.#signalledMinimum = 1;
|
|
1695
|
+
resolve();
|
|
1696
|
+
},
|
|
1697
|
+
delayMs: this.#dependencies.settings.pollIntervalMs
|
|
1698
|
+
});
|
|
1262
1699
|
this.#wakeUp = () => {
|
|
1263
|
-
|
|
1700
|
+
cancel();
|
|
1264
1701
|
resolve();
|
|
1265
1702
|
};
|
|
1266
1703
|
});
|
|
@@ -1296,13 +1733,13 @@ var Dispatcher = class {
|
|
|
1296
1733
|
});
|
|
1297
1734
|
}
|
|
1298
1735
|
} else if (free > 0) {
|
|
1299
|
-
this.#quietRefillTimer ??=
|
|
1300
|
-
() => {
|
|
1736
|
+
this.#quietRefillTimer ??= schedule(this.#dependencies.clock, {
|
|
1737
|
+
run: () => {
|
|
1301
1738
|
this.#quietRefillTimer = void 0;
|
|
1302
1739
|
this.#signal(1);
|
|
1303
1740
|
},
|
|
1304
|
-
Math.max(0, this.#lastClaimStartedAt + QUIET_REFILL_MS - Date.now())
|
|
1305
|
-
);
|
|
1741
|
+
delayMs: Math.max(0, this.#lastClaimStartedAt + QUIET_REFILL_MS - Date.now())
|
|
1742
|
+
});
|
|
1306
1743
|
}
|
|
1307
1744
|
}
|
|
1308
1745
|
#freeSlots() {
|
|
@@ -1310,23 +1747,24 @@ var Dispatcher = class {
|
|
|
1310
1747
|
}
|
|
1311
1748
|
/** Claims for every handler with room and starts what was claimed. */
|
|
1312
1749
|
async #claimAndStart() {
|
|
1313
|
-
const { store, registry, clock, settings } = this.#dependencies;
|
|
1750
|
+
const { store, registry, clock, settings, observation } = this.#dependencies;
|
|
1314
1751
|
const started = [];
|
|
1315
1752
|
let requested = 0;
|
|
1316
1753
|
for (const request of this.#planClaims(registry.all())) {
|
|
1317
1754
|
const now = clock.now();
|
|
1318
1755
|
const wakeGeneration = this.#wakeGeneration;
|
|
1319
1756
|
this.#lastClaimStartedAt = Date.now();
|
|
1320
|
-
|
|
1757
|
+
this.#quietRefillTimer?.();
|
|
1321
1758
|
this.#quietRefillTimer = void 0;
|
|
1322
1759
|
requested += request.limit;
|
|
1323
|
-
const
|
|
1760
|
+
const claim = {
|
|
1324
1761
|
handlers: request.handlers,
|
|
1325
1762
|
limit: request.limit,
|
|
1326
1763
|
leaseOwner: settings.owner,
|
|
1327
1764
|
leaseExpiresAt: new Date(now.getTime() + settings.leaseDurationMs),
|
|
1328
1765
|
now
|
|
1329
|
-
}
|
|
1766
|
+
};
|
|
1767
|
+
const claimed = await (observation === void 0 ? store.claim(claim) : observation.claim(store, claim));
|
|
1330
1768
|
for (const handler of request.handlers) {
|
|
1331
1769
|
if (claimed.length < request.limit && wakeGeneration === this.#wakeGeneration) {
|
|
1332
1770
|
this.#exhaustedHandlers.add(handler);
|
|
@@ -1334,7 +1772,7 @@ var Dispatcher = class {
|
|
|
1334
1772
|
this.#exhaustedHandlers.delete(handler);
|
|
1335
1773
|
}
|
|
1336
1774
|
}
|
|
1337
|
-
for (const delivery of claimed) {
|
|
1775
|
+
for (const delivery of this.#abandoned ? [] : claimed) {
|
|
1338
1776
|
started.push(this.#start(delivery));
|
|
1339
1777
|
}
|
|
1340
1778
|
}
|
|
@@ -1349,22 +1787,25 @@ var Dispatcher = class {
|
|
|
1349
1787
|
if (registration.concurrency === null || free === 0) {
|
|
1350
1788
|
continue;
|
|
1351
1789
|
}
|
|
1352
|
-
const room = registration.concurrency - this.#countInFlight(registration
|
|
1790
|
+
const room = registration.concurrency - this.#countInFlight(registration);
|
|
1353
1791
|
const limit = Math.min(room, free);
|
|
1354
1792
|
if (limit > 0) {
|
|
1355
|
-
plans.push({ handlers: [registration.
|
|
1793
|
+
plans.push({ handlers: [...registration.claimIds], limit });
|
|
1356
1794
|
free -= limit;
|
|
1357
1795
|
}
|
|
1358
1796
|
}
|
|
1359
1797
|
if (unlimited.length > 0 && free > 0) {
|
|
1360
|
-
plans.push({
|
|
1798
|
+
plans.push({
|
|
1799
|
+
handlers: unlimited.flatMap((registration) => registration.claimIds),
|
|
1800
|
+
limit: free
|
|
1801
|
+
});
|
|
1361
1802
|
}
|
|
1362
1803
|
return plans;
|
|
1363
1804
|
}
|
|
1364
|
-
#countInFlight(
|
|
1805
|
+
#countInFlight(registration) {
|
|
1365
1806
|
let count = 0;
|
|
1366
1807
|
for (const delivery of this.#inFlight) {
|
|
1367
|
-
if (delivery.handler
|
|
1808
|
+
if (registration.claimIds.includes(delivery.handler)) {
|
|
1368
1809
|
count += 1;
|
|
1369
1810
|
}
|
|
1370
1811
|
}
|
|
@@ -1400,7 +1841,7 @@ var Dispatcher = class {
|
|
|
1400
1841
|
if (delivery.attempt > registration.retry.attempts) {
|
|
1401
1842
|
await this.#bury(
|
|
1402
1843
|
delivery,
|
|
1403
|
-
new
|
|
1844
|
+
new AttemptsExhaustedError(
|
|
1404
1845
|
`Delivery reached attempt ${delivery.attempt} of ${registration.retry.attempts} after its lease expired`
|
|
1405
1846
|
)
|
|
1406
1847
|
);
|
|
@@ -1408,8 +1849,9 @@ var Dispatcher = class {
|
|
|
1408
1849
|
}
|
|
1409
1850
|
let stopHeartbeat = () => void 0;
|
|
1410
1851
|
try {
|
|
1411
|
-
stopHeartbeat = registration.transactional ? await this.#guardTransactionalLease(registration, delivery, controller) : this.#
|
|
1412
|
-
|
|
1852
|
+
stopHeartbeat = registration.transactional ? await this.#guardTransactionalLease(registration, { delivery, controller }) : startLeaseHeartbeat(this.#dependencies, { delivery, controller });
|
|
1853
|
+
this.#leaseKeepers.set(delivery, stopHeartbeat);
|
|
1854
|
+
const outcome = await this.#runHandler(registration, { delivery, controller });
|
|
1413
1855
|
const durationMs = clock.now().getTime() - startedAt;
|
|
1414
1856
|
if (outcome === "completed") {
|
|
1415
1857
|
notifier.notify("onDeliverySucceeded", { delivery, durationMs });
|
|
@@ -1422,22 +1864,29 @@ var Dispatcher = class {
|
|
|
1422
1864
|
}
|
|
1423
1865
|
return true;
|
|
1424
1866
|
} catch (error) {
|
|
1867
|
+
if (this.#abandoned) {
|
|
1868
|
+
return false;
|
|
1869
|
+
}
|
|
1425
1870
|
if (error instanceof LeaseLostError) {
|
|
1426
1871
|
notifier.notify("onLeaseLost", { delivery });
|
|
1427
1872
|
if (!error.storeConfirmed) {
|
|
1428
|
-
await this.#giveBack(registration, delivery, error);
|
|
1873
|
+
await this.#giveBack(registration, { delivery, error });
|
|
1429
1874
|
}
|
|
1430
1875
|
} else if (error instanceof DispatcherStoppedError) {
|
|
1431
1876
|
await this.#release(delivery);
|
|
1432
1877
|
} else {
|
|
1433
|
-
await this.#fail(registration, delivery, error);
|
|
1878
|
+
await this.#fail(registration, { delivery, error });
|
|
1434
1879
|
}
|
|
1435
1880
|
return false;
|
|
1436
1881
|
} finally {
|
|
1437
1882
|
stopHeartbeat();
|
|
1883
|
+
this.#leaseKeepers.delete(delivery);
|
|
1438
1884
|
}
|
|
1439
1885
|
}
|
|
1440
|
-
async #runHandler(registration,
|
|
1886
|
+
async #runHandler(registration, {
|
|
1887
|
+
delivery,
|
|
1888
|
+
controller
|
|
1889
|
+
}) {
|
|
1441
1890
|
const { store, clock } = this.#dependencies;
|
|
1442
1891
|
const payload = await validatePayload(
|
|
1443
1892
|
delivery.event.type,
|
|
@@ -1447,7 +1896,7 @@ var Dispatcher = class {
|
|
|
1447
1896
|
const event = { ...delivery.event, payload };
|
|
1448
1897
|
if (registration.transactional) {
|
|
1449
1898
|
await store.transaction(async (transaction) => {
|
|
1450
|
-
await this.#invoke(registration, event, delivery, controller, transaction);
|
|
1899
|
+
await this.#invoke({ registration, event, delivery, controller, transaction });
|
|
1451
1900
|
if (!await store.complete(delivery, clock.now(), transaction)) {
|
|
1452
1901
|
throw new LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost`, {
|
|
1453
1902
|
storeConfirmed: true
|
|
@@ -1456,7 +1905,7 @@ var Dispatcher = class {
|
|
|
1456
1905
|
});
|
|
1457
1906
|
return "completed";
|
|
1458
1907
|
}
|
|
1459
|
-
await this.#invoke(registration, event, delivery, controller, void 0);
|
|
1908
|
+
await this.#invoke({ registration, event, delivery, controller, transaction: void 0 });
|
|
1460
1909
|
return this.#confirmCompletion(delivery);
|
|
1461
1910
|
}
|
|
1462
1911
|
/**
|
|
@@ -1466,6 +1915,9 @@ var Dispatcher = class {
|
|
|
1466
1915
|
async #confirmCompletion(delivery) {
|
|
1467
1916
|
const { notifier } = this.#dependencies;
|
|
1468
1917
|
for (let attempt = 0; ; attempt += 1) {
|
|
1918
|
+
if (this.#abandoned) {
|
|
1919
|
+
return "unconfirmed";
|
|
1920
|
+
}
|
|
1469
1921
|
let completed;
|
|
1470
1922
|
try {
|
|
1471
1923
|
completed = await this.#complete(delivery);
|
|
@@ -1491,7 +1943,8 @@ var Dispatcher = class {
|
|
|
1491
1943
|
const { store, clock } = this.#dependencies;
|
|
1492
1944
|
return this.#completions === void 0 ? store.complete(delivery, clock.now()) : this.#completions.submit(delivery);
|
|
1493
1945
|
}
|
|
1494
|
-
async #invoke(
|
|
1946
|
+
async #invoke(input) {
|
|
1947
|
+
const { registration, event, delivery, controller, transaction } = input;
|
|
1495
1948
|
const context = {
|
|
1496
1949
|
deliveryId: delivery.deliveryId,
|
|
1497
1950
|
handler: delivery.handler,
|
|
@@ -1504,28 +1957,37 @@ var Dispatcher = class {
|
|
|
1504
1957
|
if (controller.signal.aborted) {
|
|
1505
1958
|
throw controller.signal.reason;
|
|
1506
1959
|
}
|
|
1960
|
+
const handle = () => runInIsolatedScope(scope, () => registration.handle(event, context));
|
|
1961
|
+
const { observation } = this.#dependencies;
|
|
1507
1962
|
const work = Promise.resolve().then(
|
|
1508
|
-
|
|
1963
|
+
observation === void 0 ? handle : () => observation.process({ registration, delivery }, handle)
|
|
1509
1964
|
);
|
|
1510
1965
|
work.catch(() => void 0);
|
|
1511
|
-
let
|
|
1966
|
+
let cancelTimeout;
|
|
1512
1967
|
if (registration.timeoutMs !== null) {
|
|
1513
1968
|
const timeoutMs = registration.timeoutMs;
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1969
|
+
cancelTimeout = this.#timers.start({
|
|
1970
|
+
run: () => {
|
|
1971
|
+
controller.abort(
|
|
1972
|
+
new HandlerTimeoutError(
|
|
1973
|
+
`Handler '${registration.id}' did not settle within ${timeoutMs}ms`
|
|
1974
|
+
)
|
|
1975
|
+
);
|
|
1976
|
+
},
|
|
1977
|
+
delayMs: timeoutMs
|
|
1978
|
+
});
|
|
1521
1979
|
}
|
|
1522
1980
|
try {
|
|
1523
|
-
|
|
1981
|
+
const settled = Promise.race([work, rejectOnAbort(controller.signal)]);
|
|
1982
|
+
await (observation === void 0 ? settled : observation.observe({ registration, delivery }, settled));
|
|
1524
1983
|
} catch (error) {
|
|
1525
|
-
await this.#awaitAbandonedHandler(work,
|
|
1984
|
+
await this.#awaitAbandonedHandler(work, {
|
|
1985
|
+
reason: error,
|
|
1986
|
+
transactional: transaction !== void 0
|
|
1987
|
+
});
|
|
1526
1988
|
throw error;
|
|
1527
1989
|
} finally {
|
|
1528
|
-
|
|
1990
|
+
cancelTimeout?.();
|
|
1529
1991
|
}
|
|
1530
1992
|
}
|
|
1531
1993
|
/**
|
|
@@ -1535,7 +1997,7 @@ var Dispatcher = class {
|
|
|
1535
1997
|
* Transactional handlers are always awaited; others for one lease duration,
|
|
1536
1998
|
* except at shutdown, which already waited.
|
|
1537
1999
|
*/
|
|
1538
|
-
async #awaitAbandonedHandler(work, reason, transactional) {
|
|
2000
|
+
async #awaitAbandonedHandler(work, { reason, transactional }) {
|
|
1539
2001
|
const settledWork = work.then(
|
|
1540
2002
|
() => void 0,
|
|
1541
2003
|
() => void 0
|
|
@@ -1557,7 +2019,10 @@ var Dispatcher = class {
|
|
|
1557
2019
|
* lease is extended once to cover the handler timeout, and the handler is
|
|
1558
2020
|
* aborted when that lease ends.
|
|
1559
2021
|
*/
|
|
1560
|
-
async #guardTransactionalLease(registration,
|
|
2022
|
+
async #guardTransactionalLease(registration, {
|
|
2023
|
+
delivery,
|
|
2024
|
+
controller
|
|
2025
|
+
}) {
|
|
1561
2026
|
const { store, clock, settings } = this.#dependencies;
|
|
1562
2027
|
const now = clock.now();
|
|
1563
2028
|
let expiry = delivery.leaseExpiresAt.getTime();
|
|
@@ -1569,66 +2034,24 @@ var Dispatcher = class {
|
|
|
1569
2034
|
});
|
|
1570
2035
|
}
|
|
1571
2036
|
}
|
|
1572
|
-
|
|
1573
|
-
() => {
|
|
2037
|
+
return schedule(clock, {
|
|
2038
|
+
run: () => {
|
|
1574
2039
|
controller.abort(new LeaseLostError(`Lease on delivery ${delivery.deliveryId} expired`));
|
|
1575
2040
|
},
|
|
1576
|
-
Math.max(0, expiry - clock.now().getTime())
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
return () => clearTimeout(timer);
|
|
2041
|
+
delayMs: Math.max(0, expiry - clock.now().getTime()),
|
|
2042
|
+
unref: true
|
|
2043
|
+
});
|
|
1580
2044
|
}
|
|
1581
|
-
|
|
1582
|
-
* Renews the lease every third of its duration. A worker that cannot renew
|
|
1583
|
-
* aborts its handler once the last confirmed expiry passes, because another
|
|
1584
|
-
* worker may then claim the delivery (self-fencing).
|
|
1585
|
-
*/
|
|
1586
|
-
#startHeartbeat(delivery, controller) {
|
|
1587
|
-
const { store, clock, notifier, settings } = this.#dependencies;
|
|
1588
|
-
let confirmedExpiry = delivery.leaseExpiresAt.getTime();
|
|
1589
|
-
let renewing = false;
|
|
1590
|
-
const abortLeaseLost = (storeConfirmed = false) => {
|
|
1591
|
-
controller.abort(
|
|
1592
|
-
new LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost`, { storeConfirmed })
|
|
1593
|
-
);
|
|
1594
|
-
};
|
|
1595
|
-
const timer = setInterval(
|
|
1596
|
-
() => {
|
|
1597
|
-
const now = clock.now();
|
|
1598
|
-
if (now.getTime() >= confirmedExpiry) {
|
|
1599
|
-
abortLeaseLost();
|
|
1600
|
-
return;
|
|
1601
|
-
}
|
|
1602
|
-
if (renewing) {
|
|
1603
|
-
return;
|
|
1604
|
-
}
|
|
1605
|
-
renewing = true;
|
|
1606
|
-
const expiry = now.getTime() + settings.leaseDurationMs;
|
|
1607
|
-
store.extendLease(delivery, new Date(expiry), now).then((extended) => {
|
|
1608
|
-
if (extended) {
|
|
1609
|
-
confirmedExpiry = expiry;
|
|
1610
|
-
} else {
|
|
1611
|
-
abortLeaseLost(true);
|
|
1612
|
-
}
|
|
1613
|
-
}).catch((error) => notifier.failure("extend-lease", error)).finally(() => {
|
|
1614
|
-
renewing = false;
|
|
1615
|
-
});
|
|
1616
|
-
},
|
|
1617
|
-
Math.max(1, Math.floor(settings.leaseDurationMs / 3))
|
|
1618
|
-
);
|
|
1619
|
-
timer.unref();
|
|
1620
|
-
return () => clearInterval(timer);
|
|
1621
|
-
}
|
|
1622
|
-
async #fail(registration, delivery, error) {
|
|
2045
|
+
async #fail(registration, { delivery, error }) {
|
|
1623
2046
|
const policy = registration.retry;
|
|
1624
2047
|
if (delivery.attempt >= policy.attempts || !isRetryableError(policy, error)) {
|
|
1625
2048
|
await this.#bury(delivery, error);
|
|
1626
2049
|
return;
|
|
1627
2050
|
}
|
|
1628
|
-
await this.#reschedule(registration, delivery, error);
|
|
2051
|
+
await this.#reschedule(registration, { delivery, error });
|
|
1629
2052
|
}
|
|
1630
2053
|
/** Schedules the next attempt with the retry policy's backoff. */
|
|
1631
|
-
async #reschedule(registration, delivery, error) {
|
|
2054
|
+
async #reschedule(registration, { delivery, error }) {
|
|
1632
2055
|
const { store, clock, random, notifier } = this.#dependencies;
|
|
1633
2056
|
const policy = registration.retry;
|
|
1634
2057
|
const errorInfo = toErrorInfo(error);
|
|
@@ -1683,11 +2106,11 @@ var Dispatcher = class {
|
|
|
1683
2106
|
* outlives its lease cannot run for ever at attempt 1. If the lease is gone for real the store
|
|
1684
2107
|
* refuses and nothing changes. The lost lease was reported already.
|
|
1685
2108
|
*/
|
|
1686
|
-
async #giveBack(registration, delivery, error) {
|
|
2109
|
+
async #giveBack(registration, { delivery, error }) {
|
|
1687
2110
|
if (delivery.attempt >= registration.retry.attempts) {
|
|
1688
2111
|
await this.#bury(delivery, error);
|
|
1689
2112
|
} else {
|
|
1690
|
-
await this.#reschedule(registration, delivery, error);
|
|
2113
|
+
await this.#reschedule(registration, { delivery, error });
|
|
1691
2114
|
}
|
|
1692
2115
|
}
|
|
1693
2116
|
async #release(delivery) {
|
|
@@ -1711,19 +2134,12 @@ var Dispatcher = class {
|
|
|
1711
2134
|
}
|
|
1712
2135
|
}
|
|
1713
2136
|
};
|
|
1714
|
-
function pendingKey(delivery) {
|
|
1715
|
-
return `${delivery.deliveryId}\0${delivery.leaseToken}`;
|
|
1716
|
-
}
|
|
1717
|
-
function rejectOnAbort(signal) {
|
|
1718
|
-
return new Promise((_, reject) => {
|
|
1719
|
-
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
1720
|
-
});
|
|
1721
|
-
}
|
|
1722
2137
|
|
|
1723
2138
|
// src/handler-registry.ts
|
|
1724
2139
|
var HANDLER_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$/;
|
|
1725
2140
|
var HandlerRegistry = class {
|
|
1726
2141
|
#byId = /* @__PURE__ */ new Map();
|
|
2142
|
+
#byClaimId = /* @__PURE__ */ new Map();
|
|
1727
2143
|
#byEventType = /* @__PURE__ */ new Map();
|
|
1728
2144
|
register(input, defaults) {
|
|
1729
2145
|
const { options } = input;
|
|
@@ -1732,33 +2148,50 @@ var HandlerRegistry = class {
|
|
|
1732
2148
|
`Handler id '${options.id}' must be 1-255 characters of letters, digits, '.', '_', ':', '/' or '-'`
|
|
1733
2149
|
);
|
|
1734
2150
|
}
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
throw new ConfigurationError(
|
|
1740
|
-
`Concurrency of handler '${options.id}' must be a positive integer, received ${options.concurrency}`
|
|
1741
|
-
);
|
|
2151
|
+
const claimIds = [options.id, ...input.legacyIds ?? []];
|
|
2152
|
+
this.#assertUnregistered(claimIds);
|
|
2153
|
+
if (options.concurrency !== void 0) {
|
|
2154
|
+
assertPositiveInteger(options.concurrency, `Concurrency of handler '${options.id}'`);
|
|
1742
2155
|
}
|
|
1743
2156
|
const registration = {
|
|
1744
2157
|
id: options.id,
|
|
1745
|
-
|
|
2158
|
+
eventTypes: input.eventTypes,
|
|
2159
|
+
claimIds,
|
|
1746
2160
|
schema: input.schema,
|
|
1747
2161
|
handle: input.handle,
|
|
1748
2162
|
retry: createRetryPolicy(options.retry, defaults.retry),
|
|
1749
|
-
timeoutMs: resolveTimeout(options.timeout,
|
|
2163
|
+
timeoutMs: resolveTimeout(options.timeout, {
|
|
2164
|
+
fallback: defaults.timeoutMs,
|
|
2165
|
+
handlerId: options.id
|
|
2166
|
+
}),
|
|
1750
2167
|
concurrency: options.concurrency ?? null,
|
|
1751
2168
|
transactional: options.transactional ?? false,
|
|
1752
2169
|
forwardsToTransport: input.forwardsToTransport
|
|
1753
2170
|
};
|
|
1754
|
-
this.#
|
|
1755
|
-
const forType = this.#byEventType.get(registration.eventType) ?? [];
|
|
1756
|
-
forType.push(registration);
|
|
1757
|
-
this.#byEventType.set(registration.eventType, forType);
|
|
2171
|
+
this.#index(registration);
|
|
1758
2172
|
return registration;
|
|
1759
2173
|
}
|
|
2174
|
+
#assertUnregistered(claimIds) {
|
|
2175
|
+
for (const id of claimIds) {
|
|
2176
|
+
if (this.#byClaimId.has(id)) {
|
|
2177
|
+
throw new ConfigurationError(`Handler id '${id}' is already registered`);
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
#index(registration) {
|
|
2182
|
+
this.#byId.set(registration.id, registration);
|
|
2183
|
+
for (const id of registration.claimIds) {
|
|
2184
|
+
this.#byClaimId.set(id, registration);
|
|
2185
|
+
}
|
|
2186
|
+
for (const eventType of registration.eventTypes) {
|
|
2187
|
+
const forType = this.#byEventType.get(eventType) ?? [];
|
|
2188
|
+
forType.push(registration);
|
|
2189
|
+
this.#byEventType.set(eventType, forType);
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
/** The registration that runs deliveries stored under `id`, its own id or a legacy one. */
|
|
1760
2193
|
get(id) {
|
|
1761
|
-
return this.#
|
|
2194
|
+
return this.#byClaimId.get(id);
|
|
1762
2195
|
}
|
|
1763
2196
|
forEventType(eventType) {
|
|
1764
2197
|
return this.#byEventType.get(eventType) ?? [];
|
|
@@ -1767,7 +2200,7 @@ var HandlerRegistry = class {
|
|
|
1767
2200
|
return [...this.#byId.values()];
|
|
1768
2201
|
}
|
|
1769
2202
|
};
|
|
1770
|
-
function resolveTimeout(timeout, fallback, handlerId) {
|
|
2203
|
+
function resolveTimeout(timeout, { fallback, handlerId }) {
|
|
1771
2204
|
if (timeout === void 0) {
|
|
1772
2205
|
return fallback;
|
|
1773
2206
|
}
|
|
@@ -1781,9 +2214,18 @@ function resolveTimeout(timeout, fallback, handlerId) {
|
|
|
1781
2214
|
var Notifier = class {
|
|
1782
2215
|
#observers;
|
|
1783
2216
|
#logger;
|
|
2217
|
+
/** Hooks at least one observer implements, so callers skip building events nobody reads. */
|
|
2218
|
+
#implemented;
|
|
1784
2219
|
constructor(observers, logger) {
|
|
1785
2220
|
this.#observers = observers;
|
|
1786
2221
|
this.#logger = logger;
|
|
2222
|
+
this.#implemented = new Set(
|
|
2223
|
+
observers.flatMap((observer) => hookNames(observer))
|
|
2224
|
+
);
|
|
2225
|
+
}
|
|
2226
|
+
/** True when some observer implements `method`. */
|
|
2227
|
+
wants(method) {
|
|
2228
|
+
return this.#implemented.has(method);
|
|
1787
2229
|
}
|
|
1788
2230
|
notify(method, argument) {
|
|
1789
2231
|
for (const observer of this.#observers) {
|
|
@@ -1827,12 +2269,58 @@ var Notifier = class {
|
|
|
1827
2269
|
#logObserverFailure(error) {
|
|
1828
2270
|
const errorInfo = toErrorInfo(error);
|
|
1829
2271
|
this.#logger.warn(
|
|
1830
|
-
`[Notifier](
|
|
2272
|
+
`[Notifier](logObserverFailure) relaybus observer threw | ${errorInfo.name}: ${errorInfo.message}`
|
|
1831
2273
|
);
|
|
1832
2274
|
}
|
|
1833
2275
|
};
|
|
2276
|
+
function hookNames(observer) {
|
|
2277
|
+
const names = [];
|
|
2278
|
+
for (let target = observer; target !== null && target !== Object.prototype; target = Object.getPrototypeOf(target)) {
|
|
2279
|
+
names.push(
|
|
2280
|
+
...Object.getOwnPropertyNames(target).filter(
|
|
2281
|
+
(name) => typeof observer[name] === "function"
|
|
2282
|
+
)
|
|
2283
|
+
);
|
|
2284
|
+
}
|
|
2285
|
+
return names;
|
|
2286
|
+
}
|
|
1834
2287
|
|
|
1835
|
-
// src/
|
|
2288
|
+
// src/received-envelope.ts
|
|
2289
|
+
function normalizeEnvelope(envelope) {
|
|
2290
|
+
if (typeof envelope.id !== "string" || envelope.id.length === 0) {
|
|
2291
|
+
throw new InvalidEnvelopeError("Received event has no id");
|
|
2292
|
+
}
|
|
2293
|
+
if (typeof envelope.type !== "string") {
|
|
2294
|
+
throw new InvalidEnvelopeError(`Received event ${envelope.id} has no type`);
|
|
2295
|
+
}
|
|
2296
|
+
try {
|
|
2297
|
+
assertEventType(envelope.type);
|
|
2298
|
+
} catch (error) {
|
|
2299
|
+
throw new InvalidEnvelopeError(toErrorInfo(error).message);
|
|
2300
|
+
}
|
|
2301
|
+
const occurredAt = toDate(envelope.occurredAt);
|
|
2302
|
+
if (occurredAt === void 0) {
|
|
2303
|
+
throw new InvalidEnvelopeError(`Received event ${envelope.id} has an invalid occurredAt`);
|
|
2304
|
+
}
|
|
2305
|
+
return {
|
|
2306
|
+
id: envelope.id,
|
|
2307
|
+
type: envelope.type,
|
|
2308
|
+
source: typeof envelope.source === "string" ? envelope.source : "unknown",
|
|
2309
|
+
key: optionalText(envelope.key),
|
|
2310
|
+
payload: envelope.payload,
|
|
2311
|
+
headers: { ...envelope.headers },
|
|
2312
|
+
correlationId: optionalText(envelope.correlationId),
|
|
2313
|
+
causationId: optionalText(envelope.causationId),
|
|
2314
|
+
occurredAt
|
|
2315
|
+
};
|
|
2316
|
+
}
|
|
2317
|
+
function toDate(value) {
|
|
2318
|
+
const date = typeof value === "string" ? new Date(value) : value;
|
|
2319
|
+
return date instanceof Date && !Number.isNaN(date.getTime()) ? date : void 0;
|
|
2320
|
+
}
|
|
2321
|
+
function optionalText(value) {
|
|
2322
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
2323
|
+
}
|
|
1836
2324
|
var DEFAULTS = {
|
|
1837
2325
|
concurrency: 10,
|
|
1838
2326
|
pollInterval: "1s",
|
|
@@ -1842,14 +2330,195 @@ var DEFAULTS = {
|
|
|
1842
2330
|
completionBatchSize: 200,
|
|
1843
2331
|
completedRetention: "7d",
|
|
1844
2332
|
retentionInterval: "1h",
|
|
1845
|
-
routeTimeoutMs: 3e4
|
|
2333
|
+
routeTimeoutMs: 3e4,
|
|
2334
|
+
transactionTimeout: "5s"
|
|
1846
2335
|
};
|
|
2336
|
+
function oneOf(value, { allowed, name }) {
|
|
2337
|
+
if (!allowed.includes(value)) {
|
|
2338
|
+
throw new ConfigurationError(
|
|
2339
|
+
`${name} must be one of ${allowed.map((option) => `'${option}'`).join(", ")}, received '${String(value)}'`
|
|
2340
|
+
);
|
|
2341
|
+
}
|
|
2342
|
+
return value;
|
|
2343
|
+
}
|
|
2344
|
+
function isDispatcherEnabled(options) {
|
|
2345
|
+
return options.dispatcher?.enabled ?? true;
|
|
2346
|
+
}
|
|
2347
|
+
function resolveBacklogInterval(options) {
|
|
2348
|
+
return options.backlogInterval === void 0 ? void 0 : toTimerMilliseconds(options.backlogInterval, "backlogInterval");
|
|
2349
|
+
}
|
|
2350
|
+
function resolveTransactionTimeout(options) {
|
|
2351
|
+
return toTimerMilliseconds(
|
|
2352
|
+
options.transactionTimeout ?? DEFAULTS.transactionTimeout,
|
|
2353
|
+
"transactionTimeout"
|
|
2354
|
+
);
|
|
2355
|
+
}
|
|
2356
|
+
function createHandlerDefaults(options) {
|
|
2357
|
+
return {
|
|
2358
|
+
retry: createRetryPolicy(options.retry),
|
|
2359
|
+
timeoutMs: options.handlerTimeout === void 0 || options.handlerTimeout === false ? null : toTimerMilliseconds(options.handlerTimeout, "handlerTimeout")
|
|
2360
|
+
};
|
|
2361
|
+
}
|
|
2362
|
+
function createDispatcherSettings(options, generateIdentifier) {
|
|
2363
|
+
const dispatcher = options ?? {};
|
|
2364
|
+
const concurrency = assertPositiveInteger(
|
|
2365
|
+
dispatcher.concurrency ?? DEFAULTS.concurrency,
|
|
2366
|
+
"dispatcher.concurrency"
|
|
2367
|
+
);
|
|
2368
|
+
return {
|
|
2369
|
+
owner: `${hostname()}:${process.pid}:${generateIdentifier()}`,
|
|
2370
|
+
concurrency,
|
|
2371
|
+
batchSize: assertPositiveInteger(dispatcher.batchSize ?? concurrency, "dispatcher.batchSize"),
|
|
2372
|
+
...createDispatcherTimings(dispatcher)
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
function createDispatcherTimings(dispatcher) {
|
|
2376
|
+
return {
|
|
2377
|
+
pollIntervalMs: toTimerMilliseconds(
|
|
2378
|
+
dispatcher.pollInterval ?? DEFAULTS.pollInterval,
|
|
2379
|
+
"dispatcher.pollInterval"
|
|
2380
|
+
),
|
|
2381
|
+
leaseDurationMs: toTimerMilliseconds(
|
|
2382
|
+
dispatcher.leaseDuration ?? DEFAULTS.leaseDuration,
|
|
2383
|
+
"dispatcher.leaseDuration"
|
|
2384
|
+
),
|
|
2385
|
+
shutdownTimeoutMs: toTimerMilliseconds(
|
|
2386
|
+
dispatcher.shutdownTimeout ?? DEFAULTS.shutdownTimeout,
|
|
2387
|
+
"dispatcher.shutdownTimeout"
|
|
2388
|
+
),
|
|
2389
|
+
completionBatchWindowMs: toMilliseconds(
|
|
2390
|
+
dispatcher.completionBatchWindow ?? DEFAULTS.completionBatchWindow,
|
|
2391
|
+
"dispatcher.completionBatchWindow"
|
|
2392
|
+
),
|
|
2393
|
+
completionBatchSize: assertPositiveInteger(
|
|
2394
|
+
dispatcher.completionBatchSize ?? DEFAULTS.completionBatchSize,
|
|
2395
|
+
"dispatcher.completionBatchSize"
|
|
2396
|
+
)
|
|
2397
|
+
};
|
|
2398
|
+
}
|
|
2399
|
+
function createRetentionSettings(options) {
|
|
2400
|
+
const completed = options?.completed ?? DEFAULTS.completedRetention;
|
|
2401
|
+
const dead = options?.dead ?? false;
|
|
2402
|
+
return {
|
|
2403
|
+
completedMs: completed === false ? null : toMilliseconds(completed, "retention.completed"),
|
|
2404
|
+
deadMs: dead === false ? null : toMilliseconds(dead, "retention.dead"),
|
|
2405
|
+
intervalMs: toTimerMilliseconds(
|
|
2406
|
+
options?.interval ?? DEFAULTS.retentionInterval,
|
|
2407
|
+
"retention.interval"
|
|
2408
|
+
)
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
// src/route-groups.ts
|
|
1847
2413
|
var TRANSPORT_HANDLER_PREFIX = "relaybus.transport";
|
|
2414
|
+
function groupRoutes(routes) {
|
|
2415
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2416
|
+
for (const route of routes) {
|
|
2417
|
+
const type = routedType(route.event);
|
|
2418
|
+
const destination = route.destination ?? type;
|
|
2419
|
+
const id = `${TRANSPORT_HANDLER_PREFIX}:${route.transport}:${destination}`;
|
|
2420
|
+
const group = groups.get(id);
|
|
2421
|
+
if (group === void 0) {
|
|
2422
|
+
groups.set(id, createGroup(route, { id, destination, type }));
|
|
2423
|
+
} else {
|
|
2424
|
+
addRoute(group, { route, type, destination });
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
return [...groups.values()];
|
|
2428
|
+
}
|
|
2429
|
+
function createGroup(route, {
|
|
2430
|
+
id,
|
|
2431
|
+
destination,
|
|
2432
|
+
type
|
|
2433
|
+
}) {
|
|
2434
|
+
return {
|
|
2435
|
+
transport: route.transport,
|
|
2436
|
+
destination,
|
|
2437
|
+
id,
|
|
2438
|
+
eventTypes: [type],
|
|
2439
|
+
legacyIds: [`${id}:${type}`],
|
|
2440
|
+
route,
|
|
2441
|
+
...resolveSettings(route, id)
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2444
|
+
function addRoute(group, {
|
|
2445
|
+
route,
|
|
2446
|
+
type,
|
|
2447
|
+
destination
|
|
2448
|
+
}) {
|
|
2449
|
+
const target = `'${group.destination}' of transport '${group.transport}'`;
|
|
2450
|
+
if (route.transport !== group.transport) {
|
|
2451
|
+
throw new ConfigurationError(
|
|
2452
|
+
`Routes to ${target} and to '${destination}' of transport '${route.transport}' would share the handler id '${group.id}'; rename the transport or the destination`
|
|
2453
|
+
);
|
|
2454
|
+
}
|
|
2455
|
+
if (group.eventTypes.includes(type)) {
|
|
2456
|
+
throw new ConfigurationError(`Event type ${type} is routed to ${target} twice`);
|
|
2457
|
+
}
|
|
2458
|
+
const { retry, timeoutMs } = resolveSettings(route, group.id);
|
|
2459
|
+
if (timeoutMs !== group.timeoutMs || !sameRetryPolicy(retry, group.retry)) {
|
|
2460
|
+
throw new ConfigurationError(
|
|
2461
|
+
`Routes to ${target} must share retry and timeout, because their events are sent in one queue per key | types=${group.eventTypes[0]},${type}`
|
|
2462
|
+
);
|
|
2463
|
+
}
|
|
2464
|
+
group.eventTypes.push(type);
|
|
2465
|
+
group.legacyIds.push(`${group.id}:${type}`);
|
|
2466
|
+
}
|
|
2467
|
+
function resolveSettings(route, id) {
|
|
2468
|
+
return {
|
|
2469
|
+
retry: createRetryPolicy(route.retry, DEFAULT_ROUTE_RETRY_POLICY),
|
|
2470
|
+
timeoutMs: route.timeout === void 0 ? DEFAULTS.routeTimeoutMs : toTimerMilliseconds(route.timeout, `Timeout of handler '${id}'`)
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
2473
|
+
function sameRetryPolicy(left, right) {
|
|
2474
|
+
return Object.keys(left).every(
|
|
2475
|
+
(field) => left[field] === right[field]
|
|
2476
|
+
);
|
|
2477
|
+
}
|
|
2478
|
+
function routedType(event) {
|
|
2479
|
+
const type = typeof event === "string" ? event : event.type;
|
|
2480
|
+
assertEventType(type);
|
|
2481
|
+
return type;
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
// src/transaction-budget.ts
|
|
2485
|
+
async function runBudgetedTransaction(store, { clock, timeoutMs, work }) {
|
|
2486
|
+
const startedAt = clock.now().getTime();
|
|
2487
|
+
const overrun = () => clock.now().getTime() - startedAt >= timeoutMs;
|
|
2488
|
+
let committing = false;
|
|
2489
|
+
try {
|
|
2490
|
+
return await store.transaction(
|
|
2491
|
+
async (transaction) => {
|
|
2492
|
+
committing = false;
|
|
2493
|
+
const result = await work(transaction);
|
|
2494
|
+
if (overrun()) {
|
|
2495
|
+
throw createTimeoutError(timeoutMs, void 0);
|
|
2496
|
+
}
|
|
2497
|
+
committing = true;
|
|
2498
|
+
return result;
|
|
2499
|
+
},
|
|
2500
|
+
{ timeoutMs }
|
|
2501
|
+
);
|
|
2502
|
+
} catch (error) {
|
|
2503
|
+
if (error instanceof TransactionTimeoutError || committing || !overrun()) {
|
|
2504
|
+
throw error;
|
|
2505
|
+
}
|
|
2506
|
+
throw createTimeoutError(timeoutMs, error);
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
function createTimeoutError(timeoutMs, cause) {
|
|
2510
|
+
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`;
|
|
2511
|
+
return cause === void 0 ? new TransactionTimeoutError(message) : new TransactionTimeoutError(`${message} | cause=${toErrorInfo(cause).message}`, { cause });
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
// src/relay-bus.ts
|
|
2515
|
+
var DEFAULT_DRAIN_BATCHES = 1e3;
|
|
1848
2516
|
var WAKE_DELAY_MS = 25;
|
|
1849
2517
|
var RelayBus = class {
|
|
1850
2518
|
#store;
|
|
1851
2519
|
#source;
|
|
1852
2520
|
#transactionMode;
|
|
2521
|
+
#transactionTimeoutMs;
|
|
1853
2522
|
#transactionResolver;
|
|
1854
2523
|
#unroutedEvents;
|
|
1855
2524
|
#registry = new HandlerRegistry();
|
|
@@ -1866,43 +2535,70 @@ var RelayBus = class {
|
|
|
1866
2535
|
#setupStore;
|
|
1867
2536
|
#retention;
|
|
1868
2537
|
#retentionTimer;
|
|
2538
|
+
#observation;
|
|
2539
|
+
#backlogIntervalMs;
|
|
2540
|
+
#backlogTimer;
|
|
2541
|
+
#shutdownTimeoutMs;
|
|
1869
2542
|
#state = "idle";
|
|
1870
2543
|
#starting;
|
|
1871
2544
|
#stopping;
|
|
1872
2545
|
constructor(options) {
|
|
1873
2546
|
this.#store = options.store;
|
|
1874
2547
|
this.#source = options.source ?? "relaybus";
|
|
1875
|
-
this.#transactionMode = oneOf(
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
"unroutedEvents"
|
|
1884
|
-
);
|
|
2548
|
+
this.#transactionMode = oneOf(options.transactionMode ?? "required", {
|
|
2549
|
+
allowed: ["required", "optional"],
|
|
2550
|
+
name: "transactionMode"
|
|
2551
|
+
});
|
|
2552
|
+
this.#unroutedEvents = oneOf(options.unroutedEvents ?? "warn", {
|
|
2553
|
+
allowed: ["warn", "throw"],
|
|
2554
|
+
name: "unroutedEvents"
|
|
2555
|
+
});
|
|
1885
2556
|
this.#transactionResolver = options.transactionResolver;
|
|
1886
|
-
this.#
|
|
1887
|
-
|
|
1888
|
-
timeoutMs: options.handlerTimeout === void 0 || options.handlerTimeout === false ? null : toTimerMilliseconds(options.handlerTimeout, "handlerTimeout")
|
|
1889
|
-
};
|
|
2557
|
+
this.#transactionTimeoutMs = resolveTransactionTimeout(options);
|
|
2558
|
+
this.#defaults = createHandlerDefaults(options);
|
|
1890
2559
|
this.#logger = options.logger ?? console;
|
|
1891
2560
|
this.#notifier = new Notifier(options.observers ?? [], this.#logger);
|
|
1892
2561
|
this.#clock = options.clock ?? systemClock;
|
|
2562
|
+
this.#observation = BusObservation.create({
|
|
2563
|
+
notifier: this.#notifier,
|
|
2564
|
+
tracer: options.tracer,
|
|
2565
|
+
clock: this.#clock
|
|
2566
|
+
});
|
|
2567
|
+
this.#backlogIntervalMs = resolveBacklogInterval(options);
|
|
1893
2568
|
this.#generateIdentifier = options.generateIdentifier ?? (() => createUuidV7());
|
|
1894
2569
|
this.#setupStore = options.setupStore ?? true;
|
|
1895
2570
|
this.#retention = createRetentionSettings(options.retention);
|
|
2571
|
+
this.#subscriptions = options.subscriptions ?? [];
|
|
2572
|
+
this.#registerConnections(options);
|
|
2573
|
+
this.#dispatcherEnabled = isDispatcherEnabled(options);
|
|
2574
|
+
const settings = createDispatcherSettings(options.dispatcher, this.#generateIdentifier);
|
|
2575
|
+
this.#shutdownTimeoutMs = settings.shutdownTimeoutMs;
|
|
2576
|
+
this.#dispatcher = this.#createDispatcher(settings, options);
|
|
2577
|
+
}
|
|
2578
|
+
#createDispatcher(settings, options) {
|
|
2579
|
+
return new Dispatcher({
|
|
2580
|
+
store: this.#store,
|
|
2581
|
+
registry: this.#registry,
|
|
2582
|
+
clock: this.#clock,
|
|
2583
|
+
random: options.random ?? Math.random,
|
|
2584
|
+
notifier: this.#notifier,
|
|
2585
|
+
settings,
|
|
2586
|
+
observation: DispatchObservation.create({
|
|
2587
|
+
notifier: this.#notifier,
|
|
2588
|
+
tracer: options.tracer,
|
|
2589
|
+
clock: this.#clock
|
|
2590
|
+
})
|
|
2591
|
+
});
|
|
2592
|
+
}
|
|
2593
|
+
/** Registers the transports, then the routes, checks the subscriptions and registers the catalogs. */
|
|
2594
|
+
#registerConnections(options) {
|
|
1896
2595
|
for (const transport of options.transports ?? []) {
|
|
1897
2596
|
if (this.#transports.has(transport.name)) {
|
|
1898
2597
|
throw new ConfigurationError(`Transport name '${transport.name}' is used twice`);
|
|
1899
2598
|
}
|
|
1900
2599
|
this.#transports.set(transport.name, transport);
|
|
1901
2600
|
}
|
|
1902
|
-
|
|
1903
|
-
this.#registerRoute(route);
|
|
1904
|
-
}
|
|
1905
|
-
this.#subscriptions = options.subscriptions ?? [];
|
|
2601
|
+
this.#registerRoutes(options.routes ?? []);
|
|
1906
2602
|
for (const subscription of this.#subscriptions) {
|
|
1907
2603
|
const transport = this.#requireTransport(subscription.transport);
|
|
1908
2604
|
if (transport.subscribe === void 0) {
|
|
@@ -1912,15 +2608,6 @@ var RelayBus = class {
|
|
|
1912
2608
|
for (const catalog of options.catalogs ?? []) {
|
|
1913
2609
|
this.registerCatalog(catalog);
|
|
1914
2610
|
}
|
|
1915
|
-
this.#dispatcherEnabled = options.dispatcher?.enabled ?? true;
|
|
1916
|
-
this.#dispatcher = new Dispatcher({
|
|
1917
|
-
store: this.#store,
|
|
1918
|
-
registry: this.#registry,
|
|
1919
|
-
clock: this.#clock,
|
|
1920
|
-
random: options.random ?? Math.random,
|
|
1921
|
-
notifier: this.#notifier,
|
|
1922
|
-
settings: createDispatcherSettings(options.dispatcher, this.#generateIdentifier)
|
|
1923
|
-
});
|
|
1924
2611
|
}
|
|
1925
2612
|
get store() {
|
|
1926
2613
|
return this.#store;
|
|
@@ -1940,11 +2627,12 @@ var RelayBus = class {
|
|
|
1940
2627
|
}
|
|
1941
2628
|
return true;
|
|
1942
2629
|
}
|
|
2630
|
+
// eslint-disable-next-line relaybus/max-two-params -- public RelayBus.subscribe signature
|
|
1943
2631
|
subscribe(event, handler, options) {
|
|
1944
2632
|
const { type, schema } = describeEvent(event);
|
|
1945
2633
|
this.#registry.register(
|
|
1946
2634
|
{
|
|
1947
|
-
|
|
2635
|
+
eventTypes: [type],
|
|
1948
2636
|
schema,
|
|
1949
2637
|
handle: handler,
|
|
1950
2638
|
options,
|
|
@@ -1953,8 +2641,9 @@ var RelayBus = class {
|
|
|
1953
2641
|
this.#defaults
|
|
1954
2642
|
);
|
|
1955
2643
|
}
|
|
2644
|
+
// eslint-disable-next-line relaybus/max-two-params -- public RelayBus.subscribeCatalog signature
|
|
1956
2645
|
subscribeCatalog(catalog, handlers, options = {}) {
|
|
1957
|
-
for (const binding of resolveCatalogHandlers(catalog, handlers, options)) {
|
|
2646
|
+
for (const binding of resolveCatalogHandlers(catalog, { handlers, options })) {
|
|
1958
2647
|
this.subscribe(binding.eventClass, binding.handle, binding.options);
|
|
1959
2648
|
}
|
|
1960
2649
|
}
|
|
@@ -1977,7 +2666,9 @@ var RelayBus = class {
|
|
|
1977
2666
|
}
|
|
1978
2667
|
/** Registered handlers and routes, for diagnostics. */
|
|
1979
2668
|
handlers() {
|
|
1980
|
-
return this.#registry.all().
|
|
2669
|
+
return this.#registry.all().flatMap(
|
|
2670
|
+
(registration) => registration.eventTypes.map((eventType) => toDescriptor(registration, eventType))
|
|
2671
|
+
);
|
|
1981
2672
|
}
|
|
1982
2673
|
/**
|
|
1983
2674
|
* Stores an event in the current transaction. Handlers run after the
|
|
@@ -1985,21 +2676,32 @@ var RelayBus = class {
|
|
|
1985
2676
|
* have been written when the id already exists.
|
|
1986
2677
|
*/
|
|
1987
2678
|
async publish(event, options = {}) {
|
|
1988
|
-
const [envelope] = await this.#
|
|
2679
|
+
const [envelope] = await this.#observedPublish([event], { options, id: options.id });
|
|
1989
2680
|
return envelope;
|
|
1990
2681
|
}
|
|
1991
2682
|
/** Stores several events atomically, in order, sharing one correlation id. */
|
|
1992
2683
|
async publishAll(events, options = {}) {
|
|
1993
|
-
return this.#
|
|
2684
|
+
return this.#observedPublish(events, { options, id: void 0 });
|
|
1994
2685
|
}
|
|
1995
2686
|
/**
|
|
1996
2687
|
* Runs `work` in a new store transaction. `publish` calls inside it join the
|
|
1997
|
-
* transaction without passing it explicitly.
|
|
2688
|
+
* transaction without passing it explicitly. The transaction is held to `transactionTimeout`
|
|
2689
|
+
* and rolled back with a {@link TransactionTimeoutError} when it runs past it.
|
|
1998
2690
|
*/
|
|
1999
2691
|
async transaction(work) {
|
|
2000
|
-
return this.#store
|
|
2001
|
-
|
|
2002
|
-
|
|
2692
|
+
return runBudgetedTransaction(this.#store, {
|
|
2693
|
+
clock: this.#clock,
|
|
2694
|
+
timeoutMs: this.#transactionTimeoutMs,
|
|
2695
|
+
work: (transaction) => runInScope({ transaction }, () => work(transaction))
|
|
2696
|
+
});
|
|
2697
|
+
}
|
|
2698
|
+
/**
|
|
2699
|
+
* The transaction `publish` would join right now: the one of the enclosing `bus.transaction`
|
|
2700
|
+
* (or `@RelayBusTransaction()` method, or transactional handler), else the one
|
|
2701
|
+
* `transactionResolver` finds. Repositories use it to write in the same transaction.
|
|
2702
|
+
*/
|
|
2703
|
+
currentTransaction() {
|
|
2704
|
+
return getCurrentScope()?.transaction ?? this.#transactionResolver?.();
|
|
2003
2705
|
}
|
|
2004
2706
|
/**
|
|
2005
2707
|
* Stores an event received from a broker and schedules it for local handlers.
|
|
@@ -2007,7 +2709,13 @@ var RelayBus = class {
|
|
|
2007
2709
|
* only after it resolves. Throws {@link InvalidEnvelopeError} for malformed
|
|
2008
2710
|
* events, which transports should dead-letter instead of redelivering.
|
|
2009
2711
|
*/
|
|
2010
|
-
|
|
2712
|
+
receive(envelope) {
|
|
2713
|
+
return this.#observation === void 0 ? this.#receive(envelope) : this.#observedReceive(envelope, void 0);
|
|
2714
|
+
}
|
|
2715
|
+
#observedReceive(envelope, origin) {
|
|
2716
|
+
return this.#observation.receive({ envelope, origin }, () => this.#receive(envelope));
|
|
2717
|
+
}
|
|
2718
|
+
async #receive(envelope) {
|
|
2011
2719
|
const normalized = normalizeEnvelope(envelope);
|
|
2012
2720
|
const registrations = this.#registry.forEventType(normalized.type).filter((registration) => !registration.forwardsToTransport);
|
|
2013
2721
|
if (registrations.length === 0) {
|
|
@@ -2036,7 +2744,7 @@ var RelayBus = class {
|
|
|
2036
2744
|
*/
|
|
2037
2745
|
async start() {
|
|
2038
2746
|
if (this.#state !== "idle") {
|
|
2039
|
-
throw new
|
|
2747
|
+
throw new LifecycleError(
|
|
2040
2748
|
this.#state === "stopped" ? "A stopped relaybus cannot be started again; create a new instance" : `Cannot start a relaybus that is ${this.#state}`
|
|
2041
2749
|
);
|
|
2042
2750
|
}
|
|
@@ -2050,25 +2758,43 @@ var RelayBus = class {
|
|
|
2050
2758
|
}
|
|
2051
2759
|
}
|
|
2052
2760
|
/**
|
|
2053
|
-
* Stops
|
|
2054
|
-
*
|
|
2761
|
+
* Stops claiming, waits for running handlers, hands back leases, closes transports and the store,
|
|
2762
|
+
* all within `options.timeout` (default `dispatcher.shutdownTimeout`). When the deadline passes it
|
|
2763
|
+
* aborts the handlers still running, force-closes what relaybus opened itself (never pools or
|
|
2764
|
+
* clients the application passed in) and resolves anyway: it never rejects because of the
|
|
2765
|
+
* deadline. Abandoned deliveries stay leased until their lease expires and then run again
|
|
2766
|
+
* (at-least-once). Waits for a start in progress first. Safe to call more than once: later calls
|
|
2767
|
+
* get the result of the first.
|
|
2055
2768
|
*/
|
|
2056
|
-
async stop() {
|
|
2769
|
+
async stop(options = {}) {
|
|
2770
|
+
const timeoutMs = options.timeout === void 0 ? this.#shutdownTimeoutMs : toTimerMilliseconds(options.timeout, "timeout");
|
|
2057
2771
|
if (this.#stopping !== void 0) {
|
|
2058
2772
|
return this.#stopping;
|
|
2059
2773
|
}
|
|
2060
|
-
if (this.#state
|
|
2061
|
-
|
|
2774
|
+
if (this.#state !== "running" && this.#state !== "starting") {
|
|
2775
|
+
return this.#stopOutsideRun(timeoutMs);
|
|
2062
2776
|
}
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2777
|
+
this.#stopping = this.#stopComponents(timeoutMs);
|
|
2778
|
+
const result = await this.#stopping;
|
|
2779
|
+
if (!result.graceful) {
|
|
2780
|
+
this.#logger.warn(
|
|
2781
|
+
`[RelayBus](stop) Shutdown deadline passed, forced close | abandoned=${result.abandonedDeliveries} | timeoutMs=${timeoutMs}`
|
|
2782
|
+
);
|
|
2066
2783
|
}
|
|
2067
|
-
|
|
2068
|
-
|
|
2784
|
+
return result;
|
|
2785
|
+
}
|
|
2786
|
+
/**
|
|
2787
|
+
* A bus that is not running: an idle one only settles what batches run by hand left behind (and
|
|
2788
|
+
* stays startable); a stopped one has nothing left to do.
|
|
2789
|
+
*/
|
|
2790
|
+
async #stopOutsideRun(timeoutMs) {
|
|
2791
|
+
if (this.#state !== "idle") {
|
|
2792
|
+
return { graceful: true, abandonedDeliveries: 0, durationMs: 0 };
|
|
2069
2793
|
}
|
|
2070
|
-
|
|
2071
|
-
|
|
2794
|
+
return withinDeadline(
|
|
2795
|
+
{ clock: this.#clock, timeoutMs },
|
|
2796
|
+
(deadline) => this.#dispatcher.stop(deadline)
|
|
2797
|
+
);
|
|
2072
2798
|
}
|
|
2073
2799
|
/**
|
|
2074
2800
|
* Claims and runs one batch of due deliveries, resolving when they settle.
|
|
@@ -2081,7 +2807,7 @@ var RelayBus = class {
|
|
|
2081
2807
|
* Runs batches until nothing is due. Deliveries scheduled in the future
|
|
2082
2808
|
* (retries, delays) are not waited for. Intended for tests.
|
|
2083
2809
|
*/
|
|
2084
|
-
async drain(maxBatches =
|
|
2810
|
+
async drain(maxBatches = DEFAULT_DRAIN_BATCHES) {
|
|
2085
2811
|
let total = 0;
|
|
2086
2812
|
for (let batch = 0; batch < maxBatches; batch += 1) {
|
|
2087
2813
|
const processed = await this.runOnce();
|
|
@@ -2090,7 +2816,7 @@ var RelayBus = class {
|
|
|
2090
2816
|
}
|
|
2091
2817
|
total += processed;
|
|
2092
2818
|
}
|
|
2093
|
-
throw new
|
|
2819
|
+
throw new DrainIncompleteError(`drain did not finish within ${maxBatches} batches`);
|
|
2094
2820
|
}
|
|
2095
2821
|
listDeliveries(query = {}) {
|
|
2096
2822
|
return this.#store.listDeliveries(query);
|
|
@@ -2132,41 +2858,128 @@ var RelayBus = class {
|
|
|
2132
2858
|
}
|
|
2133
2859
|
for (const subscription of this.#subscriptions) {
|
|
2134
2860
|
const transport = this.#transports.get(subscription.transport);
|
|
2135
|
-
await transport.subscribe(
|
|
2136
|
-
|
|
2137
|
-
|
|
2861
|
+
await transport.subscribe(
|
|
2862
|
+
subscription.destinations,
|
|
2863
|
+
async (envelope, message) => {
|
|
2864
|
+
await (this.#observation === void 0 ? this.#receive(envelope) : this.#observedReceive(envelope, { transport, destination: message?.destination }));
|
|
2865
|
+
},
|
|
2866
|
+
this.#subscriptionHooks(transport)
|
|
2867
|
+
);
|
|
2138
2868
|
}
|
|
2139
2869
|
} catch (error) {
|
|
2140
|
-
await
|
|
2870
|
+
await withinDeadline(
|
|
2871
|
+
{ clock: this.#clock, timeoutMs: this.#shutdownTimeoutMs },
|
|
2872
|
+
async (deadline) => {
|
|
2873
|
+
await this.#closeTransports(deadline);
|
|
2874
|
+
return 0;
|
|
2875
|
+
}
|
|
2876
|
+
);
|
|
2141
2877
|
this.#state = "stopped";
|
|
2142
2878
|
throw error;
|
|
2143
2879
|
}
|
|
2880
|
+
if (this.#state !== "starting") {
|
|
2881
|
+
throw new LifecycleError("relaybus was stopped before it finished starting");
|
|
2882
|
+
}
|
|
2144
2883
|
if (this.#dispatcherEnabled) {
|
|
2145
2884
|
this.#dispatcher.start();
|
|
2146
2885
|
}
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
this.purgeExpired().catch(() => void 0);
|
|
2150
|
-
}, this.#retention.intervalMs);
|
|
2151
|
-
this.#retentionTimer.unref();
|
|
2152
|
-
}
|
|
2886
|
+
this.#scheduleRetention();
|
|
2887
|
+
this.#scheduleBacklog();
|
|
2153
2888
|
this.#state = "running";
|
|
2154
2889
|
this.#logger.log(
|
|
2155
|
-
`[RelayBus](
|
|
2890
|
+
`[RelayBus](startComponents) relaybus started | handlers=${this.#registry.all().length} dispatcher=${this.#dispatcherEnabled}`
|
|
2156
2891
|
);
|
|
2157
2892
|
}
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2893
|
+
/** What a transport may report back while subscribed; undefined when no observer wants it. */
|
|
2894
|
+
#subscriptionHooks(transport) {
|
|
2895
|
+
if (!this.#notifier.wants("onInvalidMessage")) {
|
|
2896
|
+
return void 0;
|
|
2897
|
+
}
|
|
2898
|
+
return {
|
|
2899
|
+
onInvalidMessage: ({ destination, error }) => {
|
|
2900
|
+
const errorInfo = toErrorInfo(error);
|
|
2901
|
+
this.#notifier.notify("onInvalidMessage", {
|
|
2902
|
+
transport: transport.name,
|
|
2903
|
+
system: transport.system ?? transport.name,
|
|
2904
|
+
destination,
|
|
2905
|
+
reason: errorInfo.message,
|
|
2906
|
+
error: errorInfo
|
|
2907
|
+
});
|
|
2908
|
+
}
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2911
|
+
/** Applies the retention policy every `retention.interval` while the bus runs, when it deletes anything. */
|
|
2912
|
+
#scheduleRetention() {
|
|
2913
|
+
if (this.#retention.completedMs !== null || this.#retention.deadMs !== null) {
|
|
2914
|
+
this.#retentionTimer = schedule(this.#clock, {
|
|
2915
|
+
run: () => {
|
|
2916
|
+
this.purgeExpired().catch(() => void 0);
|
|
2917
|
+
},
|
|
2918
|
+
delayMs: this.#retention.intervalMs,
|
|
2919
|
+
repeat: true,
|
|
2920
|
+
unref: true
|
|
2921
|
+
});
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
/** Reports the backlog every `backlogInterval` while the bus runs, when an observer wants it. */
|
|
2925
|
+
#scheduleBacklog() {
|
|
2926
|
+
if (this.#backlogIntervalMs !== void 0) {
|
|
2927
|
+
this.#backlogTimer = this.#observation?.startBacklog({
|
|
2928
|
+
intervalMs: this.#backlogIntervalMs,
|
|
2929
|
+
statistics: () => this.statistics()
|
|
2930
|
+
});
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
/**
|
|
2934
|
+
* Waits for a start in progress (a failed start leaves nothing to stop), then stops the dispatcher
|
|
2935
|
+
* and closes the transports and the store. A start still running at the deadline is given up: the
|
|
2936
|
+
* transports and the store are force-closed and the start rejects when it resumes.
|
|
2937
|
+
*/
|
|
2938
|
+
async #stopComponents(timeoutMs) {
|
|
2161
2939
|
try {
|
|
2162
|
-
await this.#
|
|
2163
|
-
|
|
2164
|
-
|
|
2940
|
+
return await withinDeadline({ clock: this.#clock, timeoutMs }, async (deadline) => {
|
|
2941
|
+
if (this.#state === "starting") {
|
|
2942
|
+
const started = await deadline.race(this.#starting.catch(() => void 0));
|
|
2943
|
+
if (started === "timeout") {
|
|
2944
|
+
this.#state = "stopping";
|
|
2945
|
+
await this.#closeConnections(deadline);
|
|
2946
|
+
return 0;
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
if (this.#state !== "running") {
|
|
2950
|
+
return 0;
|
|
2951
|
+
}
|
|
2952
|
+
this.#state = "stopping";
|
|
2953
|
+
this.#retentionTimer?.();
|
|
2954
|
+
this.#backlogTimer?.();
|
|
2955
|
+
const abandoned = await this.#dispatcher.stop(deadline);
|
|
2956
|
+
await this.#closeConnections(deadline);
|
|
2957
|
+
return abandoned;
|
|
2958
|
+
});
|
|
2165
2959
|
} finally {
|
|
2166
2960
|
this.#state = "stopped";
|
|
2167
2961
|
}
|
|
2168
2962
|
}
|
|
2169
|
-
async #
|
|
2963
|
+
async #closeConnections(deadline) {
|
|
2964
|
+
await this.#closeTransports(deadline);
|
|
2965
|
+
const options = { signal: deadline.signal };
|
|
2966
|
+
await deadline.race(Promise.resolve(this.#store.close?.(options)));
|
|
2967
|
+
}
|
|
2968
|
+
/** Publishes inside the tracer's span, with its trace headers, when the bus is observed. */
|
|
2969
|
+
#observedPublish(events, input) {
|
|
2970
|
+
const observation = this.#observation;
|
|
2971
|
+
if (observation === void 0) {
|
|
2972
|
+
return this.#publish(events, input);
|
|
2973
|
+
}
|
|
2974
|
+
return observation.publish(
|
|
2975
|
+
{ eventTypes: events.map((event) => String(event.type)) },
|
|
2976
|
+
(headers) => this.#publish(events, { ...input, options: withTraceHeaders(input.options, headers) })
|
|
2977
|
+
);
|
|
2978
|
+
}
|
|
2979
|
+
async #publish(events, {
|
|
2980
|
+
options,
|
|
2981
|
+
id
|
|
2982
|
+
}) {
|
|
2170
2983
|
if (events.length === 0) {
|
|
2171
2984
|
return [];
|
|
2172
2985
|
}
|
|
@@ -2174,10 +2987,27 @@ var RelayBus = class {
|
|
|
2174
2987
|
const delayMs = options.delay === void 0 ? 0 : toMilliseconds(options.delay, "delay");
|
|
2175
2988
|
const availableAt = new Date(now.getTime() + delayMs);
|
|
2176
2989
|
const scope = getCurrentScope();
|
|
2990
|
+
const entries = await this.#createEntries({ events, options, id, scope, now, availableAt });
|
|
2991
|
+
const deliverable = entries.filter((entry) => entry.deliveries.length > 0);
|
|
2992
|
+
if (deliverable.length > 0) {
|
|
2993
|
+
await this.#appendDeliverable({ entries: deliverable, options, scope, now, delayMs });
|
|
2994
|
+
}
|
|
2995
|
+
return entries.map((entry) => entry.event);
|
|
2996
|
+
}
|
|
2997
|
+
/** One entry per event, in order: its envelope and a delivery for every registration of its type. */
|
|
2998
|
+
async #createEntries(input) {
|
|
2999
|
+
const { events, options, id, scope, now, availableAt } = input;
|
|
2177
3000
|
const entries = [];
|
|
2178
3001
|
let correlationId = options.correlationId ?? scope?.event?.correlationId ?? scope?.event?.id;
|
|
2179
3002
|
for (const event of events) {
|
|
2180
|
-
const envelope = await this.#createEnvelope(
|
|
3003
|
+
const envelope = await this.#createEnvelope({
|
|
3004
|
+
event,
|
|
3005
|
+
options,
|
|
3006
|
+
id,
|
|
3007
|
+
scope,
|
|
3008
|
+
now,
|
|
3009
|
+
correlationId
|
|
3010
|
+
});
|
|
2181
3011
|
correlationId = envelope.correlationId;
|
|
2182
3012
|
const registrations = this.#registry.forEventType(envelope.type);
|
|
2183
3013
|
if (registrations.length === 0) {
|
|
@@ -2190,31 +3020,36 @@ var RelayBus = class {
|
|
|
2190
3020
|
)
|
|
2191
3021
|
});
|
|
2192
3022
|
}
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
3023
|
+
return entries;
|
|
3024
|
+
}
|
|
3025
|
+
/** Appends the entries that have deliveries, reports what was new and wakes the dispatcher when it is due now. */
|
|
3026
|
+
async #appendDeliverable(input) {
|
|
3027
|
+
const { entries, options, scope, now, delayMs } = input;
|
|
3028
|
+
const result = await this.#appendInTransaction(entries, { options, scope, now });
|
|
3029
|
+
const appended = new Set(result.appendedEventIds);
|
|
3030
|
+
const published = entries.map((entry) => entry.event).filter((envelope) => appended.has(envelope.id));
|
|
3031
|
+
if (published.length > 0) {
|
|
3032
|
+
this.#notifier.notify("onPublished", {
|
|
3033
|
+
envelopes: published,
|
|
3034
|
+
deliveries: result.appendedDeliveryCount
|
|
3035
|
+
});
|
|
3036
|
+
if (delayMs === 0) {
|
|
3037
|
+
this.#scheduleWake();
|
|
2206
3038
|
}
|
|
2207
3039
|
}
|
|
2208
|
-
return entries.map((entry) => entry.event);
|
|
2209
3040
|
}
|
|
2210
3041
|
#reportUnrouted(envelope) {
|
|
2211
3042
|
const message = `No handler or route for event type, event not stored | type=${envelope.type} id=${envelope.id}`;
|
|
2212
3043
|
if (this.#unroutedEvents === "throw") {
|
|
2213
3044
|
throw new ConfigurationError(message);
|
|
2214
3045
|
}
|
|
2215
|
-
this.#logger.warn(`[RelayBus](
|
|
3046
|
+
this.#logger.warn(`[RelayBus](reportUnrouted) ${message}`);
|
|
2216
3047
|
}
|
|
2217
|
-
async #appendInTransaction(entries,
|
|
3048
|
+
async #appendInTransaction(entries, {
|
|
3049
|
+
options,
|
|
3050
|
+
scope,
|
|
3051
|
+
now
|
|
3052
|
+
}) {
|
|
2218
3053
|
const transaction = options.transaction ?? scope?.transaction ?? this.#transactionResolver?.();
|
|
2219
3054
|
if (transaction !== void 0) {
|
|
2220
3055
|
return this.#store.append(transaction, entries, now);
|
|
@@ -2228,7 +3063,8 @@ var RelayBus = class {
|
|
|
2228
3063
|
(ownTransaction) => this.#store.append(ownTransaction, entries, now)
|
|
2229
3064
|
);
|
|
2230
3065
|
}
|
|
2231
|
-
async #createEnvelope(
|
|
3066
|
+
async #createEnvelope(input) {
|
|
3067
|
+
const { event, options, id, scope, now, correlationId } = input;
|
|
2232
3068
|
if (typeof event.type !== "string") {
|
|
2233
3069
|
throw new ConfigurationError(
|
|
2234
3070
|
"publish expects an instance of a class created with defineEvent"
|
|
@@ -2245,7 +3081,7 @@ var RelayBus = class {
|
|
|
2245
3081
|
id: eventId,
|
|
2246
3082
|
type: event.type,
|
|
2247
3083
|
source: this.#source,
|
|
2248
|
-
key: options.key ??
|
|
3084
|
+
key: options.key ?? keyFromDefinition(event, payload),
|
|
2249
3085
|
payload,
|
|
2250
3086
|
headers: { ...options.headers },
|
|
2251
3087
|
correlationId: correlationId ?? eventId,
|
|
@@ -2261,17 +3097,26 @@ var RelayBus = class {
|
|
|
2261
3097
|
const routes = registrations.filter((registration) => registration.forwardsToTransport).length;
|
|
2262
3098
|
return { handlers: registrations.length - routes, routes };
|
|
2263
3099
|
}
|
|
2264
|
-
#
|
|
2265
|
-
const
|
|
2266
|
-
|
|
2267
|
-
|
|
3100
|
+
#registerRoutes(routes) {
|
|
3101
|
+
for (const route of routes) {
|
|
3102
|
+
this.#requireTransport(route.transport);
|
|
3103
|
+
}
|
|
3104
|
+
for (const group of groupRoutes(routes)) {
|
|
3105
|
+
this.#registerRoute(group);
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
/** Registers one handler for every route to a destination, so a key's events reach it in commit order. */
|
|
3109
|
+
#registerRoute(group) {
|
|
3110
|
+
const transport = this.#transports.get(group.transport);
|
|
3111
|
+
const { route, destination } = group;
|
|
2268
3112
|
this.#registry.register(
|
|
2269
3113
|
{
|
|
2270
|
-
|
|
3114
|
+
eventTypes: group.eventTypes,
|
|
3115
|
+
legacyIds: group.legacyIds,
|
|
2271
3116
|
schema: void 0,
|
|
2272
|
-
handle: (envelope) => transport.publish(envelope, destination),
|
|
3117
|
+
handle: this.#observation?.sender(transport, destination) ?? ((envelope) => transport.publish(envelope, destination)),
|
|
2273
3118
|
options: {
|
|
2274
|
-
id:
|
|
3119
|
+
id: group.id,
|
|
2275
3120
|
...route.retry === void 0 ? {} : { retry: route.retry },
|
|
2276
3121
|
...route.timeout === void 0 ? {} : { timeout: route.timeout }
|
|
2277
3122
|
},
|
|
@@ -2291,12 +3136,18 @@ var RelayBus = class {
|
|
|
2291
3136
|
if (this.#state !== "running" || this.#dispatcher.watchesNewDeliveries) {
|
|
2292
3137
|
return;
|
|
2293
3138
|
}
|
|
2294
|
-
|
|
3139
|
+
schedule(this.#clock, {
|
|
3140
|
+
run: () => this.#dispatcher.wake(),
|
|
3141
|
+
delayMs: WAKE_DELAY_MS,
|
|
3142
|
+
unref: true
|
|
3143
|
+
});
|
|
2295
3144
|
}
|
|
2296
|
-
|
|
3145
|
+
/** Closes every transport, each at most until the deadline; past it they are asked to force. */
|
|
3146
|
+
async #closeTransports(deadline) {
|
|
3147
|
+
const options = { signal: deadline.signal };
|
|
2297
3148
|
for (const transport of this.#transports.values()) {
|
|
2298
3149
|
try {
|
|
2299
|
-
await transport.close();
|
|
3150
|
+
await deadline.race(transport.close(options));
|
|
2300
3151
|
} catch (error) {
|
|
2301
3152
|
this.#logger.error(
|
|
2302
3153
|
`[RelayBus](closeTransports) Failed to close transport | transport=${transport.name} error=${toErrorInfo(error).message}`
|
|
@@ -2305,10 +3156,10 @@ var RelayBus = class {
|
|
|
2305
3156
|
}
|
|
2306
3157
|
}
|
|
2307
3158
|
};
|
|
2308
|
-
function toDescriptor(registration) {
|
|
3159
|
+
function toDescriptor(registration, eventType) {
|
|
2309
3160
|
return {
|
|
2310
3161
|
id: registration.id,
|
|
2311
|
-
eventType
|
|
3162
|
+
eventType,
|
|
2312
3163
|
retry: registration.retry,
|
|
2313
3164
|
timeoutMs: registration.timeoutMs,
|
|
2314
3165
|
concurrency: registration.concurrency,
|
|
@@ -2324,99 +3175,7 @@ function describeEvent(event) {
|
|
|
2324
3175
|
assertEventType(event.type);
|
|
2325
3176
|
return { type: event.type, schema: event.schema };
|
|
2326
3177
|
}
|
|
2327
|
-
function normalizeEnvelope(envelope) {
|
|
2328
|
-
if (typeof envelope.id !== "string" || envelope.id.length === 0) {
|
|
2329
|
-
throw new InvalidEnvelopeError("Received event has no id");
|
|
2330
|
-
}
|
|
2331
|
-
if (typeof envelope.type !== "string") {
|
|
2332
|
-
throw new InvalidEnvelopeError(`Received event ${envelope.id} has no type`);
|
|
2333
|
-
}
|
|
2334
|
-
try {
|
|
2335
|
-
assertEventType(envelope.type);
|
|
2336
|
-
} catch (error) {
|
|
2337
|
-
throw new InvalidEnvelopeError(toErrorInfo(error).message);
|
|
2338
|
-
}
|
|
2339
|
-
const occurredAt = toDate(envelope.occurredAt);
|
|
2340
|
-
if (occurredAt === void 0) {
|
|
2341
|
-
throw new InvalidEnvelopeError(`Received event ${envelope.id} has an invalid occurredAt`);
|
|
2342
|
-
}
|
|
2343
|
-
return {
|
|
2344
|
-
id: envelope.id,
|
|
2345
|
-
type: envelope.type,
|
|
2346
|
-
source: typeof envelope.source === "string" ? envelope.source : "unknown",
|
|
2347
|
-
key: optionalText(envelope.key),
|
|
2348
|
-
payload: envelope.payload,
|
|
2349
|
-
headers: { ...envelope.headers },
|
|
2350
|
-
correlationId: optionalText(envelope.correlationId),
|
|
2351
|
-
causationId: optionalText(envelope.causationId),
|
|
2352
|
-
occurredAt
|
|
2353
|
-
};
|
|
2354
|
-
}
|
|
2355
|
-
function toDate(value) {
|
|
2356
|
-
const date = typeof value === "string" ? new Date(value) : value;
|
|
2357
|
-
return date instanceof Date && !Number.isNaN(date.getTime()) ? date : void 0;
|
|
2358
|
-
}
|
|
2359
|
-
function optionalText(value) {
|
|
2360
|
-
return typeof value === "string" && value.length > 0 ? value : null;
|
|
2361
|
-
}
|
|
2362
|
-
function oneOf(value, allowed, name) {
|
|
2363
|
-
if (!allowed.includes(value)) {
|
|
2364
|
-
throw new ConfigurationError(
|
|
2365
|
-
`${name} must be one of ${allowed.map((option) => `'${option}'`).join(", ")}, received '${String(value)}'`
|
|
2366
|
-
);
|
|
2367
|
-
}
|
|
2368
|
-
return value;
|
|
2369
|
-
}
|
|
2370
|
-
function positiveInteger(value, name) {
|
|
2371
|
-
if (!Number.isInteger(value) || value < 1) {
|
|
2372
|
-
throw new ConfigurationError(`${name} must be a positive integer, received ${value}`);
|
|
2373
|
-
}
|
|
2374
|
-
return value;
|
|
2375
|
-
}
|
|
2376
|
-
function createDispatcherSettings(options, generateIdentifier) {
|
|
2377
|
-
const concurrency = positiveInteger(
|
|
2378
|
-
options?.concurrency ?? DEFAULTS.concurrency,
|
|
2379
|
-
"dispatcher.concurrency"
|
|
2380
|
-
);
|
|
2381
|
-
return {
|
|
2382
|
-
owner: `${hostname()}:${process.pid}:${generateIdentifier()}`,
|
|
2383
|
-
concurrency,
|
|
2384
|
-
batchSize: positiveInteger(options?.batchSize ?? concurrency, "dispatcher.batchSize"),
|
|
2385
|
-
pollIntervalMs: toTimerMilliseconds(
|
|
2386
|
-
options?.pollInterval ?? DEFAULTS.pollInterval,
|
|
2387
|
-
"dispatcher.pollInterval"
|
|
2388
|
-
),
|
|
2389
|
-
leaseDurationMs: toTimerMilliseconds(
|
|
2390
|
-
options?.leaseDuration ?? DEFAULTS.leaseDuration,
|
|
2391
|
-
"dispatcher.leaseDuration"
|
|
2392
|
-
),
|
|
2393
|
-
shutdownTimeoutMs: toTimerMilliseconds(
|
|
2394
|
-
options?.shutdownTimeout ?? DEFAULTS.shutdownTimeout,
|
|
2395
|
-
"dispatcher.shutdownTimeout"
|
|
2396
|
-
),
|
|
2397
|
-
completionBatchWindowMs: toMilliseconds(
|
|
2398
|
-
options?.completionBatchWindow ?? DEFAULTS.completionBatchWindow,
|
|
2399
|
-
"dispatcher.completionBatchWindow"
|
|
2400
|
-
),
|
|
2401
|
-
completionBatchSize: positiveInteger(
|
|
2402
|
-
options?.completionBatchSize ?? DEFAULTS.completionBatchSize,
|
|
2403
|
-
"dispatcher.completionBatchSize"
|
|
2404
|
-
)
|
|
2405
|
-
};
|
|
2406
|
-
}
|
|
2407
|
-
function createRetentionSettings(options) {
|
|
2408
|
-
const completed = options?.completed ?? DEFAULTS.completedRetention;
|
|
2409
|
-
const dead = options?.dead ?? false;
|
|
2410
|
-
return {
|
|
2411
|
-
completedMs: completed === false ? null : toMilliseconds(completed, "retention.completed"),
|
|
2412
|
-
deadMs: dead === false ? null : toMilliseconds(dead, "retention.dead"),
|
|
2413
|
-
intervalMs: toTimerMilliseconds(
|
|
2414
|
-
options?.interval ?? DEFAULTS.retentionInterval,
|
|
2415
|
-
"retention.interval"
|
|
2416
|
-
)
|
|
2417
|
-
};
|
|
2418
|
-
}
|
|
2419
3178
|
|
|
2420
|
-
export { CatalogPublisher,
|
|
3179
|
+
export { CatalogPublisher, DEFAULT_RETRY_POLICY, DEFAULT_ROUTE_RETRY_POLICY, DELIVERY_STATUSES, InMemoryEventStore, InMemoryTransaction, MAX_TIMER_DELAY_MS, RelayBus, TRACEPARENT_HEADER, TRACESTATE_HEADER, computeRetryDelay, consume, createRetryPolicy, createUuidV7, defineCatalog, defineHandlers, describeCatalog, isRetryableError, produce, systemClock, toMilliseconds, traceContextOf };
|
|
2421
3180
|
//# sourceMappingURL=index.js.map
|
|
2422
3181
|
//# sourceMappingURL=index.js.map
|