@relaybus/core 0.0.0 → 0.2.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 +230 -26
- package/dist/index.cjs +759 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +608 -227
- package/dist/index.d.ts +608 -227
- package/dist/index.js +754 -64
- package/dist/index.js.map +1 -1
- package/package.json +20 -8
package/dist/index.js
CHANGED
|
@@ -34,6 +34,15 @@ var InvalidEnvelopeError = class extends NonRetryableError {
|
|
|
34
34
|
};
|
|
35
35
|
var LeaseLostError = class extends RelayBusError {
|
|
36
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
|
+
}
|
|
37
46
|
};
|
|
38
47
|
var HandlerTimeoutError = class extends RelayBusError {
|
|
39
48
|
name = "HandlerTimeoutError";
|
|
@@ -139,6 +148,261 @@ async function validatePayload(type, schema, payload) {
|
|
|
139
148
|
return result.value;
|
|
140
149
|
}
|
|
141
150
|
|
|
151
|
+
// src/catalog.ts
|
|
152
|
+
var EVENT_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
153
|
+
var descriptions = /* @__PURE__ */ new WeakMap();
|
|
154
|
+
function defineCatalog(events, options = {}) {
|
|
155
|
+
const label = options.name === void 0 ? "Catalog" : `Catalog '${options.name}'`;
|
|
156
|
+
const names = Object.keys(events);
|
|
157
|
+
if (names.length === 0) {
|
|
158
|
+
throw new ConfigurationError(`${label} must contain at least one event`);
|
|
159
|
+
}
|
|
160
|
+
const owners = /* @__PURE__ */ new Map();
|
|
161
|
+
const entries = names.map((name) => {
|
|
162
|
+
const eventClass = events[name];
|
|
163
|
+
if (!EVENT_NAME_PATTERN.test(name)) {
|
|
164
|
+
throw new ConfigurationError(
|
|
165
|
+
`${label} entry '${name}' must be a valid identifier made of letters, digits and underscores because it names the handler method on${name} and its handler id`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
if (typeof eventClass !== "function" || typeof eventClass.type !== "string") {
|
|
169
|
+
throw new ConfigurationError(
|
|
170
|
+
`${label} entry '${name}' must be an event class created with defineEvent`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
assertEventType(eventClass.type);
|
|
174
|
+
const owner = owners.get(eventClass.type);
|
|
175
|
+
if (owner !== void 0) {
|
|
176
|
+
throw new ConfigurationError(
|
|
177
|
+
`${label} entries '${owner}' and '${name}' share the event type '${eventClass.type}'`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
owners.set(eventClass.type, name);
|
|
181
|
+
return Object.freeze({ name, eventClass, type: eventClass.type, handlerName: `on${name}` });
|
|
182
|
+
});
|
|
183
|
+
const catalog = Object.freeze({ ...events });
|
|
184
|
+
descriptions.set(catalog, Object.freeze({ name: options.name, entries: Object.freeze(entries) }));
|
|
185
|
+
return catalog;
|
|
186
|
+
}
|
|
187
|
+
function describeCatalog(catalog) {
|
|
188
|
+
const description = descriptions.get(catalog);
|
|
189
|
+
if (description === void 0) {
|
|
190
|
+
throw new ConfigurationError("Expected a catalog created with defineCatalog");
|
|
191
|
+
}
|
|
192
|
+
return description;
|
|
193
|
+
}
|
|
194
|
+
function catalogLabel(catalog) {
|
|
195
|
+
const { name } = describeCatalog(catalog);
|
|
196
|
+
return name === void 0 ? "the catalog" : `catalog '${name}'`;
|
|
197
|
+
}
|
|
198
|
+
function selectCatalogEntries(catalog, events) {
|
|
199
|
+
const { entries } = describeCatalog(catalog);
|
|
200
|
+
if (events === void 0) {
|
|
201
|
+
return entries;
|
|
202
|
+
}
|
|
203
|
+
const label = catalogLabel(catalog);
|
|
204
|
+
if (events.length === 0) {
|
|
205
|
+
throw new ConfigurationError(
|
|
206
|
+
`The events selected from ${label} must name at least one event; omit events to select all of them`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
const selected = /* @__PURE__ */ new Set();
|
|
210
|
+
for (const event of events) {
|
|
211
|
+
if (!entries.some((entry) => entry.name === event)) {
|
|
212
|
+
throw new ConfigurationError(
|
|
213
|
+
`${capitalize(label)} has no event named '${String(event)}'; its events are ${entries.map((entry) => entry.name).join(", ")}`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
if (selected.has(event)) {
|
|
217
|
+
throw new ConfigurationError(`Event '${String(event)}' of ${label} is selected twice`);
|
|
218
|
+
}
|
|
219
|
+
selected.add(event);
|
|
220
|
+
}
|
|
221
|
+
return entries.filter((entry) => selected.has(entry.name));
|
|
222
|
+
}
|
|
223
|
+
function capitalize(text) {
|
|
224
|
+
return `${text.charAt(0).toUpperCase()}${text.slice(1)}`;
|
|
225
|
+
}
|
|
226
|
+
function requireCatalogEntry(catalog, event) {
|
|
227
|
+
const entry = describeCatalog(catalog).entries.find((candidate) => candidate.type === event.type);
|
|
228
|
+
if (entry === void 0) {
|
|
229
|
+
throw new ConfigurationError(
|
|
230
|
+
`Event type '${String(event.type)}' is not part of ${catalogLabel(catalog)}`
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
return entry;
|
|
234
|
+
}
|
|
235
|
+
function resolveCatalogHandlers(catalog, handlers, options) {
|
|
236
|
+
const { name } = describeCatalog(catalog);
|
|
237
|
+
const { idPrefix, events, ...subscribeOptions } = options;
|
|
238
|
+
const entries = selectCatalogEntries(catalog, events);
|
|
239
|
+
const lookup = handlers;
|
|
240
|
+
const missing = entries.filter((entry) => typeof lookup[entry.handlerName] !== "function");
|
|
241
|
+
if (missing.length > 0) {
|
|
242
|
+
throw new ConfigurationError(
|
|
243
|
+
`Handlers for catalog ${name === void 0 ? "events" : `'${name}'`} lack methods: ${missing.map((entry) => entry.handlerName).join(", ")}`
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
const prefix = idPrefix ?? name ?? classNameOf(handlers);
|
|
247
|
+
if (prefix === void 0) {
|
|
248
|
+
throw new ConfigurationError(
|
|
249
|
+
"Cannot derive handler ids: name the catalog, pass an idPrefix or use a class instance as handlers"
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
return entries.map((entry) => {
|
|
253
|
+
const method = lookup[entry.handlerName];
|
|
254
|
+
return {
|
|
255
|
+
eventClass: entry.eventClass,
|
|
256
|
+
handle: (envelope, context) => method.call(handlers, envelope, context),
|
|
257
|
+
options: { ...subscribeOptions, id: `${prefix}.${entry.handlerName}` }
|
|
258
|
+
};
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
function classNameOf(handlers) {
|
|
262
|
+
const className = handlers.constructor.name;
|
|
263
|
+
return className === "Object" || className === "" ? void 0 : className;
|
|
264
|
+
}
|
|
265
|
+
function assertExactHandlers(catalog, entries, handlers) {
|
|
266
|
+
const expected = new Set(entries.map((entry) => entry.handlerName));
|
|
267
|
+
const present = Object.keys(handlers);
|
|
268
|
+
const missing = [...expected].filter((handlerName) => !present.includes(handlerName));
|
|
269
|
+
const extra = present.filter((key) => !expected.has(key));
|
|
270
|
+
const notFunctions = present.filter(
|
|
271
|
+
(key) => expected.has(key) && typeof handlers[key] !== "function"
|
|
272
|
+
);
|
|
273
|
+
if (missing.length + extra.length + notFunctions.length > 0) {
|
|
274
|
+
const problems = [
|
|
275
|
+
...missing.map((key) => `missing ${key}`),
|
|
276
|
+
...extra.map((key) => `unexpected ${key}`),
|
|
277
|
+
...notFunctions.map((key) => `${key} is not a function`)
|
|
278
|
+
];
|
|
279
|
+
throw new ConfigurationError(
|
|
280
|
+
`Handlers do not match ${catalogLabel(catalog)}: ${problems.join(", ")}`
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function defineHandlers(catalog, handlers, options = {}) {
|
|
285
|
+
assertExactHandlers(catalog, selectCatalogEntries(catalog, options.events), handlers);
|
|
286
|
+
return handlers;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/catalog-publisher.ts
|
|
290
|
+
var CatalogPublisher = class {
|
|
291
|
+
#catalog;
|
|
292
|
+
#target;
|
|
293
|
+
#entries;
|
|
294
|
+
/**
|
|
295
|
+
* Whether the view publishes the event named `eventName`. Its parameter
|
|
296
|
+
* makes a view of some events unassignable to a view of more events, which
|
|
297
|
+
* the bivariant method parameters alone would allow.
|
|
298
|
+
*/
|
|
299
|
+
#publishes;
|
|
300
|
+
/**
|
|
301
|
+
* Throws {@link ConfigurationError} when `catalog` was not created with
|
|
302
|
+
* `defineCatalog` or `options.events` names no event, an unknown event or
|
|
303
|
+
* the same event twice.
|
|
304
|
+
*/
|
|
305
|
+
constructor(catalog, target, options = {}) {
|
|
306
|
+
this.#entries = selectCatalogEntries(catalog, options.events);
|
|
307
|
+
const names = new Set(this.#entries.map((entry) => entry.name));
|
|
308
|
+
this.#publishes = (eventName) => names.has(eventName);
|
|
309
|
+
this.#catalog = catalog;
|
|
310
|
+
this.#target = target;
|
|
311
|
+
}
|
|
312
|
+
/** Same as `bus.publish`, limited to the events of the view. */
|
|
313
|
+
async publish(event, options) {
|
|
314
|
+
this.#assertPublishable(event);
|
|
315
|
+
return this.#target.publish(event, options);
|
|
316
|
+
}
|
|
317
|
+
/** Same as `bus.publishAll`, limited to the events of the view. Nothing is stored unless every event belongs to it. */
|
|
318
|
+
async publishAll(events, options) {
|
|
319
|
+
for (const event of events) {
|
|
320
|
+
this.#assertPublishable(event);
|
|
321
|
+
}
|
|
322
|
+
return this.#target.publishAll(events, options);
|
|
323
|
+
}
|
|
324
|
+
#assertPublishable(event) {
|
|
325
|
+
const { name } = requireCatalogEntry(this.#catalog, event);
|
|
326
|
+
if (!this.#publishes(name)) {
|
|
327
|
+
throw new ConfigurationError(
|
|
328
|
+
`Event type '${event.type}' is not one of the events this view of ${catalogLabel(
|
|
329
|
+
this.#catalog
|
|
330
|
+
)} publishes (${this.#entries.map((entry) => entry.name).join(", ")})`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// src/catalog-coverage.ts
|
|
337
|
+
var declarations = /* @__PURE__ */ new WeakSet();
|
|
338
|
+
function consume(catalog, options = {}) {
|
|
339
|
+
return declare(catalog, "consume", options);
|
|
340
|
+
}
|
|
341
|
+
function produce(catalog, options = {}) {
|
|
342
|
+
return declare(catalog, "produce", options);
|
|
343
|
+
}
|
|
344
|
+
function declare(catalog, role, options) {
|
|
345
|
+
const entries = selectCatalogEntries(catalog, options.events);
|
|
346
|
+
const declaration = Object.freeze({
|
|
347
|
+
catalog,
|
|
348
|
+
role,
|
|
349
|
+
events: Object.freeze(entries.map((entry) => entry.name)),
|
|
350
|
+
entries
|
|
351
|
+
});
|
|
352
|
+
declarations.add(declaration);
|
|
353
|
+
return declaration;
|
|
354
|
+
}
|
|
355
|
+
function toCatalogRequirement(value) {
|
|
356
|
+
if (declarations.has(value)) {
|
|
357
|
+
const declaration = value;
|
|
358
|
+
return {
|
|
359
|
+
catalogName: describeCatalog(declaration.catalog).name,
|
|
360
|
+
entries: declaration.entries,
|
|
361
|
+
need: declaration.role === "consume" ? "handler" : "route"
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
const { name, entries } = describeCatalog(value);
|
|
365
|
+
return { catalogName: name, entries, need: "handler-or-route" };
|
|
366
|
+
}
|
|
367
|
+
function assertCatalogsCovered(requirements, registrationsOf) {
|
|
368
|
+
const problems = /* @__PURE__ */ new Set();
|
|
369
|
+
let onlyBareCatalogs = true;
|
|
370
|
+
for (const { catalogName, entries, need } of requirements) {
|
|
371
|
+
for (const entry of entries) {
|
|
372
|
+
const problem = describeGap(entry, need, registrationsOf(entry.type));
|
|
373
|
+
if (problem !== void 0) {
|
|
374
|
+
problems.add(`${catalogName === void 0 ? "" : `${catalogName}.`}${problem}`);
|
|
375
|
+
onlyBareCatalogs &&= need === "handler-or-route";
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (problems.size > 0) {
|
|
380
|
+
const heading = onlyBareCatalogs ? "Catalog events without a handler or route:" : "Catalog events this service does not cover:";
|
|
381
|
+
throw new ConfigurationError(
|
|
382
|
+
`${heading}
|
|
383
|
+
${[...problems].map((line) => ` - ${line}`).join("\n")}`
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function describeGap(entry, need, { handlers, routes }) {
|
|
388
|
+
const subject = `${entry.name} (${entry.type})`;
|
|
389
|
+
if (need === "handler-or-route") {
|
|
390
|
+
return handlers + routes > 0 ? void 0 : `${subject}: expected handler method ${entry.handlerName}`;
|
|
391
|
+
}
|
|
392
|
+
if (need === "handler") {
|
|
393
|
+
if (handlers > 0) {
|
|
394
|
+
return void 0;
|
|
395
|
+
}
|
|
396
|
+
const routeNote = routes > 0 ? "; its route to a transport does not handle it here" : "";
|
|
397
|
+
return `${subject}: consumed here, needs handler method ${entry.handlerName}${routeNote}`;
|
|
398
|
+
}
|
|
399
|
+
if (routes > 0) {
|
|
400
|
+
return void 0;
|
|
401
|
+
}
|
|
402
|
+
const handlerNote = handlers > 0 ? "; a local handler does not deliver it to other services" : "";
|
|
403
|
+
return `${subject}: produced here, needs a route to a transport${handlerNote}`;
|
|
404
|
+
}
|
|
405
|
+
|
|
142
406
|
// src/event-store.ts
|
|
143
407
|
var DELIVERY_STATUSES = [
|
|
144
408
|
"pending",
|
|
@@ -200,7 +464,9 @@ var InMemoryEventStore = class {
|
|
|
200
464
|
#keySequences = /* @__PURE__ */ new Map();
|
|
201
465
|
/** Row-lock emulation: event ids and ordering keys written by open transactions. */
|
|
202
466
|
#locks = /* @__PURE__ */ new Map();
|
|
467
|
+
#writes = /* @__PURE__ */ new WeakMap();
|
|
203
468
|
#generateIdentifier;
|
|
469
|
+
#retainCompleted;
|
|
204
470
|
constructor(options = {}) {
|
|
205
471
|
if (process.env["NODE_ENV"] === "production" && options.allowInProduction !== true) {
|
|
206
472
|
throw new ConfigurationError(
|
|
@@ -208,6 +474,7 @@ var InMemoryEventStore = class {
|
|
|
208
474
|
);
|
|
209
475
|
}
|
|
210
476
|
this.#generateIdentifier = options.generateIdentifier ?? (() => createUuidV7());
|
|
477
|
+
this.#retainCompleted = options.retainCompleted ?? false;
|
|
211
478
|
}
|
|
212
479
|
async transaction(work) {
|
|
213
480
|
const transaction = new InMemoryTransaction();
|
|
@@ -228,13 +495,15 @@ var InMemoryEventStore = class {
|
|
|
228
495
|
...entry.event.key === null ? [] : [`key:${entry.event.key}`]
|
|
229
496
|
])
|
|
230
497
|
);
|
|
231
|
-
const
|
|
232
|
-
const
|
|
498
|
+
const written = this.#written(transaction);
|
|
499
|
+
const pendingEventIds = written.eventIds;
|
|
500
|
+
const pendingDeliveryKeys = written.deliveryKeys;
|
|
233
501
|
const appendedEventIds = [];
|
|
234
502
|
const duplicateEventIds = [];
|
|
235
503
|
let appendedDeliveryCount = 0;
|
|
236
504
|
for (const entry of entries) {
|
|
237
505
|
const eventId = entry.event.id;
|
|
506
|
+
const serialized = serializeEnvelope(entry.event);
|
|
238
507
|
const isDuplicate = this.#events.has(eventId) || pendingEventIds.has(eventId);
|
|
239
508
|
if (isDuplicate) {
|
|
240
509
|
duplicateEventIds.push(eventId);
|
|
@@ -244,23 +513,26 @@ var InMemoryEventStore = class {
|
|
|
244
513
|
}
|
|
245
514
|
const newDeliveries = entry.deliveries.filter((delivery) => {
|
|
246
515
|
const deliveryKey = `${eventId}\0${delivery.handler}`;
|
|
247
|
-
if (pendingDeliveryKeys.has(deliveryKey) || this.#
|
|
516
|
+
if (pendingDeliveryKeys.has(deliveryKey) || this.#events.get(eventId)?.handlers.has(delivery.handler) === true) {
|
|
248
517
|
return false;
|
|
249
518
|
}
|
|
250
519
|
pendingDeliveryKeys.add(deliveryKey);
|
|
251
520
|
return true;
|
|
252
521
|
});
|
|
253
522
|
appendedDeliveryCount += newDeliveries.length;
|
|
254
|
-
const serialized = serializeEnvelope(entry.event);
|
|
255
523
|
transaction.enqueue(() => {
|
|
256
524
|
if (!isDuplicate) {
|
|
257
525
|
this.#events.set(eventId, {
|
|
258
526
|
envelope: serialized,
|
|
259
|
-
sequence: this.#nextSequence(serialized.key)
|
|
527
|
+
sequence: this.#nextSequence(serialized.key),
|
|
528
|
+
createdAt: now,
|
|
529
|
+
handlers: /* @__PURE__ */ new Set(),
|
|
530
|
+
completedAt: null
|
|
260
531
|
});
|
|
261
532
|
}
|
|
262
533
|
const stored = this.#events.get(eventId);
|
|
263
534
|
for (const delivery of newDeliveries) {
|
|
535
|
+
stored.handlers.add(delivery.handler);
|
|
264
536
|
this.#deliveries.set(delivery.id, {
|
|
265
537
|
id: delivery.id,
|
|
266
538
|
eventId,
|
|
@@ -318,14 +590,8 @@ var InMemoryEventStore = class {
|
|
|
318
590
|
});
|
|
319
591
|
}
|
|
320
592
|
async complete(lease, now, transaction) {
|
|
321
|
-
const finish = (delivery) => {
|
|
322
|
-
delivery.status = "completed";
|
|
323
|
-
delivery.completedAt = now;
|
|
324
|
-
delivery.updatedAt = now;
|
|
325
|
-
clearLease(delivery);
|
|
326
|
-
};
|
|
327
593
|
if (transaction === void 0) {
|
|
328
|
-
return this.#updateLeased(lease, finish);
|
|
594
|
+
return this.#updateLeased(lease, (delivery) => this.#finish(delivery, now));
|
|
329
595
|
}
|
|
330
596
|
if (this.#findLeased(lease) === void 0) {
|
|
331
597
|
return false;
|
|
@@ -333,11 +599,14 @@ var InMemoryEventStore = class {
|
|
|
333
599
|
transaction.enqueue(() => {
|
|
334
600
|
const delivery = this.#findLeased(lease);
|
|
335
601
|
if (delivery !== void 0) {
|
|
336
|
-
finish(delivery);
|
|
602
|
+
this.#finish(delivery, now);
|
|
337
603
|
}
|
|
338
604
|
});
|
|
339
605
|
return true;
|
|
340
606
|
}
|
|
607
|
+
async completeMany(leases, now) {
|
|
608
|
+
return Promise.all(leases.map((lease) => this.complete(lease, now)));
|
|
609
|
+
}
|
|
341
610
|
async reschedule(lease, availableAt, error, now) {
|
|
342
611
|
return this.#updateLeased(lease, (delivery) => {
|
|
343
612
|
delivery.status = "pending";
|
|
@@ -411,9 +680,16 @@ var InMemoryEventStore = class {
|
|
|
411
680
|
const referencedEventIds = new Set(
|
|
412
681
|
[...this.#deliveries.values()].map((delivery) => delivery.eventId)
|
|
413
682
|
);
|
|
683
|
+
const cutoffs = [request.completedBefore, request.deadBefore].filter((cutoff) => cutoff !== void 0).map((cutoff) => cutoff.getTime());
|
|
684
|
+
const orphanCutoff = cutoffs.length === 0 ? Number.NEGATIVE_INFINITY : Math.min(...cutoffs);
|
|
685
|
+
const completedCutoff = request.completedBefore?.getTime() ?? Number.NEGATIVE_INFINITY;
|
|
414
686
|
let deletedEvents = 0;
|
|
415
|
-
for (const eventId of
|
|
416
|
-
if (
|
|
687
|
+
for (const [eventId, event] of [...this.#events]) {
|
|
688
|
+
if (referencedEventIds.has(eventId) || this.#locks.has(`event:${eventId}`)) {
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
const expired = event.completedAt === null ? touchedEventIds.has(eventId) || event.createdAt.getTime() < orphanCutoff : event.completedAt.getTime() < completedCutoff;
|
|
692
|
+
if (expired) {
|
|
417
693
|
this.#events.delete(eventId);
|
|
418
694
|
deletedEvents += 1;
|
|
419
695
|
}
|
|
@@ -444,6 +720,15 @@ var InMemoryEventStore = class {
|
|
|
444
720
|
* until `transaction` finishes, the way a database holds row locks. Sorted
|
|
445
721
|
* acquisition keeps two transactions from waiting on each other.
|
|
446
722
|
*/
|
|
723
|
+
/** The events and deliveries the transaction appended so far, which are not applied before it commits. */
|
|
724
|
+
#written(transaction) {
|
|
725
|
+
let written = this.#writes.get(transaction);
|
|
726
|
+
if (written === void 0) {
|
|
727
|
+
written = { eventIds: /* @__PURE__ */ new Set(), deliveryKeys: /* @__PURE__ */ new Set() };
|
|
728
|
+
this.#writes.set(transaction, written);
|
|
729
|
+
}
|
|
730
|
+
return written;
|
|
731
|
+
}
|
|
447
732
|
async #lock(transaction, names) {
|
|
448
733
|
for (const name of [...new Set(names)].sort()) {
|
|
449
734
|
let held = this.#locks.get(name);
|
|
@@ -465,14 +750,6 @@ var InMemoryEventStore = class {
|
|
|
465
750
|
});
|
|
466
751
|
}
|
|
467
752
|
}
|
|
468
|
-
#hasDelivery(eventId, handler) {
|
|
469
|
-
for (const delivery of this.#deliveries.values()) {
|
|
470
|
-
if (delivery.eventId === eventId && delivery.handler === handler) {
|
|
471
|
-
return true;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
return false;
|
|
475
|
-
}
|
|
476
753
|
#nextSequence(key) {
|
|
477
754
|
if (key === null) {
|
|
478
755
|
return null;
|
|
@@ -503,6 +780,21 @@ var InMemoryEventStore = class {
|
|
|
503
780
|
}
|
|
504
781
|
return false;
|
|
505
782
|
}
|
|
783
|
+
/** Deletes a completed delivery, or marks it completed when a history is kept. */
|
|
784
|
+
#finish(delivery, now) {
|
|
785
|
+
const event = this.#events.get(delivery.eventId);
|
|
786
|
+
if (event.completedAt === null || event.completedAt < now) {
|
|
787
|
+
event.completedAt = now;
|
|
788
|
+
}
|
|
789
|
+
if (!this.#retainCompleted) {
|
|
790
|
+
this.#deliveries.delete(delivery.id);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
delivery.status = "completed";
|
|
794
|
+
delivery.completedAt = now;
|
|
795
|
+
delivery.updatedAt = now;
|
|
796
|
+
clearLease(delivery);
|
|
797
|
+
}
|
|
506
798
|
#findLeased(lease) {
|
|
507
799
|
const delivery = this.#deliveries.get(lease.deliveryId);
|
|
508
800
|
if (delivery?.status === "processing" && delivery.leaseToken === lease.leaseToken) {
|
|
@@ -560,6 +852,66 @@ function isPurgeable(delivery, request) {
|
|
|
560
852
|
}
|
|
561
853
|
return false;
|
|
562
854
|
}
|
|
855
|
+
|
|
856
|
+
// src/completion-batcher.ts
|
|
857
|
+
var CompletionBatcher = class {
|
|
858
|
+
#store;
|
|
859
|
+
#clock;
|
|
860
|
+
#settings;
|
|
861
|
+
#runningCount;
|
|
862
|
+
#pending = [];
|
|
863
|
+
#timer;
|
|
864
|
+
#closing = false;
|
|
865
|
+
/**
|
|
866
|
+
* @param runningCount Number of deliveries the dispatcher is running right
|
|
867
|
+
* now, including the ones waiting in this batcher.
|
|
868
|
+
*/
|
|
869
|
+
constructor(store, clock, settings, runningCount) {
|
|
870
|
+
this.#store = store;
|
|
871
|
+
this.#clock = clock;
|
|
872
|
+
this.#settings = settings;
|
|
873
|
+
this.#runningCount = runningCount;
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Queues a completion. Resolves with whether the lease was still held;
|
|
877
|
+
* rejects with the store error when the batch could not be written.
|
|
878
|
+
*/
|
|
879
|
+
submit(lease) {
|
|
880
|
+
return new Promise((resolve, reject) => {
|
|
881
|
+
this.#pending.push({ lease, resolve, reject });
|
|
882
|
+
if (this.#closing || this.#pending.length >= this.#settings.maxBatchSize || this.#pending.length >= this.#runningCount()) {
|
|
883
|
+
this.#flush();
|
|
884
|
+
} else {
|
|
885
|
+
this.#timer ??= setTimeout(() => this.#flush(), this.#settings.windowMs);
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Shutdown has begun (or ended): while closing, nothing waits for company any more, so the completions
|
|
891
|
+
* already queued are written now and later ones at once.
|
|
892
|
+
*/
|
|
893
|
+
setClosing(closing) {
|
|
894
|
+
this.#closing = closing;
|
|
895
|
+
if (closing && this.#pending.length > 0) {
|
|
896
|
+
this.#flush();
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
#flush() {
|
|
900
|
+
clearTimeout(this.#timer);
|
|
901
|
+
this.#timer = void 0;
|
|
902
|
+
const batch = this.#pending;
|
|
903
|
+
this.#pending = [];
|
|
904
|
+
Promise.resolve().then(
|
|
905
|
+
() => this.#store.completeMany(
|
|
906
|
+
batch.map((entry) => entry.lease),
|
|
907
|
+
this.#clock.now()
|
|
908
|
+
)
|
|
909
|
+
).then(
|
|
910
|
+
(outcomes) => batch.forEach((entry, index) => entry.resolve(outcomes[index])),
|
|
911
|
+
(error) => batch.forEach((entry) => entry.reject(error))
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
};
|
|
563
915
|
var storage = new AsyncLocalStorage();
|
|
564
916
|
function runInScope(scope, work) {
|
|
565
917
|
return storage.run({ ...storage.getStore(), ...scope }, work);
|
|
@@ -648,18 +1000,51 @@ function isRetryableError(policy, error) {
|
|
|
648
1000
|
|
|
649
1001
|
// src/dispatcher.ts
|
|
650
1002
|
var COMPLETION_RETRY_DELAYS_MS = [50, 250, 1e3];
|
|
1003
|
+
var WAKE_COOLDOWN_MS = 5;
|
|
1004
|
+
var QUIET_REFILL_MS = 10;
|
|
1005
|
+
var REFILL_FRACTION = 0.7;
|
|
651
1006
|
var defaultSleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
652
1007
|
var Dispatcher = class {
|
|
653
1008
|
#dependencies;
|
|
654
1009
|
#sleep;
|
|
655
1010
|
#inFlight = /* @__PURE__ */ new Set();
|
|
1011
|
+
#completions;
|
|
1012
|
+
#pending = /* @__PURE__ */ new Map();
|
|
1013
|
+
#recoveryTimer;
|
|
1014
|
+
#recovery;
|
|
656
1015
|
#running = false;
|
|
657
1016
|
#loop;
|
|
658
1017
|
#wakeUp;
|
|
659
|
-
|
|
1018
|
+
/** Free slots the claim the loop was woken for needs at least; undefined while nobody woke it. */
|
|
1019
|
+
#signalledMinimum;
|
|
1020
|
+
/** Free slots a claim needs unless the poll interval or the quiet refill time passed: claims refill in groups. */
|
|
1021
|
+
#refillThreshold;
|
|
1022
|
+
/** Handlers whose last claim got less than it asked for: the store has nothing more for them right now. */
|
|
1023
|
+
#exhaustedHandlers = /* @__PURE__ */ new Set();
|
|
1024
|
+
/** Grows with every wake-up: a claim that started before one must not mark a handler exhausted. */
|
|
1025
|
+
#wakeGeneration = 0;
|
|
1026
|
+
#lastClaimStartedAt = Number.NEGATIVE_INFINITY;
|
|
1027
|
+
#cooldownTimer;
|
|
1028
|
+
#refillScheduled = false;
|
|
1029
|
+
#quietRefillTimer;
|
|
1030
|
+
#unwatch;
|
|
1031
|
+
#watching;
|
|
660
1032
|
constructor(dependencies) {
|
|
661
1033
|
this.#dependencies = dependencies;
|
|
662
1034
|
this.#sleep = dependencies.sleep ?? defaultSleep;
|
|
1035
|
+
const { store, clock, settings } = dependencies;
|
|
1036
|
+
this.#refillThreshold = Math.ceil(
|
|
1037
|
+
Math.min(settings.concurrency, settings.batchSize) * REFILL_FRACTION
|
|
1038
|
+
);
|
|
1039
|
+
const { completionBatchWindowMs: windowMs = 0, completionBatchSize: maxBatchSize = 1 } = settings;
|
|
1040
|
+
if (store.completeMany !== void 0 && windowMs > 0) {
|
|
1041
|
+
this.#completions = new CompletionBatcher(
|
|
1042
|
+
{ completeMany: (leases, now) => store.completeMany(leases, now) },
|
|
1043
|
+
clock,
|
|
1044
|
+
{ windowMs, maxBatchSize },
|
|
1045
|
+
() => this.#inFlight.size
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
663
1048
|
}
|
|
664
1049
|
get isRunning() {
|
|
665
1050
|
return this.#running;
|
|
@@ -667,29 +1052,93 @@ var Dispatcher = class {
|
|
|
667
1052
|
get inFlightCount() {
|
|
668
1053
|
return this.#inFlight.size;
|
|
669
1054
|
}
|
|
1055
|
+
/**
|
|
1056
|
+
* True while the store reports new deliveries to this dispatcher by itself (see
|
|
1057
|
+
* {@link EventStore.watchNewDeliveries}), from every process: a publisher then has no reason to wake it.
|
|
1058
|
+
*/
|
|
1059
|
+
get watchesNewDeliveries() {
|
|
1060
|
+
return this.#unwatch !== void 0;
|
|
1061
|
+
}
|
|
670
1062
|
start() {
|
|
671
1063
|
if (this.#running) {
|
|
672
1064
|
return;
|
|
673
1065
|
}
|
|
674
1066
|
this.#running = true;
|
|
1067
|
+
this.#completions?.setClosing(false);
|
|
1068
|
+
this.#watching = this.#watchNewDeliveries();
|
|
675
1069
|
this.#loop = this.#runLoop();
|
|
676
1070
|
}
|
|
677
|
-
/**
|
|
1071
|
+
/**
|
|
1072
|
+
* Asks for a claim soon, for example right after a publish: at once after a quiet period, otherwise
|
|
1073
|
+
* at the end of a short cooldown that merges the wake-ups arriving meanwhile into one claim.
|
|
1074
|
+
*/
|
|
678
1075
|
wake() {
|
|
679
|
-
this.#
|
|
1076
|
+
this.#exhaustedHandlers.clear();
|
|
1077
|
+
this.#wakeGeneration += 1;
|
|
1078
|
+
const wait = this.#lastClaimStartedAt + WAKE_COOLDOWN_MS - Date.now();
|
|
1079
|
+
if (wait <= 0) {
|
|
1080
|
+
this.#requestClaim();
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
this.#cooldownTimer ??= setTimeout(() => {
|
|
1084
|
+
this.#cooldownTimer = void 0;
|
|
1085
|
+
this.#requestClaim();
|
|
1086
|
+
}, wait);
|
|
1087
|
+
}
|
|
1088
|
+
/** Ends the current wait of the claim loop, or the next one at once, for a claim once `minimumFree` slots are free. */
|
|
1089
|
+
#signal(minimumFree) {
|
|
1090
|
+
this.#signalledMinimum = Math.min(this.#signalledMinimum ?? minimumFree, minimumFree);
|
|
680
1091
|
this.#wakeUp?.();
|
|
681
1092
|
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Starts listening to the store's reports of new deliveries. Polling stays the safety net: a store
|
|
1095
|
+
* that cannot listen, or fails to, only costs latency, so a failure is reported and otherwise ignored.
|
|
1096
|
+
*/
|
|
1097
|
+
async #watchNewDeliveries() {
|
|
1098
|
+
const { store, notifier } = this.#dependencies;
|
|
1099
|
+
try {
|
|
1100
|
+
const unwatch = await store.watchNewDeliveries?.(() => this.wake());
|
|
1101
|
+
if (unwatch !== void 0 && !this.#running) {
|
|
1102
|
+
await unwatch();
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
this.#unwatch = unwatch;
|
|
1106
|
+
if (unwatch !== void 0) {
|
|
1107
|
+
this.wake();
|
|
1108
|
+
}
|
|
1109
|
+
} catch (error) {
|
|
1110
|
+
notifier.failure("watch", error);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
/** Stops listening to the store's reports of new deliveries. */
|
|
1114
|
+
async #stopWatching() {
|
|
1115
|
+
await this.#watching;
|
|
1116
|
+
const unwatch = this.#unwatch;
|
|
1117
|
+
this.#unwatch = void 0;
|
|
1118
|
+
try {
|
|
1119
|
+
await unwatch?.();
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
this.#dependencies.notifier.failure("watch", error);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
682
1124
|
/**
|
|
683
1125
|
* Stops claiming, waits up to the shutdown timeout for running handlers,
|
|
684
1126
|
* then aborts the rest and releases their deliveries for another process.
|
|
685
1127
|
*/
|
|
686
1128
|
async stop() {
|
|
687
1129
|
if (!this.#running) {
|
|
1130
|
+
await this.#closeRecovery(Date.now() + this.#dependencies.settings.shutdownTimeoutMs);
|
|
688
1131
|
return;
|
|
689
1132
|
}
|
|
690
1133
|
this.#running = false;
|
|
691
|
-
this
|
|
1134
|
+
this.#completions?.setClosing(true);
|
|
1135
|
+
clearTimeout(this.#cooldownTimer);
|
|
1136
|
+
this.#cooldownTimer = void 0;
|
|
1137
|
+
clearTimeout(this.#quietRefillTimer);
|
|
1138
|
+
this.#quietRefillTimer = void 0;
|
|
1139
|
+
this.#signal(this.#refillThreshold);
|
|
692
1140
|
const deadline = Date.now() + this.#dependencies.settings.shutdownTimeoutMs;
|
|
1141
|
+
await this.#waitUntil(this.#stopWatching(), deadline);
|
|
693
1142
|
await this.#waitUntil(this.#loop, deadline);
|
|
694
1143
|
const settled = Promise.allSettled([...this.#inFlight].map((delivery) => delivery.settled));
|
|
695
1144
|
if (await this.#waitUntil(settled, deadline) === "timeout") {
|
|
@@ -698,10 +1147,71 @@ var Dispatcher = class {
|
|
|
698
1147
|
}
|
|
699
1148
|
await settled;
|
|
700
1149
|
}
|
|
1150
|
+
await this.#closeRecovery(deadline);
|
|
1151
|
+
await this.#waitUntil(this.#relinquish(), deadline);
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Gives the settlements still pending one last try and forgets them. A pass of the timer may be inside the
|
|
1155
|
+
* store: it ends before the last pass starts. A store that does not answer before the deadline gets no
|
|
1156
|
+
* further call, since it may be closed right after.
|
|
1157
|
+
*/
|
|
1158
|
+
async #closeRecovery(deadline) {
|
|
1159
|
+
clearInterval(this.#recoveryTimer);
|
|
1160
|
+
this.#recoveryTimer = void 0;
|
|
1161
|
+
const lastTry = (async () => {
|
|
1162
|
+
await this.#recovery;
|
|
1163
|
+
await this.#recoverSettlements();
|
|
1164
|
+
})();
|
|
1165
|
+
await this.#waitUntil(lastTry, deadline);
|
|
1166
|
+
this.#pending.clear();
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Retries every pending settlement once; a pass that is already running is the one returned. A store that
|
|
1170
|
+
* still fails leaves the settlement for the next attempt (it was reported when it first failed).
|
|
1171
|
+
*/
|
|
1172
|
+
#recoverSettlements() {
|
|
1173
|
+
this.#recovery ??= this.#recoverAll().finally(() => {
|
|
1174
|
+
this.#recovery = void 0;
|
|
1175
|
+
});
|
|
1176
|
+
return this.#recovery;
|
|
1177
|
+
}
|
|
1178
|
+
async #recoverAll() {
|
|
1179
|
+
for (const [key, entry] of [...this.#pending]) {
|
|
1180
|
+
if (this.#pending.get(key) !== entry) {
|
|
1181
|
+
continue;
|
|
1182
|
+
}
|
|
1183
|
+
try {
|
|
1184
|
+
const applied = await entry.settle();
|
|
1185
|
+
this.#pending.delete(pendingKey(entry.delivery));
|
|
1186
|
+
if (applied) {
|
|
1187
|
+
entry.applied();
|
|
1188
|
+
} else {
|
|
1189
|
+
this.#dependencies.notifier.notify("onLeaseLost", { delivery: entry.delivery });
|
|
1190
|
+
}
|
|
1191
|
+
} catch {
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
#retain(entry) {
|
|
1196
|
+
this.#pending.set(pendingKey(entry.delivery), entry);
|
|
1197
|
+
this.#recoveryTimer ??= setInterval(
|
|
1198
|
+
() => void this.#recoverSettlements(),
|
|
1199
|
+
this.#dependencies.settings.pollIntervalMs
|
|
1200
|
+
);
|
|
1201
|
+
this.#recoveryTimer.unref();
|
|
1202
|
+
}
|
|
1203
|
+
/** Gives lease groups the owner no longer works on back to the store, so other workers need not wait for expiry. */
|
|
1204
|
+
async #relinquish() {
|
|
1205
|
+
const { store, clock, notifier, settings } = this.#dependencies;
|
|
1206
|
+
try {
|
|
1207
|
+
await store.relinquish?.(settings.owner, clock.now());
|
|
1208
|
+
} catch (error) {
|
|
1209
|
+
notifier.failure("relinquish", error);
|
|
1210
|
+
}
|
|
701
1211
|
}
|
|
702
1212
|
/** Claims one batch and resolves when every claimed delivery settled. Returns how many ran. */
|
|
703
1213
|
async processBatch() {
|
|
704
|
-
const started = await this.#claimAndStart();
|
|
1214
|
+
const { started } = await this.#claimAndStart();
|
|
705
1215
|
await Promise.allSettled(started.map((delivery) => delivery.settled));
|
|
706
1216
|
return started.length;
|
|
707
1217
|
}
|
|
@@ -714,44 +1224,102 @@ var Dispatcher = class {
|
|
|
714
1224
|
clearTimeout(timer);
|
|
715
1225
|
return outcome;
|
|
716
1226
|
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Claims until stopped. A claim that filled its batch is followed by the next one at once while enough
|
|
1229
|
+
* slots are free. Otherwise the loop sleeps until a wake-up (a publish, a store notification, or enough
|
|
1230
|
+
* deliveries settling to refill a group of slots) or the poll interval. Only the poll interval claims
|
|
1231
|
+
* for fewer free slots than the refill threshold, so a busy dispatcher never claims slot by slot.
|
|
1232
|
+
*/
|
|
717
1233
|
async #runLoop() {
|
|
1234
|
+
let minimumFree = 1;
|
|
718
1235
|
while (this.#running) {
|
|
719
|
-
let
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
1236
|
+
let round = { requested: 0, claimed: 0 };
|
|
1237
|
+
if (this.#freeSlots() >= minimumFree) {
|
|
1238
|
+
try {
|
|
1239
|
+
round = await this.#claimAndStart();
|
|
1240
|
+
} catch (error) {
|
|
1241
|
+
this.#dependencies.notifier.failure("claim", error);
|
|
1242
|
+
}
|
|
724
1243
|
}
|
|
725
|
-
if (
|
|
1244
|
+
if (round.requested > 0 && round.claimed === round.requested && this.#freeSlots() >= this.#refillThreshold) {
|
|
1245
|
+
minimumFree = this.#refillThreshold;
|
|
726
1246
|
continue;
|
|
727
1247
|
}
|
|
728
|
-
await this.#waitForWakeUp();
|
|
1248
|
+
minimumFree = await this.#waitForWakeUp();
|
|
1249
|
+
if (this.#freeSlots() < minimumFree) {
|
|
1250
|
+
this.#requestClaim();
|
|
1251
|
+
}
|
|
729
1252
|
}
|
|
730
1253
|
}
|
|
1254
|
+
/** Waits for a wake-up or the poll interval. Resolves with the free slots the next claim needs. */
|
|
731
1255
|
async #waitForWakeUp() {
|
|
732
|
-
if (this.#
|
|
733
|
-
|
|
734
|
-
|
|
1256
|
+
if (this.#signalledMinimum === void 0) {
|
|
1257
|
+
await new Promise((resolve) => {
|
|
1258
|
+
const timer = setTimeout(() => {
|
|
1259
|
+
this.#signalledMinimum = 1;
|
|
1260
|
+
resolve();
|
|
1261
|
+
}, this.#dependencies.settings.pollIntervalMs);
|
|
1262
|
+
this.#wakeUp = () => {
|
|
1263
|
+
clearTimeout(timer);
|
|
1264
|
+
resolve();
|
|
1265
|
+
};
|
|
1266
|
+
});
|
|
1267
|
+
this.#wakeUp = void 0;
|
|
1268
|
+
}
|
|
1269
|
+
const minimumFree = this.#signalledMinimum;
|
|
1270
|
+
this.#signalledMinimum = void 0;
|
|
1271
|
+
return minimumFree;
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* A delivery settled. Its slot is worth refilling when the store may have more for its handler: a keyed
|
|
1275
|
+
* delivery unblocks the next one of its key, a delivery that did not succeed may be due again at once
|
|
1276
|
+
* (a retry without delay, a release), and a handler whose last claim was full may have a backlog.
|
|
1277
|
+
*/
|
|
1278
|
+
#onSettled(delivery, succeeded) {
|
|
1279
|
+
if (delivery.event.key !== null || !succeeded || !this.#exhaustedHandlers.has(delivery.handler)) {
|
|
1280
|
+
this.#requestClaim();
|
|
735
1281
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Asks the loop for a claim. Once a group of slots is free the claim waits only for the deliveries
|
|
1285
|
+
* settling in the same tick, so they are claimed for together; fewer free slots are refilled when no
|
|
1286
|
+
* claim started for a short while. With no free slot the next settlement asks again.
|
|
1287
|
+
*/
|
|
1288
|
+
#requestClaim() {
|
|
1289
|
+
const free = this.#freeSlots();
|
|
1290
|
+
if (free >= this.#refillThreshold) {
|
|
1291
|
+
if (!this.#refillScheduled) {
|
|
1292
|
+
this.#refillScheduled = true;
|
|
1293
|
+
setImmediate(() => {
|
|
1294
|
+
this.#refillScheduled = false;
|
|
1295
|
+
this.#signal(this.#refillThreshold);
|
|
1296
|
+
});
|
|
741
1297
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
1298
|
+
} else if (free > 0) {
|
|
1299
|
+
this.#quietRefillTimer ??= setTimeout(
|
|
1300
|
+
() => {
|
|
1301
|
+
this.#quietRefillTimer = void 0;
|
|
1302
|
+
this.#signal(1);
|
|
1303
|
+
},
|
|
1304
|
+
Math.max(0, this.#lastClaimStartedAt + QUIET_REFILL_MS - Date.now())
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
746
1307
|
}
|
|
747
1308
|
#freeSlots() {
|
|
748
1309
|
return this.#dependencies.settings.concurrency - this.#inFlight.size;
|
|
749
1310
|
}
|
|
1311
|
+
/** Claims for every handler with room and starts what was claimed. */
|
|
750
1312
|
async #claimAndStart() {
|
|
751
1313
|
const { store, registry, clock, settings } = this.#dependencies;
|
|
752
1314
|
const started = [];
|
|
1315
|
+
let requested = 0;
|
|
753
1316
|
for (const request of this.#planClaims(registry.all())) {
|
|
754
1317
|
const now = clock.now();
|
|
1318
|
+
const wakeGeneration = this.#wakeGeneration;
|
|
1319
|
+
this.#lastClaimStartedAt = Date.now();
|
|
1320
|
+
clearTimeout(this.#quietRefillTimer);
|
|
1321
|
+
this.#quietRefillTimer = void 0;
|
|
1322
|
+
requested += request.limit;
|
|
755
1323
|
const claimed = await store.claim({
|
|
756
1324
|
handlers: request.handlers,
|
|
757
1325
|
limit: request.limit,
|
|
@@ -759,11 +1327,18 @@ var Dispatcher = class {
|
|
|
759
1327
|
leaseExpiresAt: new Date(now.getTime() + settings.leaseDurationMs),
|
|
760
1328
|
now
|
|
761
1329
|
});
|
|
1330
|
+
for (const handler of request.handlers) {
|
|
1331
|
+
if (claimed.length < request.limit && wakeGeneration === this.#wakeGeneration) {
|
|
1332
|
+
this.#exhaustedHandlers.add(handler);
|
|
1333
|
+
} else {
|
|
1334
|
+
this.#exhaustedHandlers.delete(handler);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
762
1337
|
for (const delivery of claimed) {
|
|
763
1338
|
started.push(this.#start(delivery));
|
|
764
1339
|
}
|
|
765
1340
|
}
|
|
766
|
-
return started;
|
|
1341
|
+
return { requested, claimed: started.length, started };
|
|
767
1342
|
}
|
|
768
1343
|
/** One claim for handlers without a concurrency limit, one per limited handler with room. */
|
|
769
1344
|
#planClaims(registrations) {
|
|
@@ -797,20 +1372,29 @@ var Dispatcher = class {
|
|
|
797
1372
|
}
|
|
798
1373
|
#start(delivery) {
|
|
799
1374
|
const controller = new AbortController();
|
|
1375
|
+
let succeeded = false;
|
|
800
1376
|
const entry = {
|
|
801
1377
|
handler: delivery.handler,
|
|
802
1378
|
controller,
|
|
803
|
-
settled: Promise.resolve().then(
|
|
1379
|
+
settled: Promise.resolve().then(async () => {
|
|
1380
|
+
succeeded = await this.#execute(delivery, controller);
|
|
1381
|
+
}).catch((error) => this.#dependencies.notifier.failure("dispatch", error)).finally(() => {
|
|
804
1382
|
this.#inFlight.delete(entry);
|
|
805
|
-
this
|
|
1383
|
+
this.#onSettled(delivery, succeeded);
|
|
806
1384
|
})
|
|
807
1385
|
};
|
|
808
1386
|
this.#inFlight.add(entry);
|
|
809
1387
|
return entry;
|
|
810
1388
|
}
|
|
1389
|
+
/** Runs one delivery. Resolves true when its handler succeeded, so the delivery is done for good. */
|
|
811
1390
|
async #execute(delivery, controller) {
|
|
812
1391
|
const { registry, notifier, clock } = this.#dependencies;
|
|
813
1392
|
const registration = registry.get(delivery.handler);
|
|
1393
|
+
for (const key of [...this.#pending.keys()].filter(
|
|
1394
|
+
(pending) => pending.startsWith(`${delivery.deliveryId}\0`)
|
|
1395
|
+
)) {
|
|
1396
|
+
this.#pending.delete(key);
|
|
1397
|
+
}
|
|
814
1398
|
notifier.notify("onDeliveryStarted", { delivery });
|
|
815
1399
|
const startedAt = clock.now().getTime();
|
|
816
1400
|
if (delivery.attempt > registration.retry.attempts) {
|
|
@@ -820,26 +1404,35 @@ var Dispatcher = class {
|
|
|
820
1404
|
`Delivery reached attempt ${delivery.attempt} of ${registration.retry.attempts} after its lease expired`
|
|
821
1405
|
)
|
|
822
1406
|
);
|
|
823
|
-
return;
|
|
1407
|
+
return false;
|
|
824
1408
|
}
|
|
825
1409
|
let stopHeartbeat = () => void 0;
|
|
826
1410
|
try {
|
|
827
1411
|
stopHeartbeat = registration.transactional ? await this.#guardTransactionalLease(registration, delivery, controller) : this.#startHeartbeat(delivery, controller);
|
|
828
1412
|
const outcome = await this.#runHandler(registration, delivery, controller);
|
|
1413
|
+
const durationMs = clock.now().getTime() - startedAt;
|
|
829
1414
|
if (outcome === "completed") {
|
|
830
|
-
notifier.notify("onDeliverySucceeded", {
|
|
1415
|
+
notifier.notify("onDeliverySucceeded", { delivery, durationMs });
|
|
1416
|
+
} else {
|
|
1417
|
+
this.#retain({
|
|
831
1418
|
delivery,
|
|
832
|
-
|
|
1419
|
+
settle: () => this.#complete(delivery),
|
|
1420
|
+
applied: () => notifier.notify("onDeliverySucceeded", { delivery, durationMs })
|
|
833
1421
|
});
|
|
834
1422
|
}
|
|
1423
|
+
return true;
|
|
835
1424
|
} catch (error) {
|
|
836
1425
|
if (error instanceof LeaseLostError) {
|
|
837
1426
|
notifier.notify("onLeaseLost", { delivery });
|
|
1427
|
+
if (!error.storeConfirmed) {
|
|
1428
|
+
await this.#giveBack(registration, delivery, error);
|
|
1429
|
+
}
|
|
838
1430
|
} else if (error instanceof DispatcherStoppedError) {
|
|
839
1431
|
await this.#release(delivery);
|
|
840
1432
|
} else {
|
|
841
1433
|
await this.#fail(registration, delivery, error);
|
|
842
1434
|
}
|
|
1435
|
+
return false;
|
|
843
1436
|
} finally {
|
|
844
1437
|
stopHeartbeat();
|
|
845
1438
|
}
|
|
@@ -856,7 +1449,9 @@ var Dispatcher = class {
|
|
|
856
1449
|
await store.transaction(async (transaction) => {
|
|
857
1450
|
await this.#invoke(registration, event, delivery, controller, transaction);
|
|
858
1451
|
if (!await store.complete(delivery, clock.now(), transaction)) {
|
|
859
|
-
throw new LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost
|
|
1452
|
+
throw new LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost`, {
|
|
1453
|
+
storeConfirmed: true
|
|
1454
|
+
});
|
|
860
1455
|
}
|
|
861
1456
|
});
|
|
862
1457
|
return "completed";
|
|
@@ -869,11 +1464,11 @@ var Dispatcher = class {
|
|
|
869
1464
|
* still held. Gives up to lease expiry, which reruns the handler: at-least-once.
|
|
870
1465
|
*/
|
|
871
1466
|
async #confirmCompletion(delivery) {
|
|
872
|
-
const {
|
|
1467
|
+
const { notifier } = this.#dependencies;
|
|
873
1468
|
for (let attempt = 0; ; attempt += 1) {
|
|
874
1469
|
let completed;
|
|
875
1470
|
try {
|
|
876
|
-
completed = await
|
|
1471
|
+
completed = await this.#complete(delivery);
|
|
877
1472
|
} catch (error) {
|
|
878
1473
|
const delay = COMPLETION_RETRY_DELAYS_MS[attempt];
|
|
879
1474
|
if (delay === void 0) {
|
|
@@ -884,11 +1479,18 @@ var Dispatcher = class {
|
|
|
884
1479
|
continue;
|
|
885
1480
|
}
|
|
886
1481
|
if (!completed) {
|
|
887
|
-
throw new LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost
|
|
1482
|
+
throw new LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost`, {
|
|
1483
|
+
storeConfirmed: true
|
|
1484
|
+
});
|
|
888
1485
|
}
|
|
889
1486
|
return "completed";
|
|
890
1487
|
}
|
|
891
1488
|
}
|
|
1489
|
+
/** Records one completion, grouped with its neighbours when the store can complete many at once. */
|
|
1490
|
+
#complete(delivery) {
|
|
1491
|
+
const { store, clock } = this.#dependencies;
|
|
1492
|
+
return this.#completions === void 0 ? store.complete(delivery, clock.now()) : this.#completions.submit(delivery);
|
|
1493
|
+
}
|
|
892
1494
|
async #invoke(registration, event, delivery, controller, transaction) {
|
|
893
1495
|
const context = {
|
|
894
1496
|
deliveryId: delivery.deliveryId,
|
|
@@ -962,7 +1564,9 @@ var Dispatcher = class {
|
|
|
962
1564
|
if (registration.timeoutMs !== null && now.getTime() + registration.timeoutMs >= expiry) {
|
|
963
1565
|
expiry = now.getTime() + registration.timeoutMs + settings.leaseDurationMs;
|
|
964
1566
|
if (!await store.extendLease(delivery, new Date(expiry), now)) {
|
|
965
|
-
throw new LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost
|
|
1567
|
+
throw new LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost`, {
|
|
1568
|
+
storeConfirmed: true
|
|
1569
|
+
});
|
|
966
1570
|
}
|
|
967
1571
|
}
|
|
968
1572
|
const timer = setTimeout(
|
|
@@ -983,8 +1587,10 @@ var Dispatcher = class {
|
|
|
983
1587
|
const { store, clock, notifier, settings } = this.#dependencies;
|
|
984
1588
|
let confirmedExpiry = delivery.leaseExpiresAt.getTime();
|
|
985
1589
|
let renewing = false;
|
|
986
|
-
const abortLeaseLost = () => {
|
|
987
|
-
controller.abort(
|
|
1590
|
+
const abortLeaseLost = (storeConfirmed = false) => {
|
|
1591
|
+
controller.abort(
|
|
1592
|
+
new LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost`, { storeConfirmed })
|
|
1593
|
+
);
|
|
988
1594
|
};
|
|
989
1595
|
const timer = setInterval(
|
|
990
1596
|
() => {
|
|
@@ -1002,7 +1608,7 @@ var Dispatcher = class {
|
|
|
1002
1608
|
if (extended) {
|
|
1003
1609
|
confirmedExpiry = expiry;
|
|
1004
1610
|
} else {
|
|
1005
|
-
abortLeaseLost();
|
|
1611
|
+
abortLeaseLost(true);
|
|
1006
1612
|
}
|
|
1007
1613
|
}).catch((error) => notifier.failure("extend-lease", error)).finally(() => {
|
|
1008
1614
|
renewing = false;
|
|
@@ -1014,12 +1620,17 @@ var Dispatcher = class {
|
|
|
1014
1620
|
return () => clearInterval(timer);
|
|
1015
1621
|
}
|
|
1016
1622
|
async #fail(registration, delivery, error) {
|
|
1017
|
-
const { store, clock, random, notifier } = this.#dependencies;
|
|
1018
1623
|
const policy = registration.retry;
|
|
1019
1624
|
if (delivery.attempt >= policy.attempts || !isRetryableError(policy, error)) {
|
|
1020
1625
|
await this.#bury(delivery, error);
|
|
1021
1626
|
return;
|
|
1022
1627
|
}
|
|
1628
|
+
await this.#reschedule(registration, delivery, error);
|
|
1629
|
+
}
|
|
1630
|
+
/** Schedules the next attempt with the retry policy's backoff. */
|
|
1631
|
+
async #reschedule(registration, delivery, error) {
|
|
1632
|
+
const { store, clock, random, notifier } = this.#dependencies;
|
|
1633
|
+
const policy = registration.retry;
|
|
1023
1634
|
const errorInfo = toErrorInfo(error);
|
|
1024
1635
|
const now = clock.now();
|
|
1025
1636
|
const availableAt = new Date(
|
|
@@ -1030,6 +1641,11 @@ var Dispatcher = class {
|
|
|
1030
1641
|
rescheduled = await store.reschedule(delivery, availableAt, errorInfo, now);
|
|
1031
1642
|
} catch (storeError) {
|
|
1032
1643
|
notifier.failure("reschedule", storeError);
|
|
1644
|
+
this.#retain({
|
|
1645
|
+
delivery,
|
|
1646
|
+
settle: () => store.reschedule(delivery, availableAt, errorInfo, now),
|
|
1647
|
+
applied: () => notifier.notify("onDeliveryRetryScheduled", { delivery, error: errorInfo, availableAt })
|
|
1648
|
+
});
|
|
1033
1649
|
return;
|
|
1034
1650
|
}
|
|
1035
1651
|
if (rescheduled) {
|
|
@@ -1046,6 +1662,11 @@ var Dispatcher = class {
|
|
|
1046
1662
|
buried = await store.bury(delivery, errorInfo, clock.now());
|
|
1047
1663
|
} catch (storeError) {
|
|
1048
1664
|
notifier.failure("bury", storeError);
|
|
1665
|
+
this.#retain({
|
|
1666
|
+
delivery,
|
|
1667
|
+
settle: () => store.bury(delivery, errorInfo, clock.now()),
|
|
1668
|
+
applied: () => notifier.notify("onDeliveryDead", { delivery, error: errorInfo })
|
|
1669
|
+
});
|
|
1049
1670
|
return;
|
|
1050
1671
|
}
|
|
1051
1672
|
if (buried) {
|
|
@@ -1054,6 +1675,21 @@ var Dispatcher = class {
|
|
|
1054
1675
|
notifier.notify("onLeaseLost", { delivery });
|
|
1055
1676
|
}
|
|
1056
1677
|
}
|
|
1678
|
+
/**
|
|
1679
|
+
* A worker that only noticed locally that its lease ran out may still own the delivery in the
|
|
1680
|
+
* store, where other deliveries of its lane keep renewing the lane: it must not stay in flight
|
|
1681
|
+
* for ever. The attempt took longer than its lease, so it counts as a failed attempt (an
|
|
1682
|
+
* exhausted delivery is dead-lettered, whatever a custom `retryIf` says): a handler that always
|
|
1683
|
+
* outlives its lease cannot run for ever at attempt 1. If the lease is gone for real the store
|
|
1684
|
+
* refuses and nothing changes. The lost lease was reported already.
|
|
1685
|
+
*/
|
|
1686
|
+
async #giveBack(registration, delivery, error) {
|
|
1687
|
+
if (delivery.attempt >= registration.retry.attempts) {
|
|
1688
|
+
await this.#bury(delivery, error);
|
|
1689
|
+
} else {
|
|
1690
|
+
await this.#reschedule(registration, delivery, error);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1057
1693
|
async #release(delivery) {
|
|
1058
1694
|
const { store, clock, notifier } = this.#dependencies;
|
|
1059
1695
|
let released;
|
|
@@ -1061,6 +1697,11 @@ var Dispatcher = class {
|
|
|
1061
1697
|
released = await store.release(delivery, clock.now());
|
|
1062
1698
|
} catch (storeError) {
|
|
1063
1699
|
notifier.failure("release", storeError);
|
|
1700
|
+
this.#retain({
|
|
1701
|
+
delivery,
|
|
1702
|
+
settle: () => store.release(delivery, clock.now()),
|
|
1703
|
+
applied: () => notifier.notify("onDeliveryReleased", { delivery })
|
|
1704
|
+
});
|
|
1064
1705
|
return;
|
|
1065
1706
|
}
|
|
1066
1707
|
if (released) {
|
|
@@ -1070,6 +1711,9 @@ var Dispatcher = class {
|
|
|
1070
1711
|
}
|
|
1071
1712
|
}
|
|
1072
1713
|
};
|
|
1714
|
+
function pendingKey(delivery) {
|
|
1715
|
+
return `${delivery.deliveryId}\0${delivery.leaseToken}`;
|
|
1716
|
+
}
|
|
1073
1717
|
function rejectOnAbort(signal) {
|
|
1074
1718
|
return new Promise((_, reject) => {
|
|
1075
1719
|
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
@@ -1194,6 +1838,8 @@ var DEFAULTS = {
|
|
|
1194
1838
|
pollInterval: "1s",
|
|
1195
1839
|
leaseDuration: "30s",
|
|
1196
1840
|
shutdownTimeout: "30s",
|
|
1841
|
+
completionBatchWindow: "2ms",
|
|
1842
|
+
completionBatchSize: 200,
|
|
1197
1843
|
completedRetention: "7d",
|
|
1198
1844
|
retentionInterval: "1h",
|
|
1199
1845
|
routeTimeoutMs: 3e4
|
|
@@ -1216,6 +1862,7 @@ var RelayBus = class {
|
|
|
1216
1862
|
#generateIdentifier;
|
|
1217
1863
|
#dispatcher;
|
|
1218
1864
|
#dispatcherEnabled;
|
|
1865
|
+
#catalogRequirements = [];
|
|
1219
1866
|
#setupStore;
|
|
1220
1867
|
#retention;
|
|
1221
1868
|
#retentionTimer;
|
|
@@ -1262,6 +1909,9 @@ var RelayBus = class {
|
|
|
1262
1909
|
throw new ConfigurationError(`Transport '${transport.name}' cannot subscribe`);
|
|
1263
1910
|
}
|
|
1264
1911
|
}
|
|
1912
|
+
for (const catalog of options.catalogs ?? []) {
|
|
1913
|
+
this.registerCatalog(catalog);
|
|
1914
|
+
}
|
|
1265
1915
|
this.#dispatcherEnabled = options.dispatcher?.enabled ?? true;
|
|
1266
1916
|
this.#dispatcher = new Dispatcher({
|
|
1267
1917
|
store: this.#store,
|
|
@@ -1303,6 +1953,28 @@ var RelayBus = class {
|
|
|
1303
1953
|
this.#defaults
|
|
1304
1954
|
);
|
|
1305
1955
|
}
|
|
1956
|
+
subscribeCatalog(catalog, handlers, options = {}) {
|
|
1957
|
+
for (const binding of resolveCatalogHandlers(catalog, handlers, options)) {
|
|
1958
|
+
this.subscribe(binding.eventClass, binding.handle, binding.options);
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
/**
|
|
1962
|
+
* Adds a catalog, or a `consume` / `produce` declaration, to the checks
|
|
1963
|
+
* `start()` runs; `start()` throws a {@link ConfigurationError} naming each
|
|
1964
|
+
* event that is not covered and what it lacks. Equivalent to the `catalogs`
|
|
1965
|
+
* option.
|
|
1966
|
+
*/
|
|
1967
|
+
registerCatalog(catalog) {
|
|
1968
|
+
this.#catalogRequirements.push(toCatalogRequirement(catalog));
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* A view of this bus that publishes only the events of `catalog`, or only
|
|
1972
|
+
* the events named in `options.events` (for example the events this service
|
|
1973
|
+
* produces). Other events fail to compile and are rejected at runtime.
|
|
1974
|
+
*/
|
|
1975
|
+
forCatalog(catalog, options = {}) {
|
|
1976
|
+
return new CatalogPublisher(catalog, this, options);
|
|
1977
|
+
}
|
|
1306
1978
|
/** Registered handlers and routes, for diagnostics. */
|
|
1307
1979
|
handlers() {
|
|
1308
1980
|
return this.#registry.all().map(toDescriptor);
|
|
@@ -1368,6 +2040,7 @@ var RelayBus = class {
|
|
|
1368
2040
|
this.#state === "stopped" ? "A stopped relaybus cannot be started again; create a new instance" : `Cannot start a relaybus that is ${this.#state}`
|
|
1369
2041
|
);
|
|
1370
2042
|
}
|
|
2043
|
+
assertCatalogsCovered(this.#catalogRequirements, (type) => this.#registrationsOf(type));
|
|
1371
2044
|
this.#state = "starting";
|
|
1372
2045
|
this.#starting = this.#startComponents();
|
|
1373
2046
|
try {
|
|
@@ -1387,6 +2060,10 @@ var RelayBus = class {
|
|
|
1387
2060
|
if (this.#state === "starting") {
|
|
1388
2061
|
await this.#starting.catch(() => void 0);
|
|
1389
2062
|
}
|
|
2063
|
+
if (this.#state === "idle") {
|
|
2064
|
+
await this.#dispatcher.stop();
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
1390
2067
|
if (this.#state !== "running") {
|
|
1391
2068
|
return;
|
|
1392
2069
|
}
|
|
@@ -1579,6 +2256,11 @@ var RelayBus = class {
|
|
|
1579
2256
|
#createDelivery(registration, availableAt) {
|
|
1580
2257
|
return { id: this.#generateIdentifier(), handler: registration.id, availableAt };
|
|
1581
2258
|
}
|
|
2259
|
+
#registrationsOf(eventType) {
|
|
2260
|
+
const registrations = this.#registry.forEventType(eventType);
|
|
2261
|
+
const routes = registrations.filter((registration) => registration.forwardsToTransport).length;
|
|
2262
|
+
return { handlers: registrations.length - routes, routes };
|
|
2263
|
+
}
|
|
1582
2264
|
#registerRoute(route) {
|
|
1583
2265
|
const transport = this.#requireTransport(route.transport);
|
|
1584
2266
|
const { type } = describeEvent(route.event);
|
|
@@ -1606,7 +2288,7 @@ var RelayBus = class {
|
|
|
1606
2288
|
return transport;
|
|
1607
2289
|
}
|
|
1608
2290
|
#scheduleWake() {
|
|
1609
|
-
if (this.#state !== "running") {
|
|
2291
|
+
if (this.#state !== "running" || this.#dispatcher.watchesNewDeliveries) {
|
|
1610
2292
|
return;
|
|
1611
2293
|
}
|
|
1612
2294
|
setTimeout(() => this.#dispatcher.wake(), WAKE_DELAY_MS).unref();
|
|
@@ -1711,6 +2393,14 @@ function createDispatcherSettings(options, generateIdentifier) {
|
|
|
1711
2393
|
shutdownTimeoutMs: toTimerMilliseconds(
|
|
1712
2394
|
options?.shutdownTimeout ?? DEFAULTS.shutdownTimeout,
|
|
1713
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"
|
|
1714
2404
|
)
|
|
1715
2405
|
};
|
|
1716
2406
|
}
|
|
@@ -1727,6 +2417,6 @@ function createRetentionSettings(options) {
|
|
|
1727
2417
|
};
|
|
1728
2418
|
}
|
|
1729
2419
|
|
|
1730
|
-
export { ConfigurationError, DEFAULT_RETRY_POLICY, DEFAULT_ROUTE_RETRY_POLICY, DELIVERY_STATUSES, DispatcherStoppedError, EventValidationError, HandlerTimeoutError, InMemoryEventStore, InMemoryTransaction, InvalidEnvelopeError, LeaseLostError, MAX_TIMER_DELAY_MS, NonRetryableError, RelayBus, RelayBusError, TransactionRequiredError, assertEventType, computeRetryDelay, createRetryPolicy, createUuidV7, defineEvent, isRetryableError, systemClock, toErrorInfo, toMilliseconds, validatePayload };
|
|
2420
|
+
export { CatalogPublisher, ConfigurationError, DEFAULT_RETRY_POLICY, DEFAULT_ROUTE_RETRY_POLICY, DELIVERY_STATUSES, DispatcherStoppedError, EventValidationError, HandlerTimeoutError, InMemoryEventStore, InMemoryTransaction, InvalidEnvelopeError, LeaseLostError, MAX_TIMER_DELAY_MS, NonRetryableError, RelayBus, RelayBusError, TransactionRequiredError, assertEventType, computeRetryDelay, consume, createRetryPolicy, createUuidV7, defineCatalog, defineEvent, defineHandlers, describeCatalog, isRetryableError, produce, systemClock, toErrorInfo, toMilliseconds, validatePayload };
|
|
1731
2421
|
//# sourceMappingURL=index.js.map
|
|
1732
2422
|
//# sourceMappingURL=index.js.map
|