@relaybus/core 0.1.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 +302 -25
- 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 +2183 -671
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +871 -247
- package/dist/index.d.ts +871 -247
- package/dist/index.js +2054 -605
- 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 +37 -8
package/dist/index.cjs
CHANGED
|
@@ -1,65 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var chunk3OHMEMZA_cjs = require('./chunk-3OHMEMZA.cjs');
|
|
3
4
|
var crypto = require('crypto');
|
|
4
|
-
var os = require('os');
|
|
5
5
|
var async_hooks = require('async_hooks');
|
|
6
|
+
var os = require('os');
|
|
6
7
|
|
|
7
8
|
// src/clock.ts
|
|
8
9
|
var systemClock = {
|
|
9
10
|
now: () => /* @__PURE__ */ new Date()
|
|
10
11
|
};
|
|
11
12
|
|
|
12
|
-
// src/errors.ts
|
|
13
|
-
var RelayBusError = class extends Error {
|
|
14
|
-
// Explicit names survive minification, unlike `new.target.name`.
|
|
15
|
-
name = "RelayBusError";
|
|
16
|
-
};
|
|
17
|
-
var ConfigurationError = class extends RelayBusError {
|
|
18
|
-
name = "ConfigurationError";
|
|
19
|
-
};
|
|
20
|
-
var TransactionRequiredError = class extends RelayBusError {
|
|
21
|
-
name = "TransactionRequiredError";
|
|
22
|
-
};
|
|
23
|
-
var NonRetryableError = class extends RelayBusError {
|
|
24
|
-
name = "NonRetryableError";
|
|
25
|
-
};
|
|
26
|
-
var EventValidationError = class extends NonRetryableError {
|
|
27
|
-
constructor(message, issues) {
|
|
28
|
-
super(message);
|
|
29
|
-
this.issues = issues;
|
|
30
|
-
}
|
|
31
|
-
issues;
|
|
32
|
-
name = "EventValidationError";
|
|
33
|
-
};
|
|
34
|
-
var InvalidEnvelopeError = class extends NonRetryableError {
|
|
35
|
-
name = "InvalidEnvelopeError";
|
|
36
|
-
};
|
|
37
|
-
var LeaseLostError = class extends RelayBusError {
|
|
38
|
-
name = "LeaseLostError";
|
|
39
|
-
};
|
|
40
|
-
var HandlerTimeoutError = class extends RelayBusError {
|
|
41
|
-
name = "HandlerTimeoutError";
|
|
42
|
-
};
|
|
43
|
-
var DispatcherStoppedError = class extends RelayBusError {
|
|
44
|
-
name = "DispatcherStoppedError";
|
|
45
|
-
};
|
|
46
|
-
function toErrorInfo(error) {
|
|
47
|
-
if (error instanceof Error) {
|
|
48
|
-
return { name: error.name, message: error.message, stack: error.stack };
|
|
49
|
-
}
|
|
50
|
-
return { name: "NonError", message: safeStringify(error) };
|
|
51
|
-
}
|
|
52
|
-
function safeStringify(value) {
|
|
53
|
-
if (typeof value === "string") {
|
|
54
|
-
return value;
|
|
55
|
-
}
|
|
56
|
-
try {
|
|
57
|
-
return JSON.stringify(value) ?? String(value);
|
|
58
|
-
} catch {
|
|
59
|
-
return String(value);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
13
|
// src/duration.ts
|
|
64
14
|
var MILLISECONDS_PER_UNIT = {
|
|
65
15
|
ms: 1,
|
|
@@ -72,7 +22,7 @@ var DURATION_PATTERN = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/;
|
|
|
72
22
|
function toMilliseconds(value, name) {
|
|
73
23
|
if (typeof value === "number") {
|
|
74
24
|
if (!Number.isFinite(value) || value < 0) {
|
|
75
|
-
throw new ConfigurationError(
|
|
25
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
76
26
|
`${name} must be a non-negative finite number of milliseconds, received ${value}`
|
|
77
27
|
);
|
|
78
28
|
}
|
|
@@ -80,7 +30,7 @@ function toMilliseconds(value, name) {
|
|
|
80
30
|
}
|
|
81
31
|
const match = DURATION_PATTERN.exec(value);
|
|
82
32
|
if (match === null) {
|
|
83
|
-
throw new ConfigurationError(
|
|
33
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
84
34
|
`${name} must look like '500ms', '30s', '5m', '1h' or '7d', received '${value}'`
|
|
85
35
|
);
|
|
86
36
|
}
|
|
@@ -91,54 +41,278 @@ var MAX_TIMER_DELAY_MS = 2147483647;
|
|
|
91
41
|
function toTimerMilliseconds(value, name) {
|
|
92
42
|
const milliseconds = toMilliseconds(value, name);
|
|
93
43
|
if (milliseconds === 0) {
|
|
94
|
-
throw new ConfigurationError(`${name} must be greater than zero`);
|
|
44
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(`${name} must be greater than zero`);
|
|
95
45
|
}
|
|
96
46
|
if (milliseconds > MAX_TIMER_DELAY_MS) {
|
|
97
|
-
throw new ConfigurationError(
|
|
47
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
98
48
|
`${name} must not exceed ${MAX_TIMER_DELAY_MS}ms (about 24.8 days), received ${milliseconds}ms`
|
|
99
49
|
);
|
|
100
50
|
}
|
|
101
51
|
return milliseconds;
|
|
102
52
|
}
|
|
103
53
|
|
|
104
|
-
// src/
|
|
105
|
-
var
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
54
|
+
// src/catalog.ts
|
|
55
|
+
var EVENT_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
56
|
+
var descriptions = /* @__PURE__ */ new WeakMap();
|
|
57
|
+
function defineCatalog(events, options = {}) {
|
|
58
|
+
const label = options.name === void 0 ? "Catalog" : `Catalog '${options.name}'`;
|
|
59
|
+
const names = Object.keys(events);
|
|
60
|
+
if (names.length === 0) {
|
|
61
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(`${label} must contain at least one event`);
|
|
62
|
+
}
|
|
63
|
+
const owners = /* @__PURE__ */ new Map();
|
|
64
|
+
const entries = names.map((name) => {
|
|
65
|
+
const eventClass = events[name];
|
|
66
|
+
if (!EVENT_NAME_PATTERN.test(name)) {
|
|
67
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
68
|
+
`${label} entry '${name}' must be a valid identifier made of letters, digits and underscores because it names the handler method on${name} and its handler id`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
if (typeof eventClass !== "function" || typeof eventClass.type !== "string") {
|
|
72
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
73
|
+
`${label} entry '${name}' must be an event class created with defineEvent`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
chunk3OHMEMZA_cjs.assertEventType(eventClass.type);
|
|
77
|
+
const owner = owners.get(eventClass.type);
|
|
78
|
+
if (owner !== void 0) {
|
|
79
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
80
|
+
`${label} entries '${owner}' and '${name}' share the event type '${eventClass.type}'`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
owners.set(eventClass.type, name);
|
|
84
|
+
return Object.freeze({ name, eventClass, type: eventClass.type, handlerName: `on${name}` });
|
|
85
|
+
});
|
|
86
|
+
const catalog = Object.freeze({ ...events });
|
|
87
|
+
descriptions.set(catalog, Object.freeze({ name: options.name, entries: Object.freeze(entries) }));
|
|
88
|
+
return catalog;
|
|
89
|
+
}
|
|
90
|
+
function describeCatalog(catalog) {
|
|
91
|
+
const description = descriptions.get(catalog);
|
|
92
|
+
if (description === void 0) {
|
|
93
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError("Expected a catalog created with defineCatalog");
|
|
94
|
+
}
|
|
95
|
+
return description;
|
|
96
|
+
}
|
|
97
|
+
function catalogLabel(catalog) {
|
|
98
|
+
const { name } = describeCatalog(catalog);
|
|
99
|
+
return name === void 0 ? "the catalog" : `catalog '${name}'`;
|
|
100
|
+
}
|
|
101
|
+
function selectCatalogEntries(catalog, events) {
|
|
102
|
+
const { entries } = describeCatalog(catalog);
|
|
103
|
+
if (events === void 0) {
|
|
104
|
+
return entries;
|
|
105
|
+
}
|
|
106
|
+
const label = catalogLabel(catalog);
|
|
107
|
+
if (events.length === 0) {
|
|
108
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
109
|
+
`The events selected from ${label} must name at least one event; omit events to select all of them`
|
|
110
110
|
);
|
|
111
111
|
}
|
|
112
|
+
const selected = /* @__PURE__ */ new Set();
|
|
113
|
+
for (const event of events) {
|
|
114
|
+
if (!entries.some((entry) => entry.name === event)) {
|
|
115
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
116
|
+
`${capitalize(label)} has no event named '${String(event)}'; its events are ${entries.map((entry) => entry.name).join(", ")}`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
if (selected.has(event)) {
|
|
120
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(`Event '${String(event)}' of ${label} is selected twice`);
|
|
121
|
+
}
|
|
122
|
+
selected.add(event);
|
|
123
|
+
}
|
|
124
|
+
return entries.filter((entry) => selected.has(entry.name));
|
|
125
|
+
}
|
|
126
|
+
function capitalize(text) {
|
|
127
|
+
return `${text.charAt(0).toUpperCase()}${text.slice(1)}`;
|
|
128
|
+
}
|
|
129
|
+
function requireCatalogEntry(catalog, event) {
|
|
130
|
+
const entry = describeCatalog(catalog).entries.find((candidate) => candidate.type === event.type);
|
|
131
|
+
if (entry === void 0) {
|
|
132
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
133
|
+
`Event type '${String(event.type)}' is not part of ${catalogLabel(catalog)}`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
return entry;
|
|
137
|
+
}
|
|
138
|
+
function resolveCatalogHandlers(catalog, {
|
|
139
|
+
handlers,
|
|
140
|
+
options
|
|
141
|
+
}) {
|
|
142
|
+
const { name } = describeCatalog(catalog);
|
|
143
|
+
const { idPrefix, events, ...subscribeOptions } = options;
|
|
144
|
+
const entries = selectCatalogEntries(catalog, events);
|
|
145
|
+
const lookup = handlers;
|
|
146
|
+
const missing = entries.filter((entry) => typeof lookup[entry.handlerName] !== "function");
|
|
147
|
+
if (missing.length > 0) {
|
|
148
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
149
|
+
`Handlers for catalog ${name === void 0 ? "events" : `'${name}'`} lack methods: ${missing.map((entry) => entry.handlerName).join(", ")}`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
const prefix = idPrefix ?? name ?? classNameOf(handlers);
|
|
153
|
+
if (prefix === void 0) {
|
|
154
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
155
|
+
"Cannot derive handler ids: name the catalog, pass an idPrefix or use a class instance as handlers"
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
return entries.map((entry) => {
|
|
159
|
+
const method = lookup[entry.handlerName];
|
|
160
|
+
return {
|
|
161
|
+
eventClass: entry.eventClass,
|
|
162
|
+
handle: (envelope, context) => method.call(handlers, envelope, context),
|
|
163
|
+
options: { ...subscribeOptions, id: `${prefix}.${entry.handlerName}` }
|
|
164
|
+
};
|
|
165
|
+
});
|
|
112
166
|
}
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
167
|
+
function classNameOf(handlers) {
|
|
168
|
+
const className = handlers.constructor.name;
|
|
169
|
+
return className === "Object" || className === "" ? void 0 : className;
|
|
170
|
+
}
|
|
171
|
+
function assertExactHandlers(catalog, { entries, handlers }) {
|
|
172
|
+
const expected = new Set(entries.map((entry) => entry.handlerName));
|
|
173
|
+
const present = Object.keys(handlers);
|
|
174
|
+
const missing = [...expected].filter((handlerName) => !present.includes(handlerName));
|
|
175
|
+
const extra = present.filter((key) => !expected.has(key));
|
|
176
|
+
const notFunctions = present.filter(
|
|
177
|
+
(key) => expected.has(key) && typeof handlers[key] !== "function"
|
|
178
|
+
);
|
|
179
|
+
if (missing.length + extra.length + notFunctions.length > 0) {
|
|
180
|
+
const problems = [
|
|
181
|
+
...missing.map((key) => `missing ${key}`),
|
|
182
|
+
...extra.map((key) => `unexpected ${key}`),
|
|
183
|
+
...notFunctions.map((key) => `${key} is not a function`)
|
|
184
|
+
];
|
|
185
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
186
|
+
`Handlers do not match ${catalogLabel(catalog)}: ${problems.join(", ")}`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function defineHandlers(catalog, handlers, options = {}) {
|
|
191
|
+
assertExactHandlers(catalog, {
|
|
192
|
+
entries: selectCatalogEntries(catalog, options.events),
|
|
193
|
+
handlers
|
|
194
|
+
});
|
|
195
|
+
return handlers;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/catalog-publisher.ts
|
|
199
|
+
var CatalogPublisher = class {
|
|
200
|
+
#catalog;
|
|
201
|
+
#target;
|
|
202
|
+
#entries;
|
|
203
|
+
/**
|
|
204
|
+
* Whether the view publishes the event named `eventName`. Its parameter
|
|
205
|
+
* makes a view of some events unassignable to a view of more events, which
|
|
206
|
+
* the bivariant method parameters alone would allow.
|
|
207
|
+
*/
|
|
208
|
+
#publishes;
|
|
209
|
+
/**
|
|
210
|
+
* Throws {@link ConfigurationError} when `catalog` was not created with
|
|
211
|
+
* `defineCatalog` or `options.events` names no event, an unknown event or
|
|
212
|
+
* the same event twice.
|
|
213
|
+
*/
|
|
214
|
+
constructor(catalog, target, options = {}) {
|
|
215
|
+
this.#entries = selectCatalogEntries(catalog, options.events);
|
|
216
|
+
const names = new Set(this.#entries.map((entry) => entry.name));
|
|
217
|
+
this.#publishes = (eventName) => names.has(eventName);
|
|
218
|
+
this.#catalog = catalog;
|
|
219
|
+
this.#target = target;
|
|
220
|
+
}
|
|
221
|
+
/** Same as `bus.publish`, limited to the events of the view. */
|
|
222
|
+
async publish(event, options) {
|
|
223
|
+
this.#assertPublishable(event);
|
|
224
|
+
return this.#target.publish(event, options);
|
|
225
|
+
}
|
|
226
|
+
/** Same as `bus.publishAll`, limited to the events of the view. Nothing is stored unless every event belongs to it. */
|
|
227
|
+
async publishAll(events, options) {
|
|
228
|
+
for (const event of events) {
|
|
229
|
+
this.#assertPublishable(event);
|
|
119
230
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
231
|
+
return this.#target.publishAll(events, options);
|
|
232
|
+
}
|
|
233
|
+
#assertPublishable(event) {
|
|
234
|
+
const { name } = requireCatalogEntry(this.#catalog, event);
|
|
235
|
+
if (!this.#publishes(name)) {
|
|
236
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
237
|
+
`Event type '${event.type}' is not one of the events this view of ${catalogLabel(
|
|
238
|
+
this.#catalog
|
|
239
|
+
)} publishes (${this.#entries.map((entry) => entry.name).join(", ")})`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// src/catalog-coverage.ts
|
|
246
|
+
var declarations = /* @__PURE__ */ new WeakSet();
|
|
247
|
+
function consume(catalog, options = {}) {
|
|
248
|
+
return declare(catalog, { role: "consume", options });
|
|
249
|
+
}
|
|
250
|
+
function produce(catalog, options = {}) {
|
|
251
|
+
return declare(catalog, { role: "produce", options });
|
|
252
|
+
}
|
|
253
|
+
function declare(catalog, { role, options }) {
|
|
254
|
+
const entries = selectCatalogEntries(catalog, options.events);
|
|
255
|
+
const declaration = Object.freeze({
|
|
256
|
+
catalog,
|
|
257
|
+
role,
|
|
258
|
+
events: Object.freeze(entries.map((entry) => entry.name)),
|
|
259
|
+
entries
|
|
260
|
+
});
|
|
261
|
+
declarations.add(declaration);
|
|
262
|
+
return declaration;
|
|
263
|
+
}
|
|
264
|
+
function toCatalogRequirement(value) {
|
|
265
|
+
if (declarations.has(value)) {
|
|
266
|
+
const declaration = value;
|
|
267
|
+
return {
|
|
268
|
+
catalogName: describeCatalog(declaration.catalog).name,
|
|
269
|
+
entries: declaration.entries,
|
|
270
|
+
need: declaration.role === "consume" ? "handler" : "route"
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
const { name, entries } = describeCatalog(value);
|
|
274
|
+
return { catalogName: name, entries, need: "handler-or-route" };
|
|
125
275
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
276
|
+
function assertCatalogsCovered(requirements, registrationsOf) {
|
|
277
|
+
const problems = /* @__PURE__ */ new Set();
|
|
278
|
+
let onlyBareCatalogs = true;
|
|
279
|
+
for (const { catalogName, entries, need } of requirements) {
|
|
280
|
+
for (const entry of entries) {
|
|
281
|
+
const problem = describeGap(entry, { need, registrations: registrationsOf(entry.type) });
|
|
282
|
+
if (problem !== void 0) {
|
|
283
|
+
problems.add(`${catalogName === void 0 ? "" : `${catalogName}.`}${problem}`);
|
|
284
|
+
onlyBareCatalogs &&= need === "handler-or-route";
|
|
285
|
+
}
|
|
286
|
+
}
|
|
129
287
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}));
|
|
136
|
-
throw new EventValidationError(
|
|
137
|
-
`Payload of '${type}' failed validation: ${issues.map((issue) => issue.message).join("; ")}`,
|
|
138
|
-
issues
|
|
288
|
+
if (problems.size > 0) {
|
|
289
|
+
const heading = onlyBareCatalogs ? "Catalog events without a handler or route:" : "Catalog events this service does not cover:";
|
|
290
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
291
|
+
`${heading}
|
|
292
|
+
${[...problems].map((line) => ` - ${line}`).join("\n")}`
|
|
139
293
|
);
|
|
140
294
|
}
|
|
141
|
-
|
|
295
|
+
}
|
|
296
|
+
function describeGap(entry, {
|
|
297
|
+
need,
|
|
298
|
+
registrations: { handlers, routes }
|
|
299
|
+
}) {
|
|
300
|
+
const subject = `${entry.name} (${entry.type})`;
|
|
301
|
+
if (need === "handler-or-route") {
|
|
302
|
+
return handlers + routes > 0 ? void 0 : `${subject}: expected handler method ${entry.handlerName}`;
|
|
303
|
+
}
|
|
304
|
+
if (need === "handler") {
|
|
305
|
+
if (handlers > 0) {
|
|
306
|
+
return void 0;
|
|
307
|
+
}
|
|
308
|
+
const routeNote = routes > 0 ? "; its route to a transport does not handle it here" : "";
|
|
309
|
+
return `${subject}: consumed here, needs handler method ${entry.handlerName}${routeNote}`;
|
|
310
|
+
}
|
|
311
|
+
if (routes > 0) {
|
|
312
|
+
return void 0;
|
|
313
|
+
}
|
|
314
|
+
const handlerNote = handlers > 0 ? "; a local handler does not deliver it to other services" : "";
|
|
315
|
+
return `${subject}: produced here, needs a route to a transport${handlerNote}`;
|
|
142
316
|
}
|
|
143
317
|
|
|
144
318
|
// src/event-store.ts
|
|
@@ -148,20 +322,26 @@ var DELIVERY_STATUSES = [
|
|
|
148
322
|
"completed",
|
|
149
323
|
"dead"
|
|
150
324
|
];
|
|
325
|
+
var UUID_BYTES = 16;
|
|
326
|
+
var TIMESTAMP_BYTES = 6;
|
|
327
|
+
var BYTE_VALUES = 256;
|
|
328
|
+
var LOW_NIBBLE = 15;
|
|
329
|
+
var VERSION_7 = 112;
|
|
330
|
+
var VARIANT_PAYLOAD = 63;
|
|
331
|
+
var RFC_9562_VARIANT = 128;
|
|
151
332
|
function createUuidV7(timestamp = Date.now()) {
|
|
152
|
-
const bytes = crypto.randomBytes(
|
|
333
|
+
const bytes = crypto.randomBytes(UUID_BYTES);
|
|
153
334
|
let remaining = timestamp;
|
|
154
|
-
for (let index =
|
|
155
|
-
bytes[index] = remaining %
|
|
156
|
-
remaining = Math.floor(remaining /
|
|
335
|
+
for (let index = TIMESTAMP_BYTES - 1; index >= 0; index -= 1) {
|
|
336
|
+
bytes[index] = remaining % BYTE_VALUES;
|
|
337
|
+
remaining = Math.floor(remaining / BYTE_VALUES);
|
|
157
338
|
}
|
|
158
|
-
bytes[6] = bytes[6] &
|
|
159
|
-
bytes[8] = bytes[8] &
|
|
160
|
-
|
|
161
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
339
|
+
bytes[6] = bytes[6] & LOW_NIBBLE | VERSION_7;
|
|
340
|
+
bytes[8] = bytes[8] & VARIANT_PAYLOAD | RFC_9562_VARIANT;
|
|
341
|
+
return bytes.toString("hex").replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, "$1-$2-$3-$4-$5");
|
|
162
342
|
}
|
|
163
343
|
|
|
164
|
-
// src/in-memory-
|
|
344
|
+
// src/in-memory-transaction.ts
|
|
165
345
|
var InMemoryTransaction = class {
|
|
166
346
|
#operations = [];
|
|
167
347
|
#finishers = [];
|
|
@@ -171,7 +351,7 @@ var InMemoryTransaction = class {
|
|
|
171
351
|
}
|
|
172
352
|
enqueue(operation) {
|
|
173
353
|
if (this.#state !== "open") {
|
|
174
|
-
throw new ConfigurationError(`Cannot write through a ${this.#state} in-memory transaction`);
|
|
354
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(`Cannot write through a ${this.#state} in-memory transaction`);
|
|
175
355
|
}
|
|
176
356
|
this.#operations.push(operation);
|
|
177
357
|
}
|
|
@@ -196,20 +376,26 @@ var InMemoryTransaction = class {
|
|
|
196
376
|
}
|
|
197
377
|
}
|
|
198
378
|
};
|
|
379
|
+
|
|
380
|
+
// src/in-memory-event-store.ts
|
|
381
|
+
var DEFAULT_LIST_LIMIT = 100;
|
|
199
382
|
var InMemoryEventStore = class {
|
|
200
383
|
#events = /* @__PURE__ */ new Map();
|
|
201
384
|
#deliveries = /* @__PURE__ */ new Map();
|
|
202
385
|
#keySequences = /* @__PURE__ */ new Map();
|
|
203
386
|
/** Row-lock emulation: event ids and ordering keys written by open transactions. */
|
|
204
387
|
#locks = /* @__PURE__ */ new Map();
|
|
388
|
+
#writes = /* @__PURE__ */ new WeakMap();
|
|
205
389
|
#generateIdentifier;
|
|
390
|
+
#retainCompleted;
|
|
206
391
|
constructor(options = {}) {
|
|
207
392
|
if (process.env["NODE_ENV"] === "production" && options.allowInProduction !== true) {
|
|
208
|
-
throw new ConfigurationError(
|
|
393
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
209
394
|
"InMemoryEventStore loses every event on restart. Use a database store in production, or pass allowInProduction: true"
|
|
210
395
|
);
|
|
211
396
|
}
|
|
212
397
|
this.#generateIdentifier = options.generateIdentifier ?? (() => createUuidV7());
|
|
398
|
+
this.#retainCompleted = options.retainCompleted ?? false;
|
|
213
399
|
}
|
|
214
400
|
async transaction(work) {
|
|
215
401
|
const transaction = new InMemoryTransaction();
|
|
@@ -222,6 +408,7 @@ var InMemoryEventStore = class {
|
|
|
222
408
|
throw error;
|
|
223
409
|
}
|
|
224
410
|
}
|
|
411
|
+
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
225
412
|
async append(transaction, entries, now) {
|
|
226
413
|
await this.#lock(
|
|
227
414
|
transaction,
|
|
@@ -230,13 +417,15 @@ var InMemoryEventStore = class {
|
|
|
230
417
|
...entry.event.key === null ? [] : [`key:${entry.event.key}`]
|
|
231
418
|
])
|
|
232
419
|
);
|
|
233
|
-
const
|
|
234
|
-
const
|
|
420
|
+
const written = this.#written(transaction);
|
|
421
|
+
const pendingEventIds = written.eventIds;
|
|
422
|
+
const pendingDeliveryKeys = written.deliveryKeys;
|
|
235
423
|
const appendedEventIds = [];
|
|
236
424
|
const duplicateEventIds = [];
|
|
237
425
|
let appendedDeliveryCount = 0;
|
|
238
426
|
for (const entry of entries) {
|
|
239
427
|
const eventId = entry.event.id;
|
|
428
|
+
const serialized = serializeEnvelope(entry.event);
|
|
240
429
|
const isDuplicate = this.#events.has(eventId) || pendingEventIds.has(eventId);
|
|
241
430
|
if (isDuplicate) {
|
|
242
431
|
duplicateEventIds.push(eventId);
|
|
@@ -244,47 +433,63 @@ var InMemoryEventStore = class {
|
|
|
244
433
|
appendedEventIds.push(eventId);
|
|
245
434
|
pendingEventIds.add(eventId);
|
|
246
435
|
}
|
|
247
|
-
const newDeliveries = entry
|
|
248
|
-
const deliveryKey = `${eventId}\0${delivery.handler}`;
|
|
249
|
-
if (pendingDeliveryKeys.has(deliveryKey) || this.#hasDelivery(eventId, delivery.handler)) {
|
|
250
|
-
return false;
|
|
251
|
-
}
|
|
252
|
-
pendingDeliveryKeys.add(deliveryKey);
|
|
253
|
-
return true;
|
|
254
|
-
});
|
|
436
|
+
const newDeliveries = this.#newDeliveries(entry, pendingDeliveryKeys);
|
|
255
437
|
appendedDeliveryCount += newDeliveries.length;
|
|
256
|
-
const serialized = serializeEnvelope(entry.event);
|
|
257
438
|
transaction.enqueue(() => {
|
|
258
|
-
|
|
259
|
-
this.#events.set(eventId, {
|
|
260
|
-
envelope: serialized,
|
|
261
|
-
sequence: this.#nextSequence(serialized.key)
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
const stored = this.#events.get(eventId);
|
|
265
|
-
for (const delivery of newDeliveries) {
|
|
266
|
-
this.#deliveries.set(delivery.id, {
|
|
267
|
-
id: delivery.id,
|
|
268
|
-
eventId,
|
|
269
|
-
handler: delivery.handler,
|
|
270
|
-
key: stored.envelope.key,
|
|
271
|
-
sequence: stored.sequence,
|
|
272
|
-
status: "pending",
|
|
273
|
-
attempts: 0,
|
|
274
|
-
availableAt: delivery.availableAt,
|
|
275
|
-
leaseOwner: null,
|
|
276
|
-
leaseToken: null,
|
|
277
|
-
leaseExpiresAt: null,
|
|
278
|
-
lastError: null,
|
|
279
|
-
createdAt: now,
|
|
280
|
-
updatedAt: now,
|
|
281
|
-
completedAt: null
|
|
282
|
-
});
|
|
283
|
-
}
|
|
439
|
+
this.#applyAppend({ eventId, serialized, isDuplicate, newDeliveries, now });
|
|
284
440
|
});
|
|
285
441
|
}
|
|
286
442
|
return { appendedEventIds, duplicateEventIds, appendedDeliveryCount };
|
|
287
443
|
}
|
|
444
|
+
/**
|
|
445
|
+
* The deliveries of `entry` whose handler the event neither has nor gets earlier in the
|
|
446
|
+
* transaction; remembers them in `pendingDeliveryKeys`.
|
|
447
|
+
*/
|
|
448
|
+
#newDeliveries(entry, pendingDeliveryKeys) {
|
|
449
|
+
const eventId = entry.event.id;
|
|
450
|
+
return entry.deliveries.filter((delivery) => {
|
|
451
|
+
const deliveryKey = `${eventId}\0${delivery.handler}`;
|
|
452
|
+
if (pendingDeliveryKeys.has(deliveryKey) || this.#events.get(eventId)?.handlers.has(delivery.handler) === true) {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
pendingDeliveryKeys.add(deliveryKey);
|
|
456
|
+
return true;
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
/** Applies one entry of an append when its transaction commits. */
|
|
460
|
+
#applyAppend(input) {
|
|
461
|
+
const { eventId, serialized, isDuplicate, newDeliveries, now } = input;
|
|
462
|
+
if (!isDuplicate) {
|
|
463
|
+
this.#events.set(eventId, {
|
|
464
|
+
envelope: serialized,
|
|
465
|
+
sequence: this.#nextSequence(serialized.key),
|
|
466
|
+
createdAt: now,
|
|
467
|
+
handlers: /* @__PURE__ */ new Set(),
|
|
468
|
+
completedAt: null
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
const stored = this.#events.get(eventId);
|
|
472
|
+
for (const delivery of newDeliveries) {
|
|
473
|
+
stored.handlers.add(delivery.handler);
|
|
474
|
+
this.#deliveries.set(delivery.id, {
|
|
475
|
+
id: delivery.id,
|
|
476
|
+
eventId,
|
|
477
|
+
handler: delivery.handler,
|
|
478
|
+
key: stored.envelope.key,
|
|
479
|
+
sequence: stored.sequence,
|
|
480
|
+
status: "pending",
|
|
481
|
+
attempts: 0,
|
|
482
|
+
availableAt: delivery.availableAt,
|
|
483
|
+
leaseOwner: null,
|
|
484
|
+
leaseToken: null,
|
|
485
|
+
leaseExpiresAt: null,
|
|
486
|
+
lastError: null,
|
|
487
|
+
createdAt: now,
|
|
488
|
+
updatedAt: now,
|
|
489
|
+
completedAt: null
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}
|
|
288
493
|
async claim(request) {
|
|
289
494
|
const handlers = new Set(request.handlers);
|
|
290
495
|
const due = [...this.#deliveries.values()].filter((delivery) => handlers.has(delivery.handler) && isDue(delivery, request.now)).sort(compareDue);
|
|
@@ -313,21 +518,17 @@ var InMemoryEventStore = class {
|
|
|
313
518
|
}
|
|
314
519
|
return claimed;
|
|
315
520
|
}
|
|
521
|
+
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
316
522
|
async extendLease(lease, leaseExpiresAt, now) {
|
|
317
523
|
return this.#updateLeased(lease, (delivery) => {
|
|
318
524
|
delivery.leaseExpiresAt = leaseExpiresAt;
|
|
319
525
|
delivery.updatedAt = now;
|
|
320
526
|
});
|
|
321
527
|
}
|
|
528
|
+
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
322
529
|
async complete(lease, now, transaction) {
|
|
323
|
-
const finish = (delivery) => {
|
|
324
|
-
delivery.status = "completed";
|
|
325
|
-
delivery.completedAt = now;
|
|
326
|
-
delivery.updatedAt = now;
|
|
327
|
-
clearLease(delivery);
|
|
328
|
-
};
|
|
329
530
|
if (transaction === void 0) {
|
|
330
|
-
return this.#updateLeased(lease, finish);
|
|
531
|
+
return this.#updateLeased(lease, (delivery) => this.#finish(delivery, now));
|
|
331
532
|
}
|
|
332
533
|
if (this.#findLeased(lease) === void 0) {
|
|
333
534
|
return false;
|
|
@@ -335,11 +536,15 @@ var InMemoryEventStore = class {
|
|
|
335
536
|
transaction.enqueue(() => {
|
|
336
537
|
const delivery = this.#findLeased(lease);
|
|
337
538
|
if (delivery !== void 0) {
|
|
338
|
-
finish(delivery);
|
|
539
|
+
this.#finish(delivery, now);
|
|
339
540
|
}
|
|
340
541
|
});
|
|
341
542
|
return true;
|
|
342
543
|
}
|
|
544
|
+
async completeMany(leases, now) {
|
|
545
|
+
return Promise.all(leases.map((lease) => this.complete(lease, now)));
|
|
546
|
+
}
|
|
547
|
+
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
343
548
|
async reschedule(lease, availableAt, error, now) {
|
|
344
549
|
return this.#updateLeased(lease, (delivery) => {
|
|
345
550
|
delivery.status = "pending";
|
|
@@ -358,6 +563,7 @@ var InMemoryEventStore = class {
|
|
|
358
563
|
clearLease(delivery);
|
|
359
564
|
});
|
|
360
565
|
}
|
|
566
|
+
// eslint-disable-next-line relaybus/max-two-params -- EventStore port signature
|
|
361
567
|
async bury(lease, error, now) {
|
|
362
568
|
return this.#updateLeased(lease, (delivery) => {
|
|
363
569
|
delivery.status = "dead";
|
|
@@ -367,8 +573,10 @@ var InMemoryEventStore = class {
|
|
|
367
573
|
});
|
|
368
574
|
}
|
|
369
575
|
async listDeliveries(query) {
|
|
370
|
-
const limit = query.limit ??
|
|
371
|
-
return [...this.#deliveries.values()].filter(
|
|
576
|
+
const limit = query.limit ?? DEFAULT_LIST_LIMIT;
|
|
577
|
+
return [...this.#deliveries.values()].filter(
|
|
578
|
+
(delivery) => matchesQuery(delivery, { event: this.#events.get(delivery.eventId), query })
|
|
579
|
+
).sort(
|
|
372
580
|
(left, right) => left.createdAt.getTime() - right.createdAt.getTime() || compareIds(left.id, right.id)
|
|
373
581
|
).slice(0, limit).map((delivery) => ({
|
|
374
582
|
id: delivery.id,
|
|
@@ -410,17 +618,26 @@ var InMemoryEventStore = class {
|
|
|
410
618
|
deletedDeliveries += 1;
|
|
411
619
|
}
|
|
412
620
|
}
|
|
621
|
+
return { deletedDeliveries, deletedEvents: this.#purgeEvents(request, touchedEventIds) };
|
|
622
|
+
}
|
|
623
|
+
/** Deletes the events no delivery references whose retention ran out; returns how many. */
|
|
624
|
+
#purgeEvents(request, touchedEventIds) {
|
|
413
625
|
const referencedEventIds = new Set(
|
|
414
626
|
[...this.#deliveries.values()].map((delivery) => delivery.eventId)
|
|
415
627
|
);
|
|
628
|
+
const { orphanCutoff, completedCutoff } = eventCutoffs(request);
|
|
416
629
|
let deletedEvents = 0;
|
|
417
|
-
for (const eventId of
|
|
418
|
-
if (
|
|
630
|
+
for (const [eventId, event] of [...this.#events]) {
|
|
631
|
+
if (referencedEventIds.has(eventId) || this.#locks.has(`event:${eventId}`)) {
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
const expired = event.completedAt === null ? touchedEventIds.has(eventId) || event.createdAt.getTime() < orphanCutoff : event.completedAt.getTime() < completedCutoff;
|
|
635
|
+
if (expired) {
|
|
419
636
|
this.#events.delete(eventId);
|
|
420
637
|
deletedEvents += 1;
|
|
421
638
|
}
|
|
422
639
|
}
|
|
423
|
-
return
|
|
640
|
+
return deletedEvents;
|
|
424
641
|
}
|
|
425
642
|
async statistics(now) {
|
|
426
643
|
const counts = {
|
|
@@ -446,6 +663,15 @@ var InMemoryEventStore = class {
|
|
|
446
663
|
* until `transaction` finishes, the way a database holds row locks. Sorted
|
|
447
664
|
* acquisition keeps two transactions from waiting on each other.
|
|
448
665
|
*/
|
|
666
|
+
/** The events and deliveries the transaction appended so far, which are not applied before it commits. */
|
|
667
|
+
#written(transaction) {
|
|
668
|
+
let written = this.#writes.get(transaction);
|
|
669
|
+
if (written === void 0) {
|
|
670
|
+
written = { eventIds: /* @__PURE__ */ new Set(), deliveryKeys: /* @__PURE__ */ new Set() };
|
|
671
|
+
this.#writes.set(transaction, written);
|
|
672
|
+
}
|
|
673
|
+
return written;
|
|
674
|
+
}
|
|
449
675
|
async #lock(transaction, names) {
|
|
450
676
|
for (const name of [...new Set(names)].sort()) {
|
|
451
677
|
let held = this.#locks.get(name);
|
|
@@ -467,14 +693,6 @@ var InMemoryEventStore = class {
|
|
|
467
693
|
});
|
|
468
694
|
}
|
|
469
695
|
}
|
|
470
|
-
#hasDelivery(eventId, handler) {
|
|
471
|
-
for (const delivery of this.#deliveries.values()) {
|
|
472
|
-
if (delivery.eventId === eventId && delivery.handler === handler) {
|
|
473
|
-
return true;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
return false;
|
|
477
|
-
}
|
|
478
696
|
#nextSequence(key) {
|
|
479
697
|
if (key === null) {
|
|
480
698
|
return null;
|
|
@@ -494,17 +712,27 @@ var InMemoryEventStore = class {
|
|
|
494
712
|
return false;
|
|
495
713
|
}
|
|
496
714
|
for (const other of this.#deliveries.values()) {
|
|
497
|
-
if (other
|
|
498
|
-
continue;
|
|
499
|
-
}
|
|
500
|
-
const unfinishedPredecessor = other.sequence < delivery.sequence && (other.status === "pending" || other.status === "processing");
|
|
501
|
-
const runningUnderLiveLease = other.status === "processing" && other.leaseExpiresAt > now;
|
|
502
|
-
if (unfinishedPredecessor || runningUnderLiveLease) {
|
|
715
|
+
if (blocksKey(other, { delivery, now })) {
|
|
503
716
|
return true;
|
|
504
717
|
}
|
|
505
718
|
}
|
|
506
719
|
return false;
|
|
507
720
|
}
|
|
721
|
+
/** Deletes a completed delivery, or marks it completed when a history is kept. */
|
|
722
|
+
#finish(delivery, now) {
|
|
723
|
+
const event = this.#events.get(delivery.eventId);
|
|
724
|
+
if (event.completedAt === null || event.completedAt < now) {
|
|
725
|
+
event.completedAt = now;
|
|
726
|
+
}
|
|
727
|
+
if (!this.#retainCompleted) {
|
|
728
|
+
this.#deliveries.delete(delivery.id);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
delivery.status = "completed";
|
|
732
|
+
delivery.completedAt = now;
|
|
733
|
+
delivery.updatedAt = now;
|
|
734
|
+
clearLease(delivery);
|
|
735
|
+
}
|
|
508
736
|
#findLeased(lease) {
|
|
509
737
|
const delivery = this.#deliveries.get(lease.deliveryId);
|
|
510
738
|
if (delivery?.status === "processing" && delivery.leaseToken === lease.leaseToken) {
|
|
@@ -526,6 +754,21 @@ var InMemoryEventStore = class {
|
|
|
526
754
|
return { ...copy, occurredAt: new Date(copy.occurredAt) };
|
|
527
755
|
}
|
|
528
756
|
};
|
|
757
|
+
function blocksKey(other, { delivery, now }) {
|
|
758
|
+
if (other === delivery || other.handler !== delivery.handler || other.key !== delivery.key) {
|
|
759
|
+
return false;
|
|
760
|
+
}
|
|
761
|
+
const unfinishedPredecessor = other.sequence < delivery.sequence && (other.status === "pending" || other.status === "processing");
|
|
762
|
+
const runningUnderLiveLease = other.status === "processing" && other.leaseExpiresAt > now;
|
|
763
|
+
return unfinishedPredecessor || runningUnderLiveLease;
|
|
764
|
+
}
|
|
765
|
+
function eventCutoffs(request) {
|
|
766
|
+
const cutoffs = [request.completedBefore, request.deadBefore].filter((cutoff) => cutoff !== void 0).map((cutoff) => cutoff.getTime());
|
|
767
|
+
return {
|
|
768
|
+
orphanCutoff: cutoffs.length === 0 ? Number.NEGATIVE_INFINITY : Math.min(...cutoffs),
|
|
769
|
+
completedCutoff: request.completedBefore?.getTime() ?? Number.NEGATIVE_INFINITY
|
|
770
|
+
};
|
|
771
|
+
}
|
|
529
772
|
function serializeEnvelope(envelope) {
|
|
530
773
|
return JSON.parse(JSON.stringify(envelope));
|
|
531
774
|
}
|
|
@@ -550,7 +793,7 @@ function clearLease(delivery) {
|
|
|
550
793
|
delivery.leaseToken = null;
|
|
551
794
|
delivery.leaseExpiresAt = null;
|
|
552
795
|
}
|
|
553
|
-
function matchesQuery(delivery, event, query) {
|
|
796
|
+
function matchesQuery(delivery, { event, query }) {
|
|
554
797
|
return (query.status === void 0 || delivery.status === query.status) && (query.handler === void 0 || delivery.handler === query.handler) && (query.eventType === void 0 || event.envelope.type === query.eventType) && (query.eventId === void 0 || delivery.eventId === query.eventId);
|
|
555
798
|
}
|
|
556
799
|
function isPurgeable(delivery, request) {
|
|
@@ -562,210 +805,979 @@ function isPurgeable(delivery, request) {
|
|
|
562
805
|
}
|
|
563
806
|
return false;
|
|
564
807
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
|
|
808
|
+
|
|
809
|
+
// src/tracing.ts
|
|
810
|
+
var TRACEPARENT_HEADER = "traceparent";
|
|
811
|
+
var TRACESTATE_HEADER = "tracestate";
|
|
812
|
+
var TRACEPARENT_FIELDS = "(?!0{32})[\\da-f]{32}-(?!0{16})[\\da-f]{16}-[\\da-f]{2}";
|
|
813
|
+
var TRACEPARENT = new RegExp(
|
|
814
|
+
`^(?:00-${TRACEPARENT_FIELDS}|(?!00|ff)[\\da-f]{2}-${TRACEPARENT_FIELDS}(?:-.*)?)$`
|
|
815
|
+
);
|
|
816
|
+
function traceContextOf(headers) {
|
|
817
|
+
const traceparent = headers[TRACEPARENT_HEADER];
|
|
818
|
+
if (traceparent === void 0 || !TRACEPARENT.test(traceparent)) {
|
|
819
|
+
return void 0;
|
|
820
|
+
}
|
|
821
|
+
const tracestate = headers[TRACESTATE_HEADER];
|
|
822
|
+
return tracestate === void 0 ? { traceparent } : { traceparent, tracestate };
|
|
574
823
|
}
|
|
575
824
|
|
|
576
|
-
// src/
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
factor: options?.factor ?? base.factor,
|
|
600
|
-
maxDelayMs: options?.maxDelay === void 0 ? (
|
|
601
|
-
// Raising only the initial delay must not trip the inherited cap.
|
|
602
|
-
Math.max(base.maxDelayMs, initialDelayMs)
|
|
603
|
-
) : toMilliseconds(options.maxDelay, "retry.maxDelay"),
|
|
604
|
-
jitter: options?.jitter ?? base.jitter,
|
|
605
|
-
retryIf: options?.retryIf ?? base.retryIf
|
|
606
|
-
};
|
|
607
|
-
if (policy.attempts !== Number.POSITIVE_INFINITY && (!Number.isInteger(policy.attempts) || policy.attempts < 1)) {
|
|
608
|
-
throw new ConfigurationError(
|
|
609
|
-
`retry.attempts must be a positive integer or Infinity, received ${policy.attempts}`
|
|
825
|
+
// src/bus-observation.ts
|
|
826
|
+
function withTraceHeaders(options, headers) {
|
|
827
|
+
return Object.keys(headers).length === 0 ? options : { ...options, headers: { ...headers, ...options.headers } };
|
|
828
|
+
}
|
|
829
|
+
var BusObservation = class _BusObservation {
|
|
830
|
+
#notifier;
|
|
831
|
+
#tracer;
|
|
832
|
+
#clock;
|
|
833
|
+
#publishes;
|
|
834
|
+
#receives;
|
|
835
|
+
#sends;
|
|
836
|
+
constructor(input) {
|
|
837
|
+
this.#notifier = input.notifier;
|
|
838
|
+
this.#tracer = input.tracer;
|
|
839
|
+
this.#clock = input.clock;
|
|
840
|
+
this.#publishes = input.notifier.wants("onPublishFinished");
|
|
841
|
+
this.#receives = input.notifier.wants("onReceiveFinished");
|
|
842
|
+
this.#sends = input.notifier.wants("onTransportSent");
|
|
843
|
+
}
|
|
844
|
+
/** An observation, or undefined when no observer hook and no tracer would see anything. */
|
|
845
|
+
static create(input) {
|
|
846
|
+
const observed = input.tracer !== void 0 || ["onPublishFinished", "onReceiveFinished", "onTransportSent", "onBacklog"].some(
|
|
847
|
+
(method) => input.notifier.wants(method)
|
|
610
848
|
);
|
|
849
|
+
return observed ? new _BusObservation(input) : void 0;
|
|
850
|
+
}
|
|
851
|
+
/** Runs a publish call inside the tracer's publish span, reporting its duration and outcome. */
|
|
852
|
+
async publish(input, run) {
|
|
853
|
+
const startedAt = this.#publishes ? this.#now() : 0;
|
|
854
|
+
const tracer = this.#tracer;
|
|
855
|
+
const traced = tracer?.publish === void 0 ? run({}) : tracer.publish(input, run);
|
|
856
|
+
if (!this.#publishes) {
|
|
857
|
+
return traced;
|
|
858
|
+
}
|
|
859
|
+
const report = (outcome, error) => {
|
|
860
|
+
this.#notifier.notify("onPublishFinished", {
|
|
861
|
+
eventTypes: input.eventTypes,
|
|
862
|
+
durationMs: this.#now() - startedAt,
|
|
863
|
+
outcome,
|
|
864
|
+
...error === void 0 ? {} : { error: chunk3OHMEMZA_cjs.toErrorInfo(error) }
|
|
865
|
+
});
|
|
866
|
+
};
|
|
867
|
+
try {
|
|
868
|
+
const result = await traced;
|
|
869
|
+
report("success");
|
|
870
|
+
return result;
|
|
871
|
+
} catch (error) {
|
|
872
|
+
report("error", error);
|
|
873
|
+
throw error;
|
|
874
|
+
}
|
|
611
875
|
}
|
|
612
|
-
|
|
613
|
-
|
|
876
|
+
/** Runs `receive` for one event, reporting where it came from and what became of it. */
|
|
877
|
+
async receive(input, run) {
|
|
878
|
+
if (!this.#receives) {
|
|
879
|
+
return run();
|
|
880
|
+
}
|
|
881
|
+
const startedAt = this.#now();
|
|
882
|
+
try {
|
|
883
|
+
const outcome = await run();
|
|
884
|
+
this.#reportReceive(input, { outcome, startedAt, failure: void 0 });
|
|
885
|
+
return outcome;
|
|
886
|
+
} catch (error) {
|
|
887
|
+
const outcome = error instanceof chunk3OHMEMZA_cjs.InvalidEnvelopeError ? "invalid" : "error";
|
|
888
|
+
this.#reportReceive(input, { outcome, startedAt, failure: error });
|
|
889
|
+
throw error;
|
|
890
|
+
}
|
|
614
891
|
}
|
|
615
|
-
|
|
616
|
-
|
|
892
|
+
#reportReceive({ envelope, origin }, report) {
|
|
893
|
+
const { headers } = envelope;
|
|
894
|
+
this.#notifier.notify("onReceiveFinished", {
|
|
895
|
+
transport: origin?.transport.name,
|
|
896
|
+
system: origin === void 0 ? void 0 : systemOf(origin.transport),
|
|
897
|
+
destination: origin?.destination,
|
|
898
|
+
eventType: textOrUndefined(envelope.type),
|
|
899
|
+
source: textOrUndefined(envelope.source),
|
|
900
|
+
traceContext: isObject(headers) ? traceContextOf(headers) : void 0,
|
|
901
|
+
outcome: report.outcome,
|
|
902
|
+
durationMs: this.#now() - report.startedAt,
|
|
903
|
+
...report.failure === void 0 ? {} : { error: chunk3OHMEMZA_cjs.toErrorInfo(report.failure) }
|
|
904
|
+
});
|
|
617
905
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
906
|
+
/** The handler of a route: sends inside the tracer's send span and reports each send. */
|
|
907
|
+
sender(transport, destination) {
|
|
908
|
+
if (this.#tracer?.send === void 0 && !this.#sends) {
|
|
909
|
+
return (envelope) => transport.publish(envelope, destination);
|
|
910
|
+
}
|
|
911
|
+
return (envelope) => this.#send({ envelope, transport, destination });
|
|
622
912
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
913
|
+
async #send({ envelope, transport, destination }) {
|
|
914
|
+
const startedAt = this.#sends ? this.#now() : 0;
|
|
915
|
+
const system = systemOf(transport);
|
|
916
|
+
const run = (headers) => transport.publish(
|
|
917
|
+
Object.keys(headers).length === 0 ? envelope : { ...envelope, headers: { ...envelope.headers, ...headers } },
|
|
918
|
+
destination
|
|
919
|
+
);
|
|
920
|
+
const tracer = this.#tracer;
|
|
921
|
+
const sent = tracer?.send === void 0 ? run({}) : tracer.send({ envelope, transport: transport.name, system, destination }, run);
|
|
922
|
+
if (!this.#sends) {
|
|
923
|
+
return sent;
|
|
924
|
+
}
|
|
925
|
+
const report = (outcome, error) => {
|
|
926
|
+
this.#notifier.notify("onTransportSent", {
|
|
927
|
+
envelope,
|
|
928
|
+
transport: transport.name,
|
|
929
|
+
system,
|
|
930
|
+
destination,
|
|
931
|
+
durationMs: this.#now() - startedAt,
|
|
932
|
+
outcome,
|
|
933
|
+
...error === void 0 ? {} : { error: chunk3OHMEMZA_cjs.toErrorInfo(error) }
|
|
934
|
+
});
|
|
935
|
+
};
|
|
936
|
+
try {
|
|
937
|
+
await sent;
|
|
938
|
+
report("success");
|
|
939
|
+
} catch (error) {
|
|
940
|
+
report("error", error);
|
|
941
|
+
throw error;
|
|
942
|
+
}
|
|
635
943
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
944
|
+
/**
|
|
945
|
+
* Reads the backlog every `intervalMs` and reports it, when an observer wants it. A reading that
|
|
946
|
+
* is still running when the next one is due is not overlapped. Returns the cancel of the timer.
|
|
947
|
+
*/
|
|
948
|
+
startBacklog({ intervalMs, statistics }) {
|
|
949
|
+
if (!this.#notifier.wants("onBacklog")) {
|
|
950
|
+
return void 0;
|
|
951
|
+
}
|
|
952
|
+
let reading = false;
|
|
953
|
+
return chunk3OHMEMZA_cjs.schedule(this.#clock, {
|
|
954
|
+
run: () => {
|
|
955
|
+
if (reading) {
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
reading = true;
|
|
959
|
+
statistics().then((result) => this.#reportBacklog(result)).catch((error) => this.#notifier.failure("backlog", error)).finally(() => {
|
|
960
|
+
reading = false;
|
|
961
|
+
});
|
|
962
|
+
},
|
|
963
|
+
delayMs: intervalMs,
|
|
964
|
+
repeat: true,
|
|
965
|
+
unref: true
|
|
966
|
+
});
|
|
640
967
|
}
|
|
641
|
-
|
|
642
|
-
|
|
968
|
+
#reportBacklog(statistics) {
|
|
969
|
+
const oldestDueAt = statistics.oldestDueAt;
|
|
970
|
+
this.#notifier.notify("onBacklog", {
|
|
971
|
+
pending: statistics.pending,
|
|
972
|
+
oldestPendingAgeMs: oldestDueAt === null ? 0 : Math.max(0, this.#now() - oldestDueAt.getTime()),
|
|
973
|
+
statistics
|
|
974
|
+
});
|
|
643
975
|
}
|
|
644
|
-
|
|
645
|
-
return
|
|
646
|
-
} catch {
|
|
647
|
-
return true;
|
|
976
|
+
#now() {
|
|
977
|
+
return this.#clock.now().getTime();
|
|
648
978
|
}
|
|
979
|
+
};
|
|
980
|
+
function systemOf(transport) {
|
|
981
|
+
return transport.system ?? transport.name;
|
|
982
|
+
}
|
|
983
|
+
function textOrUndefined(value) {
|
|
984
|
+
return typeof value === "string" ? value : void 0;
|
|
985
|
+
}
|
|
986
|
+
function isObject(value) {
|
|
987
|
+
return typeof value === "object" && value !== null;
|
|
649
988
|
}
|
|
650
989
|
|
|
651
|
-
// src/
|
|
652
|
-
var
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
#
|
|
656
|
-
#
|
|
657
|
-
#
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
this.#
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
990
|
+
// src/dispatch-observation.ts
|
|
991
|
+
var DispatchObservation = class _DispatchObservation {
|
|
992
|
+
#notifier;
|
|
993
|
+
#tracer;
|
|
994
|
+
#clock;
|
|
995
|
+
#claims;
|
|
996
|
+
#runs;
|
|
997
|
+
constructor(input) {
|
|
998
|
+
this.#notifier = input.notifier;
|
|
999
|
+
this.#tracer = input.tracer;
|
|
1000
|
+
this.#clock = input.clock;
|
|
1001
|
+
this.#claims = input.notifier.wants("onClaimFinished");
|
|
1002
|
+
this.#runs = input.notifier.wants("onHandlerStarted") || input.notifier.wants("onHandlerFinished");
|
|
1003
|
+
}
|
|
1004
|
+
/** An observation, or undefined when no observer hook and no tracer would see anything. */
|
|
1005
|
+
static create(input) {
|
|
1006
|
+
const observation = new _DispatchObservation(input);
|
|
1007
|
+
return observation.#claims || observation.#runs || input.tracer?.process !== void 0 ? observation : void 0;
|
|
1008
|
+
}
|
|
1009
|
+
/** Claims through `store`, reporting the query's size, lanes and duration. */
|
|
1010
|
+
async claim(store, request) {
|
|
1011
|
+
if (!this.#claims) {
|
|
1012
|
+
return store.claim(request);
|
|
1013
|
+
}
|
|
1014
|
+
const startedAt = this.#clock.now().getTime();
|
|
1015
|
+
const report = { handlers: request.handlers, requested: request.limit };
|
|
1016
|
+
try {
|
|
1017
|
+
const claimed = await store.claim(request);
|
|
1018
|
+
this.#notifier.notify("onClaimFinished", {
|
|
1019
|
+
...report,
|
|
1020
|
+
claimed: claimed.length,
|
|
1021
|
+
lanes: new Set(claimed.map((delivery) => delivery.leaseToken)).size,
|
|
1022
|
+
durationMs: this.#clock.now().getTime() - startedAt,
|
|
1023
|
+
outcome: "success"
|
|
1024
|
+
});
|
|
1025
|
+
return claimed;
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
this.#notifier.notify("onClaimFinished", {
|
|
1028
|
+
...report,
|
|
1029
|
+
claimed: 0,
|
|
1030
|
+
lanes: 0,
|
|
1031
|
+
durationMs: this.#clock.now().getTime() - startedAt,
|
|
1032
|
+
outcome: "error",
|
|
1033
|
+
error: chunk3OHMEMZA_cjs.toErrorInfo(error)
|
|
1034
|
+
});
|
|
1035
|
+
throw error;
|
|
1036
|
+
}
|
|
668
1037
|
}
|
|
669
|
-
|
|
670
|
-
|
|
1038
|
+
/** Runs a local handler inside the tracer's process span; routes are traced as sends instead. */
|
|
1039
|
+
process(run, handle) {
|
|
1040
|
+
const tracer = this.#tracer;
|
|
1041
|
+
if (tracer?.process === void 0 || run.registration.forwardsToTransport) {
|
|
1042
|
+
return handle();
|
|
1043
|
+
}
|
|
1044
|
+
return tracer.process({ delivery: run.delivery }, async () => handle());
|
|
671
1045
|
}
|
|
672
|
-
start
|
|
1046
|
+
/** Reports the start of a handler run now and its end when `settled` settles, passing it through. */
|
|
1047
|
+
async observe(run, settled) {
|
|
1048
|
+
if (!this.#runs) {
|
|
1049
|
+
await settled;
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
const { delivery } = run;
|
|
1053
|
+
const kind = run.registration.forwardsToTransport ? "route" : "handler";
|
|
1054
|
+
const startedAt = this.#clock.now().getTime();
|
|
1055
|
+
this.#notifier.notify("onHandlerStarted", {
|
|
1056
|
+
delivery,
|
|
1057
|
+
kind,
|
|
1058
|
+
endToEndLatencyMs: startedAt - delivery.event.occurredAt.getTime()
|
|
1059
|
+
});
|
|
1060
|
+
try {
|
|
1061
|
+
await settled;
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
const durationMs2 = this.#clock.now().getTime() - startedAt;
|
|
1064
|
+
const errorInfo = chunk3OHMEMZA_cjs.toErrorInfo(error);
|
|
1065
|
+
this.#notifier.notify("onHandlerFinished", {
|
|
1066
|
+
delivery,
|
|
1067
|
+
kind,
|
|
1068
|
+
durationMs: durationMs2,
|
|
1069
|
+
outcome: "error",
|
|
1070
|
+
error: errorInfo
|
|
1071
|
+
});
|
|
1072
|
+
throw error;
|
|
1073
|
+
}
|
|
1074
|
+
const durationMs = this.#clock.now().getTime() - startedAt;
|
|
1075
|
+
this.#notifier.notify("onHandlerFinished", { delivery, kind, durationMs, outcome: "success" });
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
|
|
1079
|
+
// src/completion-batcher.ts
|
|
1080
|
+
var CompletionBatcher = class {
|
|
1081
|
+
#store;
|
|
1082
|
+
#clock;
|
|
1083
|
+
#settings;
|
|
1084
|
+
#runningCount;
|
|
1085
|
+
#pending = [];
|
|
1086
|
+
#timer;
|
|
1087
|
+
#closing = false;
|
|
1088
|
+
/**
|
|
1089
|
+
* @param runningCount Number of deliveries the dispatcher is running right
|
|
1090
|
+
* now, including the ones waiting in this batcher.
|
|
1091
|
+
*/
|
|
1092
|
+
constructor(store, clock, settings, runningCount) {
|
|
1093
|
+
this.#store = store;
|
|
1094
|
+
this.#clock = clock;
|
|
1095
|
+
this.#settings = settings;
|
|
1096
|
+
this.#runningCount = runningCount;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Queues a completion. Resolves with whether the lease was still held;
|
|
1100
|
+
* rejects with the store error when the batch could not be written.
|
|
1101
|
+
*/
|
|
1102
|
+
submit(lease) {
|
|
1103
|
+
return new Promise((resolve, reject) => {
|
|
1104
|
+
this.#pending.push({ lease, resolve, reject });
|
|
1105
|
+
if (this.#closing || this.#pending.length >= this.#settings.maxBatchSize || this.#pending.length >= this.#runningCount()) {
|
|
1106
|
+
this.#flush();
|
|
1107
|
+
} else {
|
|
1108
|
+
this.#timer ??= chunk3OHMEMZA_cjs.schedule(this.#clock, {
|
|
1109
|
+
run: () => this.#flush(),
|
|
1110
|
+
delayMs: this.#settings.windowMs
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Shutdown has begun (or ended): while closing, nothing waits for company any more, so the completions
|
|
1117
|
+
* already queued are written now and later ones at once.
|
|
1118
|
+
*/
|
|
1119
|
+
setClosing(closing) {
|
|
1120
|
+
this.#closing = closing;
|
|
1121
|
+
if (closing && this.#pending.length > 0) {
|
|
1122
|
+
this.#flush();
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
#flush() {
|
|
1126
|
+
this.#timer?.();
|
|
1127
|
+
this.#timer = void 0;
|
|
1128
|
+
const batch = this.#pending;
|
|
1129
|
+
this.#pending = [];
|
|
1130
|
+
Promise.resolve().then(
|
|
1131
|
+
() => this.#store.completeMany(
|
|
1132
|
+
batch.map((entry) => entry.lease),
|
|
1133
|
+
this.#clock.now()
|
|
1134
|
+
)
|
|
1135
|
+
).then(
|
|
1136
|
+
(outcomes) => batch.forEach((entry, index) => entry.resolve(outcomes[index])),
|
|
1137
|
+
(error) => batch.forEach((entry) => entry.reject(error))
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
// src/delivery-timers.ts
|
|
1143
|
+
var DeliveryTimers = class {
|
|
1144
|
+
#clock;
|
|
1145
|
+
/** Cancel of every timer still waiting, with what its cancellation must also do. */
|
|
1146
|
+
#waiting = /* @__PURE__ */ new Map();
|
|
1147
|
+
constructor(clock) {
|
|
1148
|
+
this.#clock = clock;
|
|
1149
|
+
}
|
|
1150
|
+
/** Runs `task.run` after `task.delayMs`; returns what cancels it. */
|
|
1151
|
+
start(task) {
|
|
1152
|
+
return this.#start(task, () => void 0);
|
|
1153
|
+
}
|
|
1154
|
+
/** Resolves after `milliseconds`, or at once when {@link DeliveryTimers.cancelAll} runs. */
|
|
1155
|
+
sleep(milliseconds) {
|
|
1156
|
+
return new Promise((resolve) => {
|
|
1157
|
+
this.#start({ run: resolve, delayMs: milliseconds }, resolve);
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
cancelAll() {
|
|
1161
|
+
for (const [cancel, onCancel] of [...this.#waiting]) {
|
|
1162
|
+
cancel();
|
|
1163
|
+
onCancel();
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
#start(task, onCancel) {
|
|
1167
|
+
const cancel = () => {
|
|
1168
|
+
cancelTimer();
|
|
1169
|
+
this.#waiting.delete(cancel);
|
|
1170
|
+
};
|
|
1171
|
+
const cancelTimer = chunk3OHMEMZA_cjs.schedule(this.#clock, {
|
|
1172
|
+
run: () => {
|
|
1173
|
+
this.#waiting.delete(cancel);
|
|
1174
|
+
task.run();
|
|
1175
|
+
},
|
|
1176
|
+
delayMs: task.delayMs
|
|
1177
|
+
});
|
|
1178
|
+
this.#waiting.set(cancel, onCancel);
|
|
1179
|
+
return cancel;
|
|
1180
|
+
}
|
|
1181
|
+
};
|
|
1182
|
+
|
|
1183
|
+
// src/dispatcher-support.ts
|
|
1184
|
+
function pendingKey(delivery) {
|
|
1185
|
+
return `${delivery.deliveryId}\0${delivery.leaseToken}`;
|
|
1186
|
+
}
|
|
1187
|
+
function rejectOnAbort(signal) {
|
|
1188
|
+
return new Promise((_, reject) => {
|
|
1189
|
+
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
var storage = new async_hooks.AsyncLocalStorage();
|
|
1193
|
+
function runInScope(scope, work) {
|
|
1194
|
+
return storage.run({ ...storage.getStore(), ...scope }, work);
|
|
1195
|
+
}
|
|
1196
|
+
function runInIsolatedScope(scope, work) {
|
|
1197
|
+
return storage.run(scope, work);
|
|
1198
|
+
}
|
|
1199
|
+
function getCurrentScope() {
|
|
1200
|
+
return storage.getStore();
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// src/shutdown-deadline.ts
|
|
1204
|
+
var DRAIN_SHARE = 0.8;
|
|
1205
|
+
var ShutdownDeadline = class {
|
|
1206
|
+
#controller = new AbortController();
|
|
1207
|
+
#drained;
|
|
1208
|
+
#expired;
|
|
1209
|
+
#cancelTimers;
|
|
1210
|
+
constructor(clock, timeoutMs) {
|
|
1211
|
+
let drain;
|
|
1212
|
+
this.#drained = new Promise((resolve) => {
|
|
1213
|
+
drain = () => resolve("timeout");
|
|
1214
|
+
});
|
|
1215
|
+
const signal = this.#controller.signal;
|
|
1216
|
+
this.#expired = new Promise((resolve) => {
|
|
1217
|
+
signal.addEventListener("abort", () => resolve("timeout"), { once: true });
|
|
1218
|
+
});
|
|
1219
|
+
this.#cancelTimers = [
|
|
1220
|
+
chunk3OHMEMZA_cjs.schedule(clock, { run: drain, delayMs: Math.floor(timeoutMs * DRAIN_SHARE) }),
|
|
1221
|
+
chunk3OHMEMZA_cjs.schedule(clock, { run: () => this.#controller.abort(), delayMs: timeoutMs })
|
|
1222
|
+
];
|
|
1223
|
+
}
|
|
1224
|
+
/** Aborted when the deadline passes. */
|
|
1225
|
+
get signal() {
|
|
1226
|
+
return this.#controller.signal;
|
|
1227
|
+
}
|
|
1228
|
+
get passed() {
|
|
1229
|
+
return this.#controller.signal.aborted;
|
|
1230
|
+
}
|
|
1231
|
+
/** Waits for `work` until the deadline. Rejects when `work` rejects before it. */
|
|
1232
|
+
race(work) {
|
|
1233
|
+
return Promise.race([work.then(() => "done"), this.#expired]);
|
|
1234
|
+
}
|
|
1235
|
+
/** Waits for running handlers until the drain point, when they are aborted. */
|
|
1236
|
+
raceDrain(work) {
|
|
1237
|
+
return Promise.race([work.then(() => "done"), this.#drained]);
|
|
1238
|
+
}
|
|
1239
|
+
/** Cancels the timers once the shutdown is over. */
|
|
1240
|
+
dispose() {
|
|
1241
|
+
for (const cancel of this.#cancelTimers) {
|
|
1242
|
+
cancel();
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
};
|
|
1246
|
+
async function withinDeadline({ clock, timeoutMs }, work) {
|
|
1247
|
+
const startedAt = clock.now().getTime();
|
|
1248
|
+
const deadline = new ShutdownDeadline(clock, timeoutMs);
|
|
1249
|
+
try {
|
|
1250
|
+
const abandonedDeliveries = await work(deadline);
|
|
1251
|
+
return {
|
|
1252
|
+
graceful: !deadline.passed,
|
|
1253
|
+
abandonedDeliveries,
|
|
1254
|
+
durationMs: clock.now().getTime() - startedAt
|
|
1255
|
+
};
|
|
1256
|
+
} finally {
|
|
1257
|
+
deadline.dispose();
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// src/lease-heartbeat.ts
|
|
1262
|
+
var RENEWALS_PER_LEASE = 3;
|
|
1263
|
+
function startLeaseHeartbeat({ store, clock, notifier, settings }, { delivery, controller }) {
|
|
1264
|
+
let confirmedExpiry = delivery.leaseExpiresAt.getTime();
|
|
1265
|
+
let renewing = false;
|
|
1266
|
+
const abortLeaseLost = (storeConfirmed = false) => {
|
|
1267
|
+
controller.abort(
|
|
1268
|
+
new chunk3OHMEMZA_cjs.LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost`, { storeConfirmed })
|
|
1269
|
+
);
|
|
1270
|
+
};
|
|
1271
|
+
return chunk3OHMEMZA_cjs.schedule(clock, {
|
|
1272
|
+
run: () => {
|
|
1273
|
+
const now = clock.now();
|
|
1274
|
+
if (now.getTime() >= confirmedExpiry) {
|
|
1275
|
+
abortLeaseLost();
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
if (renewing) {
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
renewing = true;
|
|
1282
|
+
const expiry = now.getTime() + settings.leaseDurationMs;
|
|
1283
|
+
store.extendLease(delivery, new Date(expiry), now).then((extended) => {
|
|
1284
|
+
if (extended) {
|
|
1285
|
+
confirmedExpiry = expiry;
|
|
1286
|
+
} else {
|
|
1287
|
+
abortLeaseLost(true);
|
|
1288
|
+
}
|
|
1289
|
+
}).catch((error) => notifier.failure("extend-lease", error)).finally(() => {
|
|
1290
|
+
renewing = false;
|
|
1291
|
+
});
|
|
1292
|
+
},
|
|
1293
|
+
delayMs: Math.max(1, Math.floor(settings.leaseDurationMs / RENEWALS_PER_LEASE)),
|
|
1294
|
+
repeat: true,
|
|
1295
|
+
unref: true
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
// src/retry-policy.ts
|
|
1300
|
+
var DEFAULT_RETRY_POLICY = {
|
|
1301
|
+
attempts: 10,
|
|
1302
|
+
initialDelayMs: 1e3,
|
|
1303
|
+
factor: 2,
|
|
1304
|
+
maxDelayMs: 6e5,
|
|
1305
|
+
jitter: "full",
|
|
1306
|
+
retryIf: void 0
|
|
1307
|
+
};
|
|
1308
|
+
var DEFAULT_ROUTE_RETRY_POLICY = {
|
|
1309
|
+
attempts: Number.POSITIVE_INFINITY,
|
|
1310
|
+
initialDelayMs: 1e3,
|
|
1311
|
+
factor: 2,
|
|
1312
|
+
maxDelayMs: 3e5,
|
|
1313
|
+
jitter: "full",
|
|
1314
|
+
retryIf: void 0
|
|
1315
|
+
};
|
|
1316
|
+
var JITTER_STRATEGIES = ["full", "equal", "none"];
|
|
1317
|
+
function createRetryPolicy(options, base = DEFAULT_RETRY_POLICY) {
|
|
1318
|
+
const policy = mergeRetryPolicy(options, base);
|
|
1319
|
+
assertValidRetryPolicy(policy);
|
|
1320
|
+
return policy;
|
|
1321
|
+
}
|
|
1322
|
+
function mergeRetryPolicy(options, base) {
|
|
1323
|
+
const overrides = options ?? {};
|
|
1324
|
+
const initialDelayMs = overrides.initialDelay === void 0 ? base.initialDelayMs : toMilliseconds(overrides.initialDelay, "retry.initialDelay");
|
|
1325
|
+
return {
|
|
1326
|
+
attempts: overrides.attempts ?? base.attempts,
|
|
1327
|
+
initialDelayMs,
|
|
1328
|
+
factor: overrides.factor ?? base.factor,
|
|
1329
|
+
maxDelayMs: overrides.maxDelay === void 0 ? (
|
|
1330
|
+
// Raising only the initial delay must not trip the inherited cap.
|
|
1331
|
+
Math.max(base.maxDelayMs, initialDelayMs)
|
|
1332
|
+
) : toMilliseconds(overrides.maxDelay, "retry.maxDelay"),
|
|
1333
|
+
jitter: overrides.jitter ?? base.jitter,
|
|
1334
|
+
retryIf: overrides.retryIf ?? base.retryIf
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
function assertValidRetryPolicy(policy) {
|
|
1338
|
+
if (policy.attempts !== Number.POSITIVE_INFINITY && (!Number.isInteger(policy.attempts) || policy.attempts < 1)) {
|
|
1339
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
1340
|
+
`retry.attempts must be a positive integer or Infinity, received ${policy.attempts}`
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
if (!Number.isFinite(policy.factor) || policy.factor < 1) {
|
|
1344
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(`retry.factor must be at least 1, received ${policy.factor}`);
|
|
1345
|
+
}
|
|
1346
|
+
if (policy.maxDelayMs < policy.initialDelayMs) {
|
|
1347
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError("retry.maxDelay must not be smaller than retry.initialDelay");
|
|
1348
|
+
}
|
|
1349
|
+
if (!JITTER_STRATEGIES.includes(policy.jitter)) {
|
|
1350
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
1351
|
+
`retry.jitter must be one of ${JITTER_STRATEGIES.join(", ")}, received '${String(policy.jitter)}'`
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
function computeRetryDelay(policy, failedAttempts, random = Math.random) {
|
|
1356
|
+
const exponential = policy.initialDelayMs * policy.factor ** (failedAttempts - 1);
|
|
1357
|
+
const capped = Math.min(policy.maxDelayMs, exponential);
|
|
1358
|
+
switch (policy.jitter) {
|
|
1359
|
+
case "full":
|
|
1360
|
+
return Math.round(random() * capped);
|
|
1361
|
+
case "equal":
|
|
1362
|
+
return Math.round(capped / 2 + random() * capped / 2);
|
|
1363
|
+
case "none":
|
|
1364
|
+
return Math.round(capped);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
function isRetryableError(policy, error) {
|
|
1368
|
+
if (error instanceof chunk3OHMEMZA_cjs.NonRetryableError) {
|
|
1369
|
+
return false;
|
|
1370
|
+
}
|
|
1371
|
+
if (policy.retryIf === void 0) {
|
|
1372
|
+
return true;
|
|
1373
|
+
}
|
|
1374
|
+
try {
|
|
1375
|
+
return policy.retryIf(error);
|
|
1376
|
+
} catch {
|
|
1377
|
+
return true;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
// src/dispatcher.ts
|
|
1382
|
+
var FIRST_COMPLETION_RETRY_MS = 50;
|
|
1383
|
+
var SECOND_COMPLETION_RETRY_MS = 250;
|
|
1384
|
+
var LAST_COMPLETION_RETRY_MS = 1e3;
|
|
1385
|
+
var COMPLETION_RETRY_DELAYS_MS = [
|
|
1386
|
+
FIRST_COMPLETION_RETRY_MS,
|
|
1387
|
+
SECOND_COMPLETION_RETRY_MS,
|
|
1388
|
+
LAST_COMPLETION_RETRY_MS
|
|
1389
|
+
];
|
|
1390
|
+
var WAKE_COOLDOWN_MS = 5;
|
|
1391
|
+
var QUIET_REFILL_MS = 10;
|
|
1392
|
+
var REFILL_FRACTION = 0.7;
|
|
1393
|
+
var Dispatcher = class {
|
|
1394
|
+
#dependencies;
|
|
1395
|
+
#sleep;
|
|
1396
|
+
#inFlight = /* @__PURE__ */ new Set();
|
|
1397
|
+
#completions;
|
|
1398
|
+
#pending = /* @__PURE__ */ new Map();
|
|
1399
|
+
#recoveryTimer;
|
|
1400
|
+
#recovery;
|
|
1401
|
+
#running = false;
|
|
1402
|
+
#loop;
|
|
1403
|
+
#wakeUp;
|
|
1404
|
+
/** Free slots the claim the loop was woken for needs at least; undefined while nobody woke it. */
|
|
1405
|
+
#signalledMinimum;
|
|
1406
|
+
/** Free slots a claim needs unless the poll interval or the quiet refill time passed: claims refill in groups. */
|
|
1407
|
+
#refillThreshold;
|
|
1408
|
+
/** Handlers whose last claim got less than it asked for: the store has nothing more for them right now. */
|
|
1409
|
+
#exhaustedHandlers = /* @__PURE__ */ new Set();
|
|
1410
|
+
/** Grows with every wake-up: a claim that started before one must not mark a handler exhausted. */
|
|
1411
|
+
#wakeGeneration = 0;
|
|
1412
|
+
#lastClaimStartedAt = Number.NEGATIVE_INFINITY;
|
|
1413
|
+
#cooldownTimer;
|
|
1414
|
+
#refillScheduled = false;
|
|
1415
|
+
#quietRefillTimer;
|
|
1416
|
+
#unwatch;
|
|
1417
|
+
#watching;
|
|
1418
|
+
/** Set when a stop gave up at its deadline: the store may be closed, so nothing more is started or retried. */
|
|
1419
|
+
#abandoned = false;
|
|
1420
|
+
/** Stops the lease renewal (or transactional lease guard) of each delivery whose handler runs. */
|
|
1421
|
+
#leaseKeepers = /* @__PURE__ */ new Map();
|
|
1422
|
+
#timers;
|
|
1423
|
+
constructor(dependencies) {
|
|
1424
|
+
this.#dependencies = dependencies;
|
|
1425
|
+
this.#timers = new DeliveryTimers(dependencies.clock);
|
|
1426
|
+
this.#sleep = dependencies.sleep ?? ((milliseconds) => this.#timers.sleep(milliseconds));
|
|
1427
|
+
const { store, clock, settings } = dependencies;
|
|
1428
|
+
this.#refillThreshold = Math.ceil(
|
|
1429
|
+
Math.min(settings.concurrency, settings.batchSize) * REFILL_FRACTION
|
|
1430
|
+
);
|
|
1431
|
+
const { completionBatchWindowMs: windowMs = 0, completionBatchSize: maxBatchSize = 1 } = settings;
|
|
1432
|
+
if (store.completeMany !== void 0 && windowMs > 0) {
|
|
1433
|
+
this.#completions = new CompletionBatcher(
|
|
1434
|
+
{ completeMany: (leases, now) => store.completeMany(leases, now) },
|
|
1435
|
+
clock,
|
|
1436
|
+
{ windowMs, maxBatchSize },
|
|
1437
|
+
() => this.#inFlight.size
|
|
1438
|
+
);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
get isRunning() {
|
|
1442
|
+
return this.#running;
|
|
1443
|
+
}
|
|
1444
|
+
get inFlightCount() {
|
|
1445
|
+
return this.#inFlight.size;
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* True while the store reports new deliveries to this dispatcher by itself (see
|
|
1449
|
+
* {@link EventStore.watchNewDeliveries}), from every process: a publisher then has no reason to wake it.
|
|
1450
|
+
*/
|
|
1451
|
+
get watchesNewDeliveries() {
|
|
1452
|
+
return this.#unwatch !== void 0;
|
|
1453
|
+
}
|
|
1454
|
+
start() {
|
|
673
1455
|
if (this.#running) {
|
|
674
1456
|
return;
|
|
675
1457
|
}
|
|
676
1458
|
this.#running = true;
|
|
1459
|
+
this.#completions?.setClosing(false);
|
|
1460
|
+
this.#watching = this.#watchNewDeliveries();
|
|
677
1461
|
this.#loop = this.#runLoop();
|
|
678
1462
|
}
|
|
679
|
-
/**
|
|
1463
|
+
/**
|
|
1464
|
+
* Asks for a claim soon, for example right after a publish: at once after a quiet period, otherwise
|
|
1465
|
+
* at the end of a short cooldown that merges the wake-ups arriving meanwhile into one claim.
|
|
1466
|
+
*/
|
|
680
1467
|
wake() {
|
|
681
|
-
this.#
|
|
1468
|
+
this.#exhaustedHandlers.clear();
|
|
1469
|
+
this.#wakeGeneration += 1;
|
|
1470
|
+
const wait = this.#lastClaimStartedAt + WAKE_COOLDOWN_MS - Date.now();
|
|
1471
|
+
if (wait <= 0) {
|
|
1472
|
+
this.#requestClaim();
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
this.#cooldownTimer ??= chunk3OHMEMZA_cjs.schedule(this.#dependencies.clock, {
|
|
1476
|
+
run: () => {
|
|
1477
|
+
this.#cooldownTimer = void 0;
|
|
1478
|
+
this.#requestClaim();
|
|
1479
|
+
},
|
|
1480
|
+
delayMs: wait
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
/** Ends the current wait of the claim loop, or the next one at once, for a claim once `minimumFree` slots are free. */
|
|
1484
|
+
#signal(minimumFree) {
|
|
1485
|
+
this.#signalledMinimum = Math.min(this.#signalledMinimum ?? minimumFree, minimumFree);
|
|
682
1486
|
this.#wakeUp?.();
|
|
683
1487
|
}
|
|
684
1488
|
/**
|
|
685
|
-
*
|
|
686
|
-
*
|
|
1489
|
+
* Starts listening to the store's reports of new deliveries. Polling stays the safety net: a store
|
|
1490
|
+
* that cannot listen, or fails to, only costs latency, so a failure is reported and otherwise ignored.
|
|
687
1491
|
*/
|
|
688
|
-
async
|
|
1492
|
+
async #watchNewDeliveries() {
|
|
1493
|
+
const { store, notifier } = this.#dependencies;
|
|
1494
|
+
try {
|
|
1495
|
+
const unwatch = await store.watchNewDeliveries?.(() => this.wake());
|
|
1496
|
+
if (unwatch !== void 0 && !this.#running) {
|
|
1497
|
+
await unwatch();
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
this.#unwatch = unwatch;
|
|
1501
|
+
if (unwatch !== void 0) {
|
|
1502
|
+
this.wake();
|
|
1503
|
+
}
|
|
1504
|
+
} catch (error) {
|
|
1505
|
+
notifier.failure("watch", error);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
/** Stops listening to the store's reports of new deliveries. */
|
|
1509
|
+
async #stopWatching() {
|
|
1510
|
+
await this.#watching;
|
|
1511
|
+
const unwatch = this.#unwatch;
|
|
1512
|
+
this.#unwatch = void 0;
|
|
1513
|
+
try {
|
|
1514
|
+
await unwatch?.();
|
|
1515
|
+
} catch (error) {
|
|
1516
|
+
this.#dependencies.notifier.failure("watch", error);
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Stops claiming and waits for running handlers until the drain point of `deadline` (default: the
|
|
1521
|
+
* shutdown timeout from now), then aborts the rest and releases their deliveries for another
|
|
1522
|
+
* process. Deliveries still in flight when the deadline itself passes are abandoned: their leases
|
|
1523
|
+
* expire and they run again elsewhere. Resolves with how many were abandoned.
|
|
1524
|
+
*/
|
|
1525
|
+
async stop(deadline) {
|
|
1526
|
+
const { clock, settings } = this.#dependencies;
|
|
1527
|
+
const own = deadline ?? new ShutdownDeadline(clock, settings.shutdownTimeoutMs);
|
|
1528
|
+
try {
|
|
1529
|
+
return await this.#stopWithin(own);
|
|
1530
|
+
} finally {
|
|
1531
|
+
if (deadline === void 0) {
|
|
1532
|
+
own.dispose();
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
async #stopWithin(deadline) {
|
|
689
1537
|
if (!this.#running) {
|
|
690
|
-
|
|
1538
|
+
await this.#closeRecovery(deadline);
|
|
1539
|
+
return 0;
|
|
691
1540
|
}
|
|
692
1541
|
this.#running = false;
|
|
693
|
-
this
|
|
694
|
-
|
|
695
|
-
|
|
1542
|
+
this.#completions?.setClosing(true);
|
|
1543
|
+
this.#cooldownTimer?.();
|
|
1544
|
+
this.#cooldownTimer = void 0;
|
|
1545
|
+
this.#quietRefillTimer?.();
|
|
1546
|
+
this.#quietRefillTimer = void 0;
|
|
1547
|
+
this.#signal(this.#refillThreshold);
|
|
1548
|
+
await deadline.race(this.#stopWatching());
|
|
1549
|
+
await deadline.race(this.#loop);
|
|
696
1550
|
const settled = Promise.allSettled([...this.#inFlight].map((delivery) => delivery.settled));
|
|
697
|
-
if (await
|
|
1551
|
+
if (await deadline.raceDrain(settled) === "timeout") {
|
|
698
1552
|
for (const delivery of this.#inFlight) {
|
|
699
|
-
delivery.controller.abort(new DispatcherStoppedError("relaybus dispatcher stopped"));
|
|
1553
|
+
delivery.controller.abort(new chunk3OHMEMZA_cjs.DispatcherStoppedError("relaybus dispatcher stopped"));
|
|
1554
|
+
}
|
|
1555
|
+
if (await deadline.race(settled) === "timeout") {
|
|
1556
|
+
return this.#abandon();
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
await this.#closeRecovery(deadline);
|
|
1560
|
+
await deadline.race(this.#relinquish());
|
|
1561
|
+
return 0;
|
|
1562
|
+
}
|
|
1563
|
+
/**
|
|
1564
|
+
* The deadline passed with deliveries still in flight (aborted, their release not answered): forgets
|
|
1565
|
+
* them and every settlement still to retry. Whatever they release or complete from now on may reach a
|
|
1566
|
+
* closed store; a delivery whose state change is lost stays leased until the lease expires and then
|
|
1567
|
+
* runs again (at-least-once).
|
|
1568
|
+
*/
|
|
1569
|
+
#abandon() {
|
|
1570
|
+
this.#abandoned = true;
|
|
1571
|
+
for (const stopKeeping of this.#leaseKeepers.values()) {
|
|
1572
|
+
stopKeeping();
|
|
1573
|
+
}
|
|
1574
|
+
this.#leaseKeepers.clear();
|
|
1575
|
+
this.#timers.cancelAll();
|
|
1576
|
+
this.#recoveryTimer?.();
|
|
1577
|
+
this.#recoveryTimer = void 0;
|
|
1578
|
+
this.#pending.clear();
|
|
1579
|
+
return this.#inFlight.size;
|
|
1580
|
+
}
|
|
1581
|
+
/**
|
|
1582
|
+
* Gives the settlements still pending one last try and forgets them. A pass of the timer may be inside the
|
|
1583
|
+
* store: it ends before the last pass starts. A store that does not answer before the deadline gets no
|
|
1584
|
+
* further call, since it may be closed right after.
|
|
1585
|
+
*/
|
|
1586
|
+
async #closeRecovery(deadline) {
|
|
1587
|
+
this.#recoveryTimer?.();
|
|
1588
|
+
this.#recoveryTimer = void 0;
|
|
1589
|
+
const lastTry = (async () => {
|
|
1590
|
+
await this.#recovery;
|
|
1591
|
+
await this.#recoverSettlements();
|
|
1592
|
+
})();
|
|
1593
|
+
await deadline.race(lastTry);
|
|
1594
|
+
this.#pending.clear();
|
|
1595
|
+
}
|
|
1596
|
+
/**
|
|
1597
|
+
* Retries every pending settlement once; a pass that is already running is the one returned. A store that
|
|
1598
|
+
* still fails leaves the settlement for the next attempt (it was reported when it first failed).
|
|
1599
|
+
*/
|
|
1600
|
+
#recoverSettlements() {
|
|
1601
|
+
this.#recovery ??= this.#recoverAll().finally(() => {
|
|
1602
|
+
this.#recovery = void 0;
|
|
1603
|
+
});
|
|
1604
|
+
return this.#recovery;
|
|
1605
|
+
}
|
|
1606
|
+
async #recoverAll() {
|
|
1607
|
+
for (const [key, entry] of [...this.#pending]) {
|
|
1608
|
+
if (this.#pending.get(key) !== entry) {
|
|
1609
|
+
continue;
|
|
1610
|
+
}
|
|
1611
|
+
try {
|
|
1612
|
+
const applied = await entry.settle();
|
|
1613
|
+
this.#pending.delete(pendingKey(entry.delivery));
|
|
1614
|
+
if (applied) {
|
|
1615
|
+
entry.applied();
|
|
1616
|
+
} else {
|
|
1617
|
+
this.#dependencies.notifier.notify("onLeaseLost", { delivery: entry.delivery });
|
|
1618
|
+
}
|
|
1619
|
+
} catch {
|
|
700
1620
|
}
|
|
701
|
-
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
#retain(entry) {
|
|
1624
|
+
if (this.#abandoned) {
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
this.#pending.set(pendingKey(entry.delivery), entry);
|
|
1628
|
+
this.#recoveryTimer ??= chunk3OHMEMZA_cjs.schedule(this.#dependencies.clock, {
|
|
1629
|
+
run: () => void this.#recoverSettlements(),
|
|
1630
|
+
delayMs: this.#dependencies.settings.pollIntervalMs,
|
|
1631
|
+
repeat: true,
|
|
1632
|
+
unref: true
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
/** Gives lease groups the owner no longer works on back to the store, so other workers need not wait for expiry. */
|
|
1636
|
+
async #relinquish() {
|
|
1637
|
+
const { store, clock, notifier, settings } = this.#dependencies;
|
|
1638
|
+
try {
|
|
1639
|
+
await store.relinquish?.(settings.owner, clock.now());
|
|
1640
|
+
} catch (error) {
|
|
1641
|
+
notifier.failure("relinquish", error);
|
|
702
1642
|
}
|
|
703
1643
|
}
|
|
704
1644
|
/** Claims one batch and resolves when every claimed delivery settled. Returns how many ran. */
|
|
705
1645
|
async processBatch() {
|
|
706
|
-
const started = await this.#claimAndStart();
|
|
1646
|
+
const { started } = await this.#claimAndStart();
|
|
707
1647
|
await Promise.allSettled(started.map((delivery) => delivery.settled));
|
|
708
1648
|
return started.length;
|
|
709
1649
|
}
|
|
710
1650
|
async #waitUntil(work, deadline) {
|
|
711
|
-
let
|
|
1651
|
+
let cancel;
|
|
712
1652
|
const timedOut = new Promise((resolve) => {
|
|
713
|
-
|
|
1653
|
+
cancel = chunk3OHMEMZA_cjs.schedule(this.#dependencies.clock, {
|
|
1654
|
+
run: () => resolve("timeout"),
|
|
1655
|
+
delayMs: Math.max(0, deadline - Date.now())
|
|
1656
|
+
});
|
|
714
1657
|
});
|
|
715
1658
|
const outcome = await Promise.race([work.then(() => "done"), timedOut]);
|
|
716
|
-
|
|
1659
|
+
cancel?.();
|
|
717
1660
|
return outcome;
|
|
718
1661
|
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Claims until stopped. A claim that filled its batch is followed by the next one at once while enough
|
|
1664
|
+
* slots are free. Otherwise the loop sleeps until a wake-up (a publish, a store notification, or enough
|
|
1665
|
+
* deliveries settling to refill a group of slots) or the poll interval. Only the poll interval claims
|
|
1666
|
+
* for fewer free slots than the refill threshold, so a busy dispatcher never claims slot by slot.
|
|
1667
|
+
*/
|
|
719
1668
|
async #runLoop() {
|
|
1669
|
+
let minimumFree = 1;
|
|
720
1670
|
while (this.#running) {
|
|
721
|
-
let
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
1671
|
+
let round = { requested: 0, claimed: 0 };
|
|
1672
|
+
if (this.#freeSlots() >= minimumFree) {
|
|
1673
|
+
try {
|
|
1674
|
+
round = await this.#claimAndStart();
|
|
1675
|
+
} catch (error) {
|
|
1676
|
+
this.#dependencies.notifier.failure("claim", error);
|
|
1677
|
+
}
|
|
726
1678
|
}
|
|
727
|
-
if (
|
|
1679
|
+
if (round.requested > 0 && round.claimed === round.requested && this.#freeSlots() >= this.#refillThreshold) {
|
|
1680
|
+
minimumFree = this.#refillThreshold;
|
|
728
1681
|
continue;
|
|
729
1682
|
}
|
|
730
|
-
await this.#waitForWakeUp();
|
|
1683
|
+
minimumFree = await this.#waitForWakeUp();
|
|
1684
|
+
if (this.#freeSlots() < minimumFree) {
|
|
1685
|
+
this.#requestClaim();
|
|
1686
|
+
}
|
|
731
1687
|
}
|
|
732
1688
|
}
|
|
1689
|
+
/** Waits for a wake-up or the poll interval. Resolves with the free slots the next claim needs. */
|
|
733
1690
|
async #waitForWakeUp() {
|
|
734
|
-
if (this.#
|
|
735
|
-
|
|
736
|
-
|
|
1691
|
+
if (this.#signalledMinimum === void 0) {
|
|
1692
|
+
await new Promise((resolve) => {
|
|
1693
|
+
const cancel = chunk3OHMEMZA_cjs.schedule(this.#dependencies.clock, {
|
|
1694
|
+
run: () => {
|
|
1695
|
+
this.#signalledMinimum = 1;
|
|
1696
|
+
resolve();
|
|
1697
|
+
},
|
|
1698
|
+
delayMs: this.#dependencies.settings.pollIntervalMs
|
|
1699
|
+
});
|
|
1700
|
+
this.#wakeUp = () => {
|
|
1701
|
+
cancel();
|
|
1702
|
+
resolve();
|
|
1703
|
+
};
|
|
1704
|
+
});
|
|
1705
|
+
this.#wakeUp = void 0;
|
|
737
1706
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
1707
|
+
const minimumFree = this.#signalledMinimum;
|
|
1708
|
+
this.#signalledMinimum = void 0;
|
|
1709
|
+
return minimumFree;
|
|
1710
|
+
}
|
|
1711
|
+
/**
|
|
1712
|
+
* A delivery settled. Its slot is worth refilling when the store may have more for its handler: a keyed
|
|
1713
|
+
* delivery unblocks the next one of its key, a delivery that did not succeed may be due again at once
|
|
1714
|
+
* (a retry without delay, a release), and a handler whose last claim was full may have a backlog.
|
|
1715
|
+
*/
|
|
1716
|
+
#onSettled(delivery, succeeded) {
|
|
1717
|
+
if (delivery.event.key !== null || !succeeded || !this.#exhaustedHandlers.has(delivery.handler)) {
|
|
1718
|
+
this.#requestClaim();
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
/**
|
|
1722
|
+
* Asks the loop for a claim. Once a group of slots is free the claim waits only for the deliveries
|
|
1723
|
+
* settling in the same tick, so they are claimed for together; fewer free slots are refilled when no
|
|
1724
|
+
* claim started for a short while. With no free slot the next settlement asks again.
|
|
1725
|
+
*/
|
|
1726
|
+
#requestClaim() {
|
|
1727
|
+
const free = this.#freeSlots();
|
|
1728
|
+
if (free >= this.#refillThreshold) {
|
|
1729
|
+
if (!this.#refillScheduled) {
|
|
1730
|
+
this.#refillScheduled = true;
|
|
1731
|
+
setImmediate(() => {
|
|
1732
|
+
this.#refillScheduled = false;
|
|
1733
|
+
this.#signal(this.#refillThreshold);
|
|
1734
|
+
});
|
|
743
1735
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
1736
|
+
} else if (free > 0) {
|
|
1737
|
+
this.#quietRefillTimer ??= chunk3OHMEMZA_cjs.schedule(this.#dependencies.clock, {
|
|
1738
|
+
run: () => {
|
|
1739
|
+
this.#quietRefillTimer = void 0;
|
|
1740
|
+
this.#signal(1);
|
|
1741
|
+
},
|
|
1742
|
+
delayMs: Math.max(0, this.#lastClaimStartedAt + QUIET_REFILL_MS - Date.now())
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
748
1745
|
}
|
|
749
1746
|
#freeSlots() {
|
|
750
1747
|
return this.#dependencies.settings.concurrency - this.#inFlight.size;
|
|
751
1748
|
}
|
|
1749
|
+
/** Claims for every handler with room and starts what was claimed. */
|
|
752
1750
|
async #claimAndStart() {
|
|
753
|
-
const { store, registry, clock, settings } = this.#dependencies;
|
|
1751
|
+
const { store, registry, clock, settings, observation } = this.#dependencies;
|
|
754
1752
|
const started = [];
|
|
1753
|
+
let requested = 0;
|
|
755
1754
|
for (const request of this.#planClaims(registry.all())) {
|
|
756
1755
|
const now = clock.now();
|
|
757
|
-
const
|
|
1756
|
+
const wakeGeneration = this.#wakeGeneration;
|
|
1757
|
+
this.#lastClaimStartedAt = Date.now();
|
|
1758
|
+
this.#quietRefillTimer?.();
|
|
1759
|
+
this.#quietRefillTimer = void 0;
|
|
1760
|
+
requested += request.limit;
|
|
1761
|
+
const claim = {
|
|
758
1762
|
handlers: request.handlers,
|
|
759
1763
|
limit: request.limit,
|
|
760
1764
|
leaseOwner: settings.owner,
|
|
761
1765
|
leaseExpiresAt: new Date(now.getTime() + settings.leaseDurationMs),
|
|
762
1766
|
now
|
|
763
|
-
}
|
|
764
|
-
|
|
1767
|
+
};
|
|
1768
|
+
const claimed = await (observation === void 0 ? store.claim(claim) : observation.claim(store, claim));
|
|
1769
|
+
for (const handler of request.handlers) {
|
|
1770
|
+
if (claimed.length < request.limit && wakeGeneration === this.#wakeGeneration) {
|
|
1771
|
+
this.#exhaustedHandlers.add(handler);
|
|
1772
|
+
} else {
|
|
1773
|
+
this.#exhaustedHandlers.delete(handler);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
for (const delivery of this.#abandoned ? [] : claimed) {
|
|
765
1777
|
started.push(this.#start(delivery));
|
|
766
1778
|
}
|
|
767
1779
|
}
|
|
768
|
-
return started;
|
|
1780
|
+
return { requested, claimed: started.length, started };
|
|
769
1781
|
}
|
|
770
1782
|
/** One claim for handlers without a concurrency limit, one per limited handler with room. */
|
|
771
1783
|
#planClaims(registrations) {
|
|
@@ -776,22 +1788,25 @@ var Dispatcher = class {
|
|
|
776
1788
|
if (registration.concurrency === null || free === 0) {
|
|
777
1789
|
continue;
|
|
778
1790
|
}
|
|
779
|
-
const room = registration.concurrency - this.#countInFlight(registration
|
|
1791
|
+
const room = registration.concurrency - this.#countInFlight(registration);
|
|
780
1792
|
const limit = Math.min(room, free);
|
|
781
1793
|
if (limit > 0) {
|
|
782
|
-
plans.push({ handlers: [registration.
|
|
1794
|
+
plans.push({ handlers: [...registration.claimIds], limit });
|
|
783
1795
|
free -= limit;
|
|
784
1796
|
}
|
|
785
1797
|
}
|
|
786
1798
|
if (unlimited.length > 0 && free > 0) {
|
|
787
|
-
plans.push({
|
|
1799
|
+
plans.push({
|
|
1800
|
+
handlers: unlimited.flatMap((registration) => registration.claimIds),
|
|
1801
|
+
limit: free
|
|
1802
|
+
});
|
|
788
1803
|
}
|
|
789
1804
|
return plans;
|
|
790
1805
|
}
|
|
791
|
-
#countInFlight(
|
|
1806
|
+
#countInFlight(registration) {
|
|
792
1807
|
let count = 0;
|
|
793
1808
|
for (const delivery of this.#inFlight) {
|
|
794
|
-
if (delivery.handler
|
|
1809
|
+
if (registration.claimIds.includes(delivery.handler)) {
|
|
795
1810
|
count += 1;
|
|
796
1811
|
}
|
|
797
1812
|
}
|
|
@@ -799,56 +1814,82 @@ var Dispatcher = class {
|
|
|
799
1814
|
}
|
|
800
1815
|
#start(delivery) {
|
|
801
1816
|
const controller = new AbortController();
|
|
1817
|
+
let succeeded = false;
|
|
802
1818
|
const entry = {
|
|
803
1819
|
handler: delivery.handler,
|
|
804
1820
|
controller,
|
|
805
|
-
settled: Promise.resolve().then(
|
|
1821
|
+
settled: Promise.resolve().then(async () => {
|
|
1822
|
+
succeeded = await this.#execute(delivery, controller);
|
|
1823
|
+
}).catch((error) => this.#dependencies.notifier.failure("dispatch", error)).finally(() => {
|
|
806
1824
|
this.#inFlight.delete(entry);
|
|
807
|
-
this
|
|
1825
|
+
this.#onSettled(delivery, succeeded);
|
|
808
1826
|
})
|
|
809
1827
|
};
|
|
810
1828
|
this.#inFlight.add(entry);
|
|
811
1829
|
return entry;
|
|
812
1830
|
}
|
|
1831
|
+
/** Runs one delivery. Resolves true when its handler succeeded, so the delivery is done for good. */
|
|
813
1832
|
async #execute(delivery, controller) {
|
|
814
1833
|
const { registry, notifier, clock } = this.#dependencies;
|
|
815
1834
|
const registration = registry.get(delivery.handler);
|
|
1835
|
+
for (const key of [...this.#pending.keys()].filter(
|
|
1836
|
+
(pending) => pending.startsWith(`${delivery.deliveryId}\0`)
|
|
1837
|
+
)) {
|
|
1838
|
+
this.#pending.delete(key);
|
|
1839
|
+
}
|
|
816
1840
|
notifier.notify("onDeliveryStarted", { delivery });
|
|
817
1841
|
const startedAt = clock.now().getTime();
|
|
818
1842
|
if (delivery.attempt > registration.retry.attempts) {
|
|
819
1843
|
await this.#bury(
|
|
820
1844
|
delivery,
|
|
821
|
-
new
|
|
1845
|
+
new chunk3OHMEMZA_cjs.AttemptsExhaustedError(
|
|
822
1846
|
`Delivery reached attempt ${delivery.attempt} of ${registration.retry.attempts} after its lease expired`
|
|
823
1847
|
)
|
|
824
1848
|
);
|
|
825
|
-
return;
|
|
1849
|
+
return false;
|
|
826
1850
|
}
|
|
827
1851
|
let stopHeartbeat = () => void 0;
|
|
828
1852
|
try {
|
|
829
|
-
stopHeartbeat = registration.transactional ? await this.#guardTransactionalLease(registration, delivery, controller) : this.#
|
|
830
|
-
|
|
1853
|
+
stopHeartbeat = registration.transactional ? await this.#guardTransactionalLease(registration, { delivery, controller }) : startLeaseHeartbeat(this.#dependencies, { delivery, controller });
|
|
1854
|
+
this.#leaseKeepers.set(delivery, stopHeartbeat);
|
|
1855
|
+
const outcome = await this.#runHandler(registration, { delivery, controller });
|
|
1856
|
+
const durationMs = clock.now().getTime() - startedAt;
|
|
831
1857
|
if (outcome === "completed") {
|
|
832
|
-
notifier.notify("onDeliverySucceeded", {
|
|
1858
|
+
notifier.notify("onDeliverySucceeded", { delivery, durationMs });
|
|
1859
|
+
} else {
|
|
1860
|
+
this.#retain({
|
|
833
1861
|
delivery,
|
|
834
|
-
|
|
1862
|
+
settle: () => this.#complete(delivery),
|
|
1863
|
+
applied: () => notifier.notify("onDeliverySucceeded", { delivery, durationMs })
|
|
835
1864
|
});
|
|
836
1865
|
}
|
|
1866
|
+
return true;
|
|
837
1867
|
} catch (error) {
|
|
838
|
-
if (
|
|
1868
|
+
if (this.#abandoned) {
|
|
1869
|
+
return false;
|
|
1870
|
+
}
|
|
1871
|
+
if (error instanceof chunk3OHMEMZA_cjs.LeaseLostError) {
|
|
839
1872
|
notifier.notify("onLeaseLost", { delivery });
|
|
840
|
-
|
|
1873
|
+
if (!error.storeConfirmed) {
|
|
1874
|
+
await this.#giveBack(registration, { delivery, error });
|
|
1875
|
+
}
|
|
1876
|
+
} else if (error instanceof chunk3OHMEMZA_cjs.DispatcherStoppedError) {
|
|
841
1877
|
await this.#release(delivery);
|
|
842
1878
|
} else {
|
|
843
|
-
await this.#fail(registration, delivery, error);
|
|
1879
|
+
await this.#fail(registration, { delivery, error });
|
|
844
1880
|
}
|
|
1881
|
+
return false;
|
|
845
1882
|
} finally {
|
|
846
1883
|
stopHeartbeat();
|
|
1884
|
+
this.#leaseKeepers.delete(delivery);
|
|
847
1885
|
}
|
|
848
1886
|
}
|
|
849
|
-
async #runHandler(registration,
|
|
1887
|
+
async #runHandler(registration, {
|
|
1888
|
+
delivery,
|
|
1889
|
+
controller
|
|
1890
|
+
}) {
|
|
850
1891
|
const { store, clock } = this.#dependencies;
|
|
851
|
-
const payload = await validatePayload(
|
|
1892
|
+
const payload = await chunk3OHMEMZA_cjs.validatePayload(
|
|
852
1893
|
delivery.event.type,
|
|
853
1894
|
registration.schema,
|
|
854
1895
|
delivery.event.payload
|
|
@@ -856,14 +1897,16 @@ var Dispatcher = class {
|
|
|
856
1897
|
const event = { ...delivery.event, payload };
|
|
857
1898
|
if (registration.transactional) {
|
|
858
1899
|
await store.transaction(async (transaction) => {
|
|
859
|
-
await this.#invoke(registration, event, delivery, controller, transaction);
|
|
1900
|
+
await this.#invoke({ registration, event, delivery, controller, transaction });
|
|
860
1901
|
if (!await store.complete(delivery, clock.now(), transaction)) {
|
|
861
|
-
throw new LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost
|
|
1902
|
+
throw new chunk3OHMEMZA_cjs.LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost`, {
|
|
1903
|
+
storeConfirmed: true
|
|
1904
|
+
});
|
|
862
1905
|
}
|
|
863
1906
|
});
|
|
864
1907
|
return "completed";
|
|
865
1908
|
}
|
|
866
|
-
await this.#invoke(registration, event, delivery, controller, void 0);
|
|
1909
|
+
await this.#invoke({ registration, event, delivery, controller, transaction: void 0 });
|
|
867
1910
|
return this.#confirmCompletion(delivery);
|
|
868
1911
|
}
|
|
869
1912
|
/**
|
|
@@ -871,11 +1914,14 @@ var Dispatcher = class {
|
|
|
871
1914
|
* still held. Gives up to lease expiry, which reruns the handler: at-least-once.
|
|
872
1915
|
*/
|
|
873
1916
|
async #confirmCompletion(delivery) {
|
|
874
|
-
const {
|
|
1917
|
+
const { notifier } = this.#dependencies;
|
|
875
1918
|
for (let attempt = 0; ; attempt += 1) {
|
|
1919
|
+
if (this.#abandoned) {
|
|
1920
|
+
return "unconfirmed";
|
|
1921
|
+
}
|
|
876
1922
|
let completed;
|
|
877
1923
|
try {
|
|
878
|
-
completed = await
|
|
1924
|
+
completed = await this.#complete(delivery);
|
|
879
1925
|
} catch (error) {
|
|
880
1926
|
const delay = COMPLETION_RETRY_DELAYS_MS[attempt];
|
|
881
1927
|
if (delay === void 0) {
|
|
@@ -886,12 +1932,20 @@ var Dispatcher = class {
|
|
|
886
1932
|
continue;
|
|
887
1933
|
}
|
|
888
1934
|
if (!completed) {
|
|
889
|
-
throw new LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost
|
|
1935
|
+
throw new chunk3OHMEMZA_cjs.LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost`, {
|
|
1936
|
+
storeConfirmed: true
|
|
1937
|
+
});
|
|
890
1938
|
}
|
|
891
1939
|
return "completed";
|
|
892
1940
|
}
|
|
893
1941
|
}
|
|
894
|
-
|
|
1942
|
+
/** Records one completion, grouped with its neighbours when the store can complete many at once. */
|
|
1943
|
+
#complete(delivery) {
|
|
1944
|
+
const { store, clock } = this.#dependencies;
|
|
1945
|
+
return this.#completions === void 0 ? store.complete(delivery, clock.now()) : this.#completions.submit(delivery);
|
|
1946
|
+
}
|
|
1947
|
+
async #invoke(input) {
|
|
1948
|
+
const { registration, event, delivery, controller, transaction } = input;
|
|
895
1949
|
const context = {
|
|
896
1950
|
deliveryId: delivery.deliveryId,
|
|
897
1951
|
handler: delivery.handler,
|
|
@@ -904,28 +1958,37 @@ var Dispatcher = class {
|
|
|
904
1958
|
if (controller.signal.aborted) {
|
|
905
1959
|
throw controller.signal.reason;
|
|
906
1960
|
}
|
|
1961
|
+
const handle = () => runInIsolatedScope(scope, () => registration.handle(event, context));
|
|
1962
|
+
const { observation } = this.#dependencies;
|
|
907
1963
|
const work = Promise.resolve().then(
|
|
908
|
-
|
|
1964
|
+
observation === void 0 ? handle : () => observation.process({ registration, delivery }, handle)
|
|
909
1965
|
);
|
|
910
1966
|
work.catch(() => void 0);
|
|
911
|
-
let
|
|
1967
|
+
let cancelTimeout;
|
|
912
1968
|
if (registration.timeoutMs !== null) {
|
|
913
1969
|
const timeoutMs = registration.timeoutMs;
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
1970
|
+
cancelTimeout = this.#timers.start({
|
|
1971
|
+
run: () => {
|
|
1972
|
+
controller.abort(
|
|
1973
|
+
new chunk3OHMEMZA_cjs.HandlerTimeoutError(
|
|
1974
|
+
`Handler '${registration.id}' did not settle within ${timeoutMs}ms`
|
|
1975
|
+
)
|
|
1976
|
+
);
|
|
1977
|
+
},
|
|
1978
|
+
delayMs: timeoutMs
|
|
1979
|
+
});
|
|
921
1980
|
}
|
|
922
1981
|
try {
|
|
923
|
-
|
|
1982
|
+
const settled = Promise.race([work, rejectOnAbort(controller.signal)]);
|
|
1983
|
+
await (observation === void 0 ? settled : observation.observe({ registration, delivery }, settled));
|
|
924
1984
|
} catch (error) {
|
|
925
|
-
await this.#awaitAbandonedHandler(work,
|
|
1985
|
+
await this.#awaitAbandonedHandler(work, {
|
|
1986
|
+
reason: error,
|
|
1987
|
+
transactional: transaction !== void 0
|
|
1988
|
+
});
|
|
926
1989
|
throw error;
|
|
927
1990
|
} finally {
|
|
928
|
-
|
|
1991
|
+
cancelTimeout?.();
|
|
929
1992
|
}
|
|
930
1993
|
}
|
|
931
1994
|
/**
|
|
@@ -935,7 +1998,7 @@ var Dispatcher = class {
|
|
|
935
1998
|
* Transactional handlers are always awaited; others for one lease duration,
|
|
936
1999
|
* except at shutdown, which already waited.
|
|
937
2000
|
*/
|
|
938
|
-
async #awaitAbandonedHandler(work, reason, transactional) {
|
|
2001
|
+
async #awaitAbandonedHandler(work, { reason, transactional }) {
|
|
939
2002
|
const settledWork = work.then(
|
|
940
2003
|
() => void 0,
|
|
941
2004
|
() => void 0
|
|
@@ -944,7 +2007,7 @@ var Dispatcher = class {
|
|
|
944
2007
|
await settledWork;
|
|
945
2008
|
return;
|
|
946
2009
|
}
|
|
947
|
-
if (reason instanceof DispatcherStoppedError) {
|
|
2010
|
+
if (reason instanceof chunk3OHMEMZA_cjs.DispatcherStoppedError) {
|
|
948
2011
|
return;
|
|
949
2012
|
}
|
|
950
2013
|
const deadline = Date.now() + this.#dependencies.settings.leaseDurationMs;
|
|
@@ -957,72 +2020,42 @@ var Dispatcher = class {
|
|
|
957
2020
|
* lease is extended once to cover the handler timeout, and the handler is
|
|
958
2021
|
* aborted when that lease ends.
|
|
959
2022
|
*/
|
|
960
|
-
async #guardTransactionalLease(registration,
|
|
2023
|
+
async #guardTransactionalLease(registration, {
|
|
2024
|
+
delivery,
|
|
2025
|
+
controller
|
|
2026
|
+
}) {
|
|
961
2027
|
const { store, clock, settings } = this.#dependencies;
|
|
962
2028
|
const now = clock.now();
|
|
963
2029
|
let expiry = delivery.leaseExpiresAt.getTime();
|
|
964
2030
|
if (registration.timeoutMs !== null && now.getTime() + registration.timeoutMs >= expiry) {
|
|
965
2031
|
expiry = now.getTime() + registration.timeoutMs + settings.leaseDurationMs;
|
|
966
2032
|
if (!await store.extendLease(delivery, new Date(expiry), now)) {
|
|
967
|
-
throw new LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost
|
|
2033
|
+
throw new chunk3OHMEMZA_cjs.LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost`, {
|
|
2034
|
+
storeConfirmed: true
|
|
2035
|
+
});
|
|
968
2036
|
}
|
|
969
2037
|
}
|
|
970
|
-
|
|
971
|
-
() => {
|
|
972
|
-
controller.abort(new LeaseLostError(`Lease on delivery ${delivery.deliveryId} expired`));
|
|
973
|
-
},
|
|
974
|
-
Math.max(0, expiry - clock.now().getTime())
|
|
975
|
-
);
|
|
976
|
-
timer.unref();
|
|
977
|
-
return () => clearTimeout(timer);
|
|
978
|
-
}
|
|
979
|
-
/**
|
|
980
|
-
* Renews the lease every third of its duration. A worker that cannot renew
|
|
981
|
-
* aborts its handler once the last confirmed expiry passes, because another
|
|
982
|
-
* worker may then claim the delivery (self-fencing).
|
|
983
|
-
*/
|
|
984
|
-
#startHeartbeat(delivery, controller) {
|
|
985
|
-
const { store, clock, notifier, settings } = this.#dependencies;
|
|
986
|
-
let confirmedExpiry = delivery.leaseExpiresAt.getTime();
|
|
987
|
-
let renewing = false;
|
|
988
|
-
const abortLeaseLost = () => {
|
|
989
|
-
controller.abort(new LeaseLostError(`Lease on delivery ${delivery.deliveryId} was lost`));
|
|
990
|
-
};
|
|
991
|
-
const timer = setInterval(
|
|
992
|
-
() => {
|
|
993
|
-
const now = clock.now();
|
|
994
|
-
if (now.getTime() >= confirmedExpiry) {
|
|
995
|
-
abortLeaseLost();
|
|
996
|
-
return;
|
|
997
|
-
}
|
|
998
|
-
if (renewing) {
|
|
999
|
-
return;
|
|
1000
|
-
}
|
|
1001
|
-
renewing = true;
|
|
1002
|
-
const expiry = now.getTime() + settings.leaseDurationMs;
|
|
1003
|
-
store.extendLease(delivery, new Date(expiry), now).then((extended) => {
|
|
1004
|
-
if (extended) {
|
|
1005
|
-
confirmedExpiry = expiry;
|
|
1006
|
-
} else {
|
|
1007
|
-
abortLeaseLost();
|
|
1008
|
-
}
|
|
1009
|
-
}).catch((error) => notifier.failure("extend-lease", error)).finally(() => {
|
|
1010
|
-
renewing = false;
|
|
1011
|
-
});
|
|
2038
|
+
return chunk3OHMEMZA_cjs.schedule(clock, {
|
|
2039
|
+
run: () => {
|
|
2040
|
+
controller.abort(new chunk3OHMEMZA_cjs.LeaseLostError(`Lease on delivery ${delivery.deliveryId} expired`));
|
|
1012
2041
|
},
|
|
1013
|
-
Math.max(
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
return () => clearInterval(timer);
|
|
2042
|
+
delayMs: Math.max(0, expiry - clock.now().getTime()),
|
|
2043
|
+
unref: true
|
|
2044
|
+
});
|
|
1017
2045
|
}
|
|
1018
|
-
async #fail(registration, delivery, error) {
|
|
1019
|
-
const { store, clock, random, notifier } = this.#dependencies;
|
|
2046
|
+
async #fail(registration, { delivery, error }) {
|
|
1020
2047
|
const policy = registration.retry;
|
|
1021
2048
|
if (delivery.attempt >= policy.attempts || !isRetryableError(policy, error)) {
|
|
1022
2049
|
await this.#bury(delivery, error);
|
|
1023
2050
|
return;
|
|
1024
2051
|
}
|
|
1025
|
-
|
|
2052
|
+
await this.#reschedule(registration, { delivery, error });
|
|
2053
|
+
}
|
|
2054
|
+
/** Schedules the next attempt with the retry policy's backoff. */
|
|
2055
|
+
async #reschedule(registration, { delivery, error }) {
|
|
2056
|
+
const { store, clock, random, notifier } = this.#dependencies;
|
|
2057
|
+
const policy = registration.retry;
|
|
2058
|
+
const errorInfo = chunk3OHMEMZA_cjs.toErrorInfo(error);
|
|
1026
2059
|
const now = clock.now();
|
|
1027
2060
|
const availableAt = new Date(
|
|
1028
2061
|
now.getTime() + computeRetryDelay(policy, delivery.attempt, random)
|
|
@@ -1032,6 +2065,11 @@ var Dispatcher = class {
|
|
|
1032
2065
|
rescheduled = await store.reschedule(delivery, availableAt, errorInfo, now);
|
|
1033
2066
|
} catch (storeError) {
|
|
1034
2067
|
notifier.failure("reschedule", storeError);
|
|
2068
|
+
this.#retain({
|
|
2069
|
+
delivery,
|
|
2070
|
+
settle: () => store.reschedule(delivery, availableAt, errorInfo, now),
|
|
2071
|
+
applied: () => notifier.notify("onDeliveryRetryScheduled", { delivery, error: errorInfo, availableAt })
|
|
2072
|
+
});
|
|
1035
2073
|
return;
|
|
1036
2074
|
}
|
|
1037
2075
|
if (rescheduled) {
|
|
@@ -1042,12 +2080,17 @@ var Dispatcher = class {
|
|
|
1042
2080
|
}
|
|
1043
2081
|
async #bury(delivery, error) {
|
|
1044
2082
|
const { store, clock, notifier } = this.#dependencies;
|
|
1045
|
-
const errorInfo = toErrorInfo(error);
|
|
2083
|
+
const errorInfo = chunk3OHMEMZA_cjs.toErrorInfo(error);
|
|
1046
2084
|
let buried;
|
|
1047
2085
|
try {
|
|
1048
2086
|
buried = await store.bury(delivery, errorInfo, clock.now());
|
|
1049
2087
|
} catch (storeError) {
|
|
1050
2088
|
notifier.failure("bury", storeError);
|
|
2089
|
+
this.#retain({
|
|
2090
|
+
delivery,
|
|
2091
|
+
settle: () => store.bury(delivery, errorInfo, clock.now()),
|
|
2092
|
+
applied: () => notifier.notify("onDeliveryDead", { delivery, error: errorInfo })
|
|
2093
|
+
});
|
|
1051
2094
|
return;
|
|
1052
2095
|
}
|
|
1053
2096
|
if (buried) {
|
|
@@ -1056,6 +2099,21 @@ var Dispatcher = class {
|
|
|
1056
2099
|
notifier.notify("onLeaseLost", { delivery });
|
|
1057
2100
|
}
|
|
1058
2101
|
}
|
|
2102
|
+
/**
|
|
2103
|
+
* A worker that only noticed locally that its lease ran out may still own the delivery in the
|
|
2104
|
+
* store, where other deliveries of its lane keep renewing the lane: it must not stay in flight
|
|
2105
|
+
* for ever. The attempt took longer than its lease, so it counts as a failed attempt (an
|
|
2106
|
+
* exhausted delivery is dead-lettered, whatever a custom `retryIf` says): a handler that always
|
|
2107
|
+
* outlives its lease cannot run for ever at attempt 1. If the lease is gone for real the store
|
|
2108
|
+
* refuses and nothing changes. The lost lease was reported already.
|
|
2109
|
+
*/
|
|
2110
|
+
async #giveBack(registration, { delivery, error }) {
|
|
2111
|
+
if (delivery.attempt >= registration.retry.attempts) {
|
|
2112
|
+
await this.#bury(delivery, error);
|
|
2113
|
+
} else {
|
|
2114
|
+
await this.#reschedule(registration, { delivery, error });
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
1059
2117
|
async #release(delivery) {
|
|
1060
2118
|
const { store, clock, notifier } = this.#dependencies;
|
|
1061
2119
|
let released;
|
|
@@ -1063,6 +2121,11 @@ var Dispatcher = class {
|
|
|
1063
2121
|
released = await store.release(delivery, clock.now());
|
|
1064
2122
|
} catch (storeError) {
|
|
1065
2123
|
notifier.failure("release", storeError);
|
|
2124
|
+
this.#retain({
|
|
2125
|
+
delivery,
|
|
2126
|
+
settle: () => store.release(delivery, clock.now()),
|
|
2127
|
+
applied: () => notifier.notify("onDeliveryReleased", { delivery })
|
|
2128
|
+
});
|
|
1066
2129
|
return;
|
|
1067
2130
|
}
|
|
1068
2131
|
if (released) {
|
|
@@ -1072,51 +2135,64 @@ var Dispatcher = class {
|
|
|
1072
2135
|
}
|
|
1073
2136
|
}
|
|
1074
2137
|
};
|
|
1075
|
-
function rejectOnAbort(signal) {
|
|
1076
|
-
return new Promise((_, reject) => {
|
|
1077
|
-
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
1078
|
-
});
|
|
1079
|
-
}
|
|
1080
2138
|
|
|
1081
2139
|
// src/handler-registry.ts
|
|
1082
2140
|
var HANDLER_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,254}$/;
|
|
1083
2141
|
var HandlerRegistry = class {
|
|
1084
2142
|
#byId = /* @__PURE__ */ new Map();
|
|
2143
|
+
#byClaimId = /* @__PURE__ */ new Map();
|
|
1085
2144
|
#byEventType = /* @__PURE__ */ new Map();
|
|
1086
2145
|
register(input, defaults) {
|
|
1087
2146
|
const { options } = input;
|
|
1088
2147
|
if (!HANDLER_ID_PATTERN.test(options.id)) {
|
|
1089
|
-
throw new ConfigurationError(
|
|
2148
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
1090
2149
|
`Handler id '${options.id}' must be 1-255 characters of letters, digits, '.', '_', ':', '/' or '-'`
|
|
1091
2150
|
);
|
|
1092
2151
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
throw new ConfigurationError(
|
|
1098
|
-
`Concurrency of handler '${options.id}' must be a positive integer, received ${options.concurrency}`
|
|
1099
|
-
);
|
|
2152
|
+
const claimIds = [options.id, ...input.legacyIds ?? []];
|
|
2153
|
+
this.#assertUnregistered(claimIds);
|
|
2154
|
+
if (options.concurrency !== void 0) {
|
|
2155
|
+
chunk3OHMEMZA_cjs.assertPositiveInteger(options.concurrency, `Concurrency of handler '${options.id}'`);
|
|
1100
2156
|
}
|
|
1101
2157
|
const registration = {
|
|
1102
2158
|
id: options.id,
|
|
1103
|
-
|
|
2159
|
+
eventTypes: input.eventTypes,
|
|
2160
|
+
claimIds,
|
|
1104
2161
|
schema: input.schema,
|
|
1105
2162
|
handle: input.handle,
|
|
1106
2163
|
retry: createRetryPolicy(options.retry, defaults.retry),
|
|
1107
|
-
timeoutMs: resolveTimeout(options.timeout,
|
|
2164
|
+
timeoutMs: resolveTimeout(options.timeout, {
|
|
2165
|
+
fallback: defaults.timeoutMs,
|
|
2166
|
+
handlerId: options.id
|
|
2167
|
+
}),
|
|
1108
2168
|
concurrency: options.concurrency ?? null,
|
|
1109
2169
|
transactional: options.transactional ?? false,
|
|
1110
2170
|
forwardsToTransport: input.forwardsToTransport
|
|
1111
2171
|
};
|
|
1112
|
-
this.#
|
|
1113
|
-
const forType = this.#byEventType.get(registration.eventType) ?? [];
|
|
1114
|
-
forType.push(registration);
|
|
1115
|
-
this.#byEventType.set(registration.eventType, forType);
|
|
2172
|
+
this.#index(registration);
|
|
1116
2173
|
return registration;
|
|
1117
2174
|
}
|
|
2175
|
+
#assertUnregistered(claimIds) {
|
|
2176
|
+
for (const id of claimIds) {
|
|
2177
|
+
if (this.#byClaimId.has(id)) {
|
|
2178
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(`Handler id '${id}' is already registered`);
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
#index(registration) {
|
|
2183
|
+
this.#byId.set(registration.id, registration);
|
|
2184
|
+
for (const id of registration.claimIds) {
|
|
2185
|
+
this.#byClaimId.set(id, registration);
|
|
2186
|
+
}
|
|
2187
|
+
for (const eventType of registration.eventTypes) {
|
|
2188
|
+
const forType = this.#byEventType.get(eventType) ?? [];
|
|
2189
|
+
forType.push(registration);
|
|
2190
|
+
this.#byEventType.set(eventType, forType);
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
/** The registration that runs deliveries stored under `id`, its own id or a legacy one. */
|
|
1118
2194
|
get(id) {
|
|
1119
|
-
return this.#
|
|
2195
|
+
return this.#byClaimId.get(id);
|
|
1120
2196
|
}
|
|
1121
2197
|
forEventType(eventType) {
|
|
1122
2198
|
return this.#byEventType.get(eventType) ?? [];
|
|
@@ -1125,7 +2201,7 @@ var HandlerRegistry = class {
|
|
|
1125
2201
|
return [...this.#byId.values()];
|
|
1126
2202
|
}
|
|
1127
2203
|
};
|
|
1128
|
-
function resolveTimeout(timeout, fallback, handlerId) {
|
|
2204
|
+
function resolveTimeout(timeout, { fallback, handlerId }) {
|
|
1129
2205
|
if (timeout === void 0) {
|
|
1130
2206
|
return fallback;
|
|
1131
2207
|
}
|
|
@@ -1135,77 +2211,315 @@ function resolveTimeout(timeout, fallback, handlerId) {
|
|
|
1135
2211
|
return toTimerMilliseconds(timeout, `Timeout of handler '${handlerId}'`);
|
|
1136
2212
|
}
|
|
1137
2213
|
|
|
1138
|
-
// src/notifier.ts
|
|
1139
|
-
var Notifier = class {
|
|
1140
|
-
#observers;
|
|
1141
|
-
#logger;
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
2214
|
+
// src/notifier.ts
|
|
2215
|
+
var Notifier = class {
|
|
2216
|
+
#observers;
|
|
2217
|
+
#logger;
|
|
2218
|
+
/** Hooks at least one observer implements, so callers skip building events nobody reads. */
|
|
2219
|
+
#implemented;
|
|
2220
|
+
constructor(observers, logger) {
|
|
2221
|
+
this.#observers = observers;
|
|
2222
|
+
this.#logger = logger;
|
|
2223
|
+
this.#implemented = new Set(
|
|
2224
|
+
observers.flatMap((observer) => hookNames(observer))
|
|
2225
|
+
);
|
|
2226
|
+
}
|
|
2227
|
+
/** True when some observer implements `method`. */
|
|
2228
|
+
wants(method) {
|
|
2229
|
+
return this.#implemented.has(method);
|
|
2230
|
+
}
|
|
2231
|
+
notify(method, argument) {
|
|
2232
|
+
for (const observer of this.#observers) {
|
|
2233
|
+
try {
|
|
2234
|
+
observer[method]?.call(
|
|
2235
|
+
observer,
|
|
2236
|
+
argument
|
|
2237
|
+
);
|
|
2238
|
+
} catch (error) {
|
|
2239
|
+
this.#reportObserverFailure(observer, error);
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
failure(stage, error) {
|
|
2244
|
+
const errorInfo = chunk3OHMEMZA_cjs.toErrorInfo(error);
|
|
2245
|
+
this.#logger.error(
|
|
2246
|
+
`[Notifier](failure) relaybus ${stage} failed | ${errorInfo.name}: ${errorInfo.message}`
|
|
2247
|
+
);
|
|
2248
|
+
for (const observer of this.#observers) {
|
|
2249
|
+
try {
|
|
2250
|
+
observer.onFailure?.({ stage, error: errorInfo });
|
|
2251
|
+
} catch (observerError) {
|
|
2252
|
+
this.#logObserverFailure(observerError);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
#reportObserverFailure(failedObserver, error) {
|
|
2257
|
+
this.#logObserverFailure(error);
|
|
2258
|
+
const errorInfo = chunk3OHMEMZA_cjs.toErrorInfo(error);
|
|
2259
|
+
for (const observer of this.#observers) {
|
|
2260
|
+
if (observer === failedObserver) {
|
|
2261
|
+
continue;
|
|
2262
|
+
}
|
|
2263
|
+
try {
|
|
2264
|
+
observer.onFailure?.({ stage: "observer", error: errorInfo });
|
|
2265
|
+
} catch (observerError) {
|
|
2266
|
+
this.#logObserverFailure(observerError);
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
#logObserverFailure(error) {
|
|
2271
|
+
const errorInfo = chunk3OHMEMZA_cjs.toErrorInfo(error);
|
|
2272
|
+
this.#logger.warn(
|
|
2273
|
+
`[Notifier](logObserverFailure) relaybus observer threw | ${errorInfo.name}: ${errorInfo.message}`
|
|
2274
|
+
);
|
|
2275
|
+
}
|
|
2276
|
+
};
|
|
2277
|
+
function hookNames(observer) {
|
|
2278
|
+
const names = [];
|
|
2279
|
+
for (let target = observer; target !== null && target !== Object.prototype; target = Object.getPrototypeOf(target)) {
|
|
2280
|
+
names.push(
|
|
2281
|
+
...Object.getOwnPropertyNames(target).filter(
|
|
2282
|
+
(name) => typeof observer[name] === "function"
|
|
2283
|
+
)
|
|
2284
|
+
);
|
|
2285
|
+
}
|
|
2286
|
+
return names;
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
// src/received-envelope.ts
|
|
2290
|
+
function normalizeEnvelope(envelope) {
|
|
2291
|
+
if (typeof envelope.id !== "string" || envelope.id.length === 0) {
|
|
2292
|
+
throw new chunk3OHMEMZA_cjs.InvalidEnvelopeError("Received event has no id");
|
|
2293
|
+
}
|
|
2294
|
+
if (typeof envelope.type !== "string") {
|
|
2295
|
+
throw new chunk3OHMEMZA_cjs.InvalidEnvelopeError(`Received event ${envelope.id} has no type`);
|
|
2296
|
+
}
|
|
2297
|
+
try {
|
|
2298
|
+
chunk3OHMEMZA_cjs.assertEventType(envelope.type);
|
|
2299
|
+
} catch (error) {
|
|
2300
|
+
throw new chunk3OHMEMZA_cjs.InvalidEnvelopeError(chunk3OHMEMZA_cjs.toErrorInfo(error).message);
|
|
2301
|
+
}
|
|
2302
|
+
const occurredAt = toDate(envelope.occurredAt);
|
|
2303
|
+
if (occurredAt === void 0) {
|
|
2304
|
+
throw new chunk3OHMEMZA_cjs.InvalidEnvelopeError(`Received event ${envelope.id} has an invalid occurredAt`);
|
|
2305
|
+
}
|
|
2306
|
+
return {
|
|
2307
|
+
id: envelope.id,
|
|
2308
|
+
type: envelope.type,
|
|
2309
|
+
source: typeof envelope.source === "string" ? envelope.source : "unknown",
|
|
2310
|
+
key: optionalText(envelope.key),
|
|
2311
|
+
payload: envelope.payload,
|
|
2312
|
+
headers: { ...envelope.headers },
|
|
2313
|
+
correlationId: optionalText(envelope.correlationId),
|
|
2314
|
+
causationId: optionalText(envelope.causationId),
|
|
2315
|
+
occurredAt
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
function toDate(value) {
|
|
2319
|
+
const date = typeof value === "string" ? new Date(value) : value;
|
|
2320
|
+
return date instanceof Date && !Number.isNaN(date.getTime()) ? date : void 0;
|
|
2321
|
+
}
|
|
2322
|
+
function optionalText(value) {
|
|
2323
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
2324
|
+
}
|
|
2325
|
+
var DEFAULTS = {
|
|
2326
|
+
concurrency: 10,
|
|
2327
|
+
pollInterval: "1s",
|
|
2328
|
+
leaseDuration: "30s",
|
|
2329
|
+
shutdownTimeout: "30s",
|
|
2330
|
+
completionBatchWindow: "2ms",
|
|
2331
|
+
completionBatchSize: 200,
|
|
2332
|
+
completedRetention: "7d",
|
|
2333
|
+
retentionInterval: "1h",
|
|
2334
|
+
routeTimeoutMs: 3e4,
|
|
2335
|
+
transactionTimeout: "5s"
|
|
2336
|
+
};
|
|
2337
|
+
function oneOf(value, { allowed, name }) {
|
|
2338
|
+
if (!allowed.includes(value)) {
|
|
2339
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
2340
|
+
`${name} must be one of ${allowed.map((option) => `'${option}'`).join(", ")}, received '${String(value)}'`
|
|
2341
|
+
);
|
|
2342
|
+
}
|
|
2343
|
+
return value;
|
|
2344
|
+
}
|
|
2345
|
+
function isDispatcherEnabled(options) {
|
|
2346
|
+
return options.dispatcher?.enabled ?? true;
|
|
2347
|
+
}
|
|
2348
|
+
function resolveBacklogInterval(options) {
|
|
2349
|
+
return options.backlogInterval === void 0 ? void 0 : toTimerMilliseconds(options.backlogInterval, "backlogInterval");
|
|
2350
|
+
}
|
|
2351
|
+
function resolveTransactionTimeout(options) {
|
|
2352
|
+
return toTimerMilliseconds(
|
|
2353
|
+
options.transactionTimeout ?? DEFAULTS.transactionTimeout,
|
|
2354
|
+
"transactionTimeout"
|
|
2355
|
+
);
|
|
2356
|
+
}
|
|
2357
|
+
function createHandlerDefaults(options) {
|
|
2358
|
+
return {
|
|
2359
|
+
retry: createRetryPolicy(options.retry),
|
|
2360
|
+
timeoutMs: options.handlerTimeout === void 0 || options.handlerTimeout === false ? null : toTimerMilliseconds(options.handlerTimeout, "handlerTimeout")
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
function createDispatcherSettings(options, generateIdentifier) {
|
|
2364
|
+
const dispatcher = options ?? {};
|
|
2365
|
+
const concurrency = chunk3OHMEMZA_cjs.assertPositiveInteger(
|
|
2366
|
+
dispatcher.concurrency ?? DEFAULTS.concurrency,
|
|
2367
|
+
"dispatcher.concurrency"
|
|
2368
|
+
);
|
|
2369
|
+
return {
|
|
2370
|
+
owner: `${os.hostname()}:${process.pid}:${generateIdentifier()}`,
|
|
2371
|
+
concurrency,
|
|
2372
|
+
batchSize: chunk3OHMEMZA_cjs.assertPositiveInteger(dispatcher.batchSize ?? concurrency, "dispatcher.batchSize"),
|
|
2373
|
+
...createDispatcherTimings(dispatcher)
|
|
2374
|
+
};
|
|
2375
|
+
}
|
|
2376
|
+
function createDispatcherTimings(dispatcher) {
|
|
2377
|
+
return {
|
|
2378
|
+
pollIntervalMs: toTimerMilliseconds(
|
|
2379
|
+
dispatcher.pollInterval ?? DEFAULTS.pollInterval,
|
|
2380
|
+
"dispatcher.pollInterval"
|
|
2381
|
+
),
|
|
2382
|
+
leaseDurationMs: toTimerMilliseconds(
|
|
2383
|
+
dispatcher.leaseDuration ?? DEFAULTS.leaseDuration,
|
|
2384
|
+
"dispatcher.leaseDuration"
|
|
2385
|
+
),
|
|
2386
|
+
shutdownTimeoutMs: toTimerMilliseconds(
|
|
2387
|
+
dispatcher.shutdownTimeout ?? DEFAULTS.shutdownTimeout,
|
|
2388
|
+
"dispatcher.shutdownTimeout"
|
|
2389
|
+
),
|
|
2390
|
+
completionBatchWindowMs: toMilliseconds(
|
|
2391
|
+
dispatcher.completionBatchWindow ?? DEFAULTS.completionBatchWindow,
|
|
2392
|
+
"dispatcher.completionBatchWindow"
|
|
2393
|
+
),
|
|
2394
|
+
completionBatchSize: chunk3OHMEMZA_cjs.assertPositiveInteger(
|
|
2395
|
+
dispatcher.completionBatchSize ?? DEFAULTS.completionBatchSize,
|
|
2396
|
+
"dispatcher.completionBatchSize"
|
|
2397
|
+
)
|
|
2398
|
+
};
|
|
2399
|
+
}
|
|
2400
|
+
function createRetentionSettings(options) {
|
|
2401
|
+
const completed = options?.completed ?? DEFAULTS.completedRetention;
|
|
2402
|
+
const dead = options?.dead ?? false;
|
|
2403
|
+
return {
|
|
2404
|
+
completedMs: completed === false ? null : toMilliseconds(completed, "retention.completed"),
|
|
2405
|
+
deadMs: dead === false ? null : toMilliseconds(dead, "retention.dead"),
|
|
2406
|
+
intervalMs: toTimerMilliseconds(
|
|
2407
|
+
options?.interval ?? DEFAULTS.retentionInterval,
|
|
2408
|
+
"retention.interval"
|
|
2409
|
+
)
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
// src/route-groups.ts
|
|
2414
|
+
var TRANSPORT_HANDLER_PREFIX = "relaybus.transport";
|
|
2415
|
+
function groupRoutes(routes) {
|
|
2416
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2417
|
+
for (const route of routes) {
|
|
2418
|
+
const type = routedType(route.event);
|
|
2419
|
+
const destination = route.destination ?? type;
|
|
2420
|
+
const id = `${TRANSPORT_HANDLER_PREFIX}:${route.transport}:${destination}`;
|
|
2421
|
+
const group = groups.get(id);
|
|
2422
|
+
if (group === void 0) {
|
|
2423
|
+
groups.set(id, createGroup(route, { id, destination, type }));
|
|
2424
|
+
} else {
|
|
2425
|
+
addRoute(group, { route, type, destination });
|
|
1156
2426
|
}
|
|
1157
2427
|
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
2428
|
+
return [...groups.values()];
|
|
2429
|
+
}
|
|
2430
|
+
function createGroup(route, {
|
|
2431
|
+
id,
|
|
2432
|
+
destination,
|
|
2433
|
+
type
|
|
2434
|
+
}) {
|
|
2435
|
+
return {
|
|
2436
|
+
transport: route.transport,
|
|
2437
|
+
destination,
|
|
2438
|
+
id,
|
|
2439
|
+
eventTypes: [type],
|
|
2440
|
+
legacyIds: [`${id}:${type}`],
|
|
2441
|
+
route,
|
|
2442
|
+
...resolveSettings(route, id)
|
|
2443
|
+
};
|
|
2444
|
+
}
|
|
2445
|
+
function addRoute(group, {
|
|
2446
|
+
route,
|
|
2447
|
+
type,
|
|
2448
|
+
destination
|
|
2449
|
+
}) {
|
|
2450
|
+
const target = `'${group.destination}' of transport '${group.transport}'`;
|
|
2451
|
+
if (route.transport !== group.transport) {
|
|
2452
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
2453
|
+
`Routes to ${target} and to '${destination}' of transport '${route.transport}' would share the handler id '${group.id}'; rename the transport or the destination`
|
|
1162
2454
|
);
|
|
1163
|
-
for (const observer of this.#observers) {
|
|
1164
|
-
try {
|
|
1165
|
-
observer.onFailure?.({ stage, error: errorInfo });
|
|
1166
|
-
} catch (observerError) {
|
|
1167
|
-
this.#logObserverFailure(observerError);
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
2455
|
}
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
const errorInfo = toErrorInfo(error);
|
|
1174
|
-
for (const observer of this.#observers) {
|
|
1175
|
-
if (observer === failedObserver) {
|
|
1176
|
-
continue;
|
|
1177
|
-
}
|
|
1178
|
-
try {
|
|
1179
|
-
observer.onFailure?.({ stage: "observer", error: errorInfo });
|
|
1180
|
-
} catch (observerError) {
|
|
1181
|
-
this.#logObserverFailure(observerError);
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
2456
|
+
if (group.eventTypes.includes(type)) {
|
|
2457
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(`Event type ${type} is routed to ${target} twice`);
|
|
1184
2458
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
`
|
|
2459
|
+
const { retry, timeoutMs } = resolveSettings(route, group.id);
|
|
2460
|
+
if (timeoutMs !== group.timeoutMs || !sameRetryPolicy(retry, group.retry)) {
|
|
2461
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
2462
|
+
`Routes to ${target} must share retry and timeout, because their events are sent in one queue per key | types=${group.eventTypes[0]},${type}`
|
|
1189
2463
|
);
|
|
1190
2464
|
}
|
|
1191
|
-
|
|
2465
|
+
group.eventTypes.push(type);
|
|
2466
|
+
group.legacyIds.push(`${group.id}:${type}`);
|
|
2467
|
+
}
|
|
2468
|
+
function resolveSettings(route, id) {
|
|
2469
|
+
return {
|
|
2470
|
+
retry: createRetryPolicy(route.retry, DEFAULT_ROUTE_RETRY_POLICY),
|
|
2471
|
+
timeoutMs: route.timeout === void 0 ? DEFAULTS.routeTimeoutMs : toTimerMilliseconds(route.timeout, `Timeout of handler '${id}'`)
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
function sameRetryPolicy(left, right) {
|
|
2475
|
+
return Object.keys(left).every(
|
|
2476
|
+
(field) => left[field] === right[field]
|
|
2477
|
+
);
|
|
2478
|
+
}
|
|
2479
|
+
function routedType(event) {
|
|
2480
|
+
const type = typeof event === "string" ? event : event.type;
|
|
2481
|
+
chunk3OHMEMZA_cjs.assertEventType(type);
|
|
2482
|
+
return type;
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
// src/transaction-budget.ts
|
|
2486
|
+
async function runBudgetedTransaction(store, { clock, timeoutMs, work }) {
|
|
2487
|
+
const startedAt = clock.now().getTime();
|
|
2488
|
+
const overrun = () => clock.now().getTime() - startedAt >= timeoutMs;
|
|
2489
|
+
let committing = false;
|
|
2490
|
+
try {
|
|
2491
|
+
return await store.transaction(
|
|
2492
|
+
async (transaction) => {
|
|
2493
|
+
committing = false;
|
|
2494
|
+
const result = await work(transaction);
|
|
2495
|
+
if (overrun()) {
|
|
2496
|
+
throw createTimeoutError(timeoutMs, void 0);
|
|
2497
|
+
}
|
|
2498
|
+
committing = true;
|
|
2499
|
+
return result;
|
|
2500
|
+
},
|
|
2501
|
+
{ timeoutMs }
|
|
2502
|
+
);
|
|
2503
|
+
} catch (error) {
|
|
2504
|
+
if (error instanceof chunk3OHMEMZA_cjs.TransactionTimeoutError || committing || !overrun()) {
|
|
2505
|
+
throw error;
|
|
2506
|
+
}
|
|
2507
|
+
throw createTimeoutError(timeoutMs, error);
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
function createTimeoutError(timeoutMs, cause) {
|
|
2511
|
+
const message = `The transaction ran past its ${timeoutMs} ms budget (transactionTimeout) and was rolled back. The usual cause is a slow call to another service (HTTP, gRPC, a broker, a lock) inside the transaction: make external calls before the transaction starts or after it commits, or raise transactionTimeout`;
|
|
2512
|
+
return cause === void 0 ? new chunk3OHMEMZA_cjs.TransactionTimeoutError(message) : new chunk3OHMEMZA_cjs.TransactionTimeoutError(`${message} | cause=${chunk3OHMEMZA_cjs.toErrorInfo(cause).message}`, { cause });
|
|
2513
|
+
}
|
|
1192
2514
|
|
|
1193
2515
|
// src/relay-bus.ts
|
|
1194
|
-
var
|
|
1195
|
-
concurrency: 10,
|
|
1196
|
-
pollInterval: "1s",
|
|
1197
|
-
leaseDuration: "30s",
|
|
1198
|
-
shutdownTimeout: "30s",
|
|
1199
|
-
completedRetention: "7d",
|
|
1200
|
-
retentionInterval: "1h",
|
|
1201
|
-
routeTimeoutMs: 3e4
|
|
1202
|
-
};
|
|
1203
|
-
var TRANSPORT_HANDLER_PREFIX = "relaybus.transport";
|
|
2516
|
+
var DEFAULT_DRAIN_BATCHES = 1e3;
|
|
1204
2517
|
var WAKE_DELAY_MS = 25;
|
|
1205
2518
|
var RelayBus = class {
|
|
1206
2519
|
#store;
|
|
1207
2520
|
#source;
|
|
1208
2521
|
#transactionMode;
|
|
2522
|
+
#transactionTimeoutMs;
|
|
1209
2523
|
#transactionResolver;
|
|
1210
2524
|
#unroutedEvents;
|
|
1211
2525
|
#registry = new HandlerRegistry();
|
|
@@ -1218,61 +2532,83 @@ var RelayBus = class {
|
|
|
1218
2532
|
#generateIdentifier;
|
|
1219
2533
|
#dispatcher;
|
|
1220
2534
|
#dispatcherEnabled;
|
|
2535
|
+
#catalogRequirements = [];
|
|
1221
2536
|
#setupStore;
|
|
1222
2537
|
#retention;
|
|
1223
2538
|
#retentionTimer;
|
|
2539
|
+
#observation;
|
|
2540
|
+
#backlogIntervalMs;
|
|
2541
|
+
#backlogTimer;
|
|
2542
|
+
#shutdownTimeoutMs;
|
|
1224
2543
|
#state = "idle";
|
|
1225
2544
|
#starting;
|
|
1226
2545
|
#stopping;
|
|
1227
2546
|
constructor(options) {
|
|
1228
2547
|
this.#store = options.store;
|
|
1229
2548
|
this.#source = options.source ?? "relaybus";
|
|
1230
|
-
this.#transactionMode = oneOf(
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
"unroutedEvents"
|
|
1239
|
-
);
|
|
2549
|
+
this.#transactionMode = oneOf(options.transactionMode ?? "required", {
|
|
2550
|
+
allowed: ["required", "optional"],
|
|
2551
|
+
name: "transactionMode"
|
|
2552
|
+
});
|
|
2553
|
+
this.#unroutedEvents = oneOf(options.unroutedEvents ?? "warn", {
|
|
2554
|
+
allowed: ["warn", "throw"],
|
|
2555
|
+
name: "unroutedEvents"
|
|
2556
|
+
});
|
|
1240
2557
|
this.#transactionResolver = options.transactionResolver;
|
|
1241
|
-
this.#
|
|
1242
|
-
|
|
1243
|
-
timeoutMs: options.handlerTimeout === void 0 || options.handlerTimeout === false ? null : toTimerMilliseconds(options.handlerTimeout, "handlerTimeout")
|
|
1244
|
-
};
|
|
2558
|
+
this.#transactionTimeoutMs = resolveTransactionTimeout(options);
|
|
2559
|
+
this.#defaults = createHandlerDefaults(options);
|
|
1245
2560
|
this.#logger = options.logger ?? console;
|
|
1246
2561
|
this.#notifier = new Notifier(options.observers ?? [], this.#logger);
|
|
1247
2562
|
this.#clock = options.clock ?? systemClock;
|
|
2563
|
+
this.#observation = BusObservation.create({
|
|
2564
|
+
notifier: this.#notifier,
|
|
2565
|
+
tracer: options.tracer,
|
|
2566
|
+
clock: this.#clock
|
|
2567
|
+
});
|
|
2568
|
+
this.#backlogIntervalMs = resolveBacklogInterval(options);
|
|
1248
2569
|
this.#generateIdentifier = options.generateIdentifier ?? (() => createUuidV7());
|
|
1249
2570
|
this.#setupStore = options.setupStore ?? true;
|
|
1250
2571
|
this.#retention = createRetentionSettings(options.retention);
|
|
2572
|
+
this.#subscriptions = options.subscriptions ?? [];
|
|
2573
|
+
this.#registerConnections(options);
|
|
2574
|
+
this.#dispatcherEnabled = isDispatcherEnabled(options);
|
|
2575
|
+
const settings = createDispatcherSettings(options.dispatcher, this.#generateIdentifier);
|
|
2576
|
+
this.#shutdownTimeoutMs = settings.shutdownTimeoutMs;
|
|
2577
|
+
this.#dispatcher = this.#createDispatcher(settings, options);
|
|
2578
|
+
}
|
|
2579
|
+
#createDispatcher(settings, options) {
|
|
2580
|
+
return new Dispatcher({
|
|
2581
|
+
store: this.#store,
|
|
2582
|
+
registry: this.#registry,
|
|
2583
|
+
clock: this.#clock,
|
|
2584
|
+
random: options.random ?? Math.random,
|
|
2585
|
+
notifier: this.#notifier,
|
|
2586
|
+
settings,
|
|
2587
|
+
observation: DispatchObservation.create({
|
|
2588
|
+
notifier: this.#notifier,
|
|
2589
|
+
tracer: options.tracer,
|
|
2590
|
+
clock: this.#clock
|
|
2591
|
+
})
|
|
2592
|
+
});
|
|
2593
|
+
}
|
|
2594
|
+
/** Registers the transports, then the routes, checks the subscriptions and registers the catalogs. */
|
|
2595
|
+
#registerConnections(options) {
|
|
1251
2596
|
for (const transport of options.transports ?? []) {
|
|
1252
2597
|
if (this.#transports.has(transport.name)) {
|
|
1253
|
-
throw new ConfigurationError(`Transport name '${transport.name}' is used twice`);
|
|
2598
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(`Transport name '${transport.name}' is used twice`);
|
|
1254
2599
|
}
|
|
1255
2600
|
this.#transports.set(transport.name, transport);
|
|
1256
2601
|
}
|
|
1257
|
-
|
|
1258
|
-
this.#registerRoute(route);
|
|
1259
|
-
}
|
|
1260
|
-
this.#subscriptions = options.subscriptions ?? [];
|
|
2602
|
+
this.#registerRoutes(options.routes ?? []);
|
|
1261
2603
|
for (const subscription of this.#subscriptions) {
|
|
1262
2604
|
const transport = this.#requireTransport(subscription.transport);
|
|
1263
2605
|
if (transport.subscribe === void 0) {
|
|
1264
|
-
throw new ConfigurationError(`Transport '${transport.name}' cannot subscribe`);
|
|
2606
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(`Transport '${transport.name}' cannot subscribe`);
|
|
1265
2607
|
}
|
|
1266
2608
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
registry: this.#registry,
|
|
1271
|
-
clock: this.#clock,
|
|
1272
|
-
random: options.random ?? Math.random,
|
|
1273
|
-
notifier: this.#notifier,
|
|
1274
|
-
settings: createDispatcherSettings(options.dispatcher, this.#generateIdentifier)
|
|
1275
|
-
});
|
|
2609
|
+
for (const catalog of options.catalogs ?? []) {
|
|
2610
|
+
this.registerCatalog(catalog);
|
|
2611
|
+
}
|
|
1276
2612
|
}
|
|
1277
2613
|
get store() {
|
|
1278
2614
|
return this.#store;
|
|
@@ -1292,11 +2628,12 @@ var RelayBus = class {
|
|
|
1292
2628
|
}
|
|
1293
2629
|
return true;
|
|
1294
2630
|
}
|
|
2631
|
+
// eslint-disable-next-line relaybus/max-two-params -- public RelayBus.subscribe signature
|
|
1295
2632
|
subscribe(event, handler, options) {
|
|
1296
2633
|
const { type, schema } = describeEvent(event);
|
|
1297
2634
|
this.#registry.register(
|
|
1298
2635
|
{
|
|
1299
|
-
|
|
2636
|
+
eventTypes: [type],
|
|
1300
2637
|
schema,
|
|
1301
2638
|
handle: handler,
|
|
1302
2639
|
options,
|
|
@@ -1305,9 +2642,34 @@ var RelayBus = class {
|
|
|
1305
2642
|
this.#defaults
|
|
1306
2643
|
);
|
|
1307
2644
|
}
|
|
2645
|
+
// eslint-disable-next-line relaybus/max-two-params -- public RelayBus.subscribeCatalog signature
|
|
2646
|
+
subscribeCatalog(catalog, handlers, options = {}) {
|
|
2647
|
+
for (const binding of resolveCatalogHandlers(catalog, { handlers, options })) {
|
|
2648
|
+
this.subscribe(binding.eventClass, binding.handle, binding.options);
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
/**
|
|
2652
|
+
* Adds a catalog, or a `consume` / `produce` declaration, to the checks
|
|
2653
|
+
* `start()` runs; `start()` throws a {@link ConfigurationError} naming each
|
|
2654
|
+
* event that is not covered and what it lacks. Equivalent to the `catalogs`
|
|
2655
|
+
* option.
|
|
2656
|
+
*/
|
|
2657
|
+
registerCatalog(catalog) {
|
|
2658
|
+
this.#catalogRequirements.push(toCatalogRequirement(catalog));
|
|
2659
|
+
}
|
|
2660
|
+
/**
|
|
2661
|
+
* A view of this bus that publishes only the events of `catalog`, or only
|
|
2662
|
+
* the events named in `options.events` (for example the events this service
|
|
2663
|
+
* produces). Other events fail to compile and are rejected at runtime.
|
|
2664
|
+
*/
|
|
2665
|
+
forCatalog(catalog, options = {}) {
|
|
2666
|
+
return new CatalogPublisher(catalog, this, options);
|
|
2667
|
+
}
|
|
1308
2668
|
/** Registered handlers and routes, for diagnostics. */
|
|
1309
2669
|
handlers() {
|
|
1310
|
-
return this.#registry.all().
|
|
2670
|
+
return this.#registry.all().flatMap(
|
|
2671
|
+
(registration) => registration.eventTypes.map((eventType) => toDescriptor(registration, eventType))
|
|
2672
|
+
);
|
|
1311
2673
|
}
|
|
1312
2674
|
/**
|
|
1313
2675
|
* Stores an event in the current transaction. Handlers run after the
|
|
@@ -1315,21 +2677,32 @@ var RelayBus = class {
|
|
|
1315
2677
|
* have been written when the id already exists.
|
|
1316
2678
|
*/
|
|
1317
2679
|
async publish(event, options = {}) {
|
|
1318
|
-
const [envelope] = await this.#
|
|
2680
|
+
const [envelope] = await this.#observedPublish([event], { options, id: options.id });
|
|
1319
2681
|
return envelope;
|
|
1320
2682
|
}
|
|
1321
2683
|
/** Stores several events atomically, in order, sharing one correlation id. */
|
|
1322
2684
|
async publishAll(events, options = {}) {
|
|
1323
|
-
return this.#
|
|
2685
|
+
return this.#observedPublish(events, { options, id: void 0 });
|
|
1324
2686
|
}
|
|
1325
2687
|
/**
|
|
1326
2688
|
* Runs `work` in a new store transaction. `publish` calls inside it join the
|
|
1327
|
-
* transaction without passing it explicitly.
|
|
2689
|
+
* transaction without passing it explicitly. The transaction is held to `transactionTimeout`
|
|
2690
|
+
* and rolled back with a {@link TransactionTimeoutError} when it runs past it.
|
|
1328
2691
|
*/
|
|
1329
2692
|
async transaction(work) {
|
|
1330
|
-
return this.#store
|
|
1331
|
-
|
|
1332
|
-
|
|
2693
|
+
return runBudgetedTransaction(this.#store, {
|
|
2694
|
+
clock: this.#clock,
|
|
2695
|
+
timeoutMs: this.#transactionTimeoutMs,
|
|
2696
|
+
work: (transaction) => runInScope({ transaction }, () => work(transaction))
|
|
2697
|
+
});
|
|
2698
|
+
}
|
|
2699
|
+
/**
|
|
2700
|
+
* The transaction `publish` would join right now: the one of the enclosing `bus.transaction`
|
|
2701
|
+
* (or `@RelayBusTransaction()` method, or transactional handler), else the one
|
|
2702
|
+
* `transactionResolver` finds. Repositories use it to write in the same transaction.
|
|
2703
|
+
*/
|
|
2704
|
+
currentTransaction() {
|
|
2705
|
+
return getCurrentScope()?.transaction ?? this.#transactionResolver?.();
|
|
1333
2706
|
}
|
|
1334
2707
|
/**
|
|
1335
2708
|
* Stores an event received from a broker and schedules it for local handlers.
|
|
@@ -1337,7 +2710,13 @@ var RelayBus = class {
|
|
|
1337
2710
|
* only after it resolves. Throws {@link InvalidEnvelopeError} for malformed
|
|
1338
2711
|
* events, which transports should dead-letter instead of redelivering.
|
|
1339
2712
|
*/
|
|
1340
|
-
|
|
2713
|
+
receive(envelope) {
|
|
2714
|
+
return this.#observation === void 0 ? this.#receive(envelope) : this.#observedReceive(envelope, void 0);
|
|
2715
|
+
}
|
|
2716
|
+
#observedReceive(envelope, origin) {
|
|
2717
|
+
return this.#observation.receive({ envelope, origin }, () => this.#receive(envelope));
|
|
2718
|
+
}
|
|
2719
|
+
async #receive(envelope) {
|
|
1341
2720
|
const normalized = normalizeEnvelope(envelope);
|
|
1342
2721
|
const registrations = this.#registry.forEventType(normalized.type).filter((registration) => !registration.forwardsToTransport);
|
|
1343
2722
|
if (registrations.length === 0) {
|
|
@@ -1366,10 +2745,11 @@ var RelayBus = class {
|
|
|
1366
2745
|
*/
|
|
1367
2746
|
async start() {
|
|
1368
2747
|
if (this.#state !== "idle") {
|
|
1369
|
-
throw new
|
|
2748
|
+
throw new chunk3OHMEMZA_cjs.LifecycleError(
|
|
1370
2749
|
this.#state === "stopped" ? "A stopped relaybus cannot be started again; create a new instance" : `Cannot start a relaybus that is ${this.#state}`
|
|
1371
2750
|
);
|
|
1372
2751
|
}
|
|
2752
|
+
assertCatalogsCovered(this.#catalogRequirements, (type) => this.#registrationsOf(type));
|
|
1373
2753
|
this.#state = "starting";
|
|
1374
2754
|
this.#starting = this.#startComponents();
|
|
1375
2755
|
try {
|
|
@@ -1379,21 +2759,43 @@ var RelayBus = class {
|
|
|
1379
2759
|
}
|
|
1380
2760
|
}
|
|
1381
2761
|
/**
|
|
1382
|
-
* Stops
|
|
1383
|
-
*
|
|
2762
|
+
* Stops claiming, waits for running handlers, hands back leases, closes transports and the store,
|
|
2763
|
+
* all within `options.timeout` (default `dispatcher.shutdownTimeout`). When the deadline passes it
|
|
2764
|
+
* aborts the handlers still running, force-closes what relaybus opened itself (never pools or
|
|
2765
|
+
* clients the application passed in) and resolves anyway: it never rejects because of the
|
|
2766
|
+
* deadline. Abandoned deliveries stay leased until their lease expires and then run again
|
|
2767
|
+
* (at-least-once). Waits for a start in progress first. Safe to call more than once: later calls
|
|
2768
|
+
* get the result of the first.
|
|
1384
2769
|
*/
|
|
1385
|
-
async stop() {
|
|
2770
|
+
async stop(options = {}) {
|
|
2771
|
+
const timeoutMs = options.timeout === void 0 ? this.#shutdownTimeoutMs : toTimerMilliseconds(options.timeout, "timeout");
|
|
1386
2772
|
if (this.#stopping !== void 0) {
|
|
1387
2773
|
return this.#stopping;
|
|
1388
2774
|
}
|
|
1389
|
-
if (this.#state
|
|
1390
|
-
|
|
2775
|
+
if (this.#state !== "running" && this.#state !== "starting") {
|
|
2776
|
+
return this.#stopOutsideRun(timeoutMs);
|
|
1391
2777
|
}
|
|
1392
|
-
|
|
1393
|
-
|
|
2778
|
+
this.#stopping = this.#stopComponents(timeoutMs);
|
|
2779
|
+
const result = await this.#stopping;
|
|
2780
|
+
if (!result.graceful) {
|
|
2781
|
+
this.#logger.warn(
|
|
2782
|
+
`[RelayBus](stop) Shutdown deadline passed, forced close | abandoned=${result.abandonedDeliveries} | timeoutMs=${timeoutMs}`
|
|
2783
|
+
);
|
|
2784
|
+
}
|
|
2785
|
+
return result;
|
|
2786
|
+
}
|
|
2787
|
+
/**
|
|
2788
|
+
* A bus that is not running: an idle one only settles what batches run by hand left behind (and
|
|
2789
|
+
* stays startable); a stopped one has nothing left to do.
|
|
2790
|
+
*/
|
|
2791
|
+
async #stopOutsideRun(timeoutMs) {
|
|
2792
|
+
if (this.#state !== "idle") {
|
|
2793
|
+
return { graceful: true, abandonedDeliveries: 0, durationMs: 0 };
|
|
1394
2794
|
}
|
|
1395
|
-
|
|
1396
|
-
|
|
2795
|
+
return withinDeadline(
|
|
2796
|
+
{ clock: this.#clock, timeoutMs },
|
|
2797
|
+
(deadline) => this.#dispatcher.stop(deadline)
|
|
2798
|
+
);
|
|
1397
2799
|
}
|
|
1398
2800
|
/**
|
|
1399
2801
|
* Claims and runs one batch of due deliveries, resolving when they settle.
|
|
@@ -1406,7 +2808,7 @@ var RelayBus = class {
|
|
|
1406
2808
|
* Runs batches until nothing is due. Deliveries scheduled in the future
|
|
1407
2809
|
* (retries, delays) are not waited for. Intended for tests.
|
|
1408
2810
|
*/
|
|
1409
|
-
async drain(maxBatches =
|
|
2811
|
+
async drain(maxBatches = DEFAULT_DRAIN_BATCHES) {
|
|
1410
2812
|
let total = 0;
|
|
1411
2813
|
for (let batch = 0; batch < maxBatches; batch += 1) {
|
|
1412
2814
|
const processed = await this.runOnce();
|
|
@@ -1415,7 +2817,7 @@ var RelayBus = class {
|
|
|
1415
2817
|
}
|
|
1416
2818
|
total += processed;
|
|
1417
2819
|
}
|
|
1418
|
-
throw new
|
|
2820
|
+
throw new chunk3OHMEMZA_cjs.DrainIncompleteError(`drain did not finish within ${maxBatches} batches`);
|
|
1419
2821
|
}
|
|
1420
2822
|
listDeliveries(query = {}) {
|
|
1421
2823
|
return this.#store.listDeliveries(query);
|
|
@@ -1457,41 +2859,128 @@ var RelayBus = class {
|
|
|
1457
2859
|
}
|
|
1458
2860
|
for (const subscription of this.#subscriptions) {
|
|
1459
2861
|
const transport = this.#transports.get(subscription.transport);
|
|
1460
|
-
await transport.subscribe(
|
|
1461
|
-
|
|
1462
|
-
|
|
2862
|
+
await transport.subscribe(
|
|
2863
|
+
subscription.destinations,
|
|
2864
|
+
async (envelope, message) => {
|
|
2865
|
+
await (this.#observation === void 0 ? this.#receive(envelope) : this.#observedReceive(envelope, { transport, destination: message?.destination }));
|
|
2866
|
+
},
|
|
2867
|
+
this.#subscriptionHooks(transport)
|
|
2868
|
+
);
|
|
1463
2869
|
}
|
|
1464
2870
|
} catch (error) {
|
|
1465
|
-
await
|
|
2871
|
+
await withinDeadline(
|
|
2872
|
+
{ clock: this.#clock, timeoutMs: this.#shutdownTimeoutMs },
|
|
2873
|
+
async (deadline) => {
|
|
2874
|
+
await this.#closeTransports(deadline);
|
|
2875
|
+
return 0;
|
|
2876
|
+
}
|
|
2877
|
+
);
|
|
1466
2878
|
this.#state = "stopped";
|
|
1467
2879
|
throw error;
|
|
1468
2880
|
}
|
|
2881
|
+
if (this.#state !== "starting") {
|
|
2882
|
+
throw new chunk3OHMEMZA_cjs.LifecycleError("relaybus was stopped before it finished starting");
|
|
2883
|
+
}
|
|
1469
2884
|
if (this.#dispatcherEnabled) {
|
|
1470
2885
|
this.#dispatcher.start();
|
|
1471
2886
|
}
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
this.purgeExpired().catch(() => void 0);
|
|
1475
|
-
}, this.#retention.intervalMs);
|
|
1476
|
-
this.#retentionTimer.unref();
|
|
1477
|
-
}
|
|
2887
|
+
this.#scheduleRetention();
|
|
2888
|
+
this.#scheduleBacklog();
|
|
1478
2889
|
this.#state = "running";
|
|
1479
2890
|
this.#logger.log(
|
|
1480
|
-
`[RelayBus](
|
|
2891
|
+
`[RelayBus](startComponents) relaybus started | handlers=${this.#registry.all().length} dispatcher=${this.#dispatcherEnabled}`
|
|
1481
2892
|
);
|
|
1482
2893
|
}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
2894
|
+
/** What a transport may report back while subscribed; undefined when no observer wants it. */
|
|
2895
|
+
#subscriptionHooks(transport) {
|
|
2896
|
+
if (!this.#notifier.wants("onInvalidMessage")) {
|
|
2897
|
+
return void 0;
|
|
2898
|
+
}
|
|
2899
|
+
return {
|
|
2900
|
+
onInvalidMessage: ({ destination, error }) => {
|
|
2901
|
+
const errorInfo = chunk3OHMEMZA_cjs.toErrorInfo(error);
|
|
2902
|
+
this.#notifier.notify("onInvalidMessage", {
|
|
2903
|
+
transport: transport.name,
|
|
2904
|
+
system: transport.system ?? transport.name,
|
|
2905
|
+
destination,
|
|
2906
|
+
reason: errorInfo.message,
|
|
2907
|
+
error: errorInfo
|
|
2908
|
+
});
|
|
2909
|
+
}
|
|
2910
|
+
};
|
|
2911
|
+
}
|
|
2912
|
+
/** Applies the retention policy every `retention.interval` while the bus runs, when it deletes anything. */
|
|
2913
|
+
#scheduleRetention() {
|
|
2914
|
+
if (this.#retention.completedMs !== null || this.#retention.deadMs !== null) {
|
|
2915
|
+
this.#retentionTimer = chunk3OHMEMZA_cjs.schedule(this.#clock, {
|
|
2916
|
+
run: () => {
|
|
2917
|
+
this.purgeExpired().catch(() => void 0);
|
|
2918
|
+
},
|
|
2919
|
+
delayMs: this.#retention.intervalMs,
|
|
2920
|
+
repeat: true,
|
|
2921
|
+
unref: true
|
|
2922
|
+
});
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
/** Reports the backlog every `backlogInterval` while the bus runs, when an observer wants it. */
|
|
2926
|
+
#scheduleBacklog() {
|
|
2927
|
+
if (this.#backlogIntervalMs !== void 0) {
|
|
2928
|
+
this.#backlogTimer = this.#observation?.startBacklog({
|
|
2929
|
+
intervalMs: this.#backlogIntervalMs,
|
|
2930
|
+
statistics: () => this.statistics()
|
|
2931
|
+
});
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
/**
|
|
2935
|
+
* Waits for a start in progress (a failed start leaves nothing to stop), then stops the dispatcher
|
|
2936
|
+
* and closes the transports and the store. A start still running at the deadline is given up: the
|
|
2937
|
+
* transports and the store are force-closed and the start rejects when it resumes.
|
|
2938
|
+
*/
|
|
2939
|
+
async #stopComponents(timeoutMs) {
|
|
1486
2940
|
try {
|
|
1487
|
-
await this.#
|
|
1488
|
-
|
|
1489
|
-
|
|
2941
|
+
return await withinDeadline({ clock: this.#clock, timeoutMs }, async (deadline) => {
|
|
2942
|
+
if (this.#state === "starting") {
|
|
2943
|
+
const started = await deadline.race(this.#starting.catch(() => void 0));
|
|
2944
|
+
if (started === "timeout") {
|
|
2945
|
+
this.#state = "stopping";
|
|
2946
|
+
await this.#closeConnections(deadline);
|
|
2947
|
+
return 0;
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
if (this.#state !== "running") {
|
|
2951
|
+
return 0;
|
|
2952
|
+
}
|
|
2953
|
+
this.#state = "stopping";
|
|
2954
|
+
this.#retentionTimer?.();
|
|
2955
|
+
this.#backlogTimer?.();
|
|
2956
|
+
const abandoned = await this.#dispatcher.stop(deadline);
|
|
2957
|
+
await this.#closeConnections(deadline);
|
|
2958
|
+
return abandoned;
|
|
2959
|
+
});
|
|
1490
2960
|
} finally {
|
|
1491
2961
|
this.#state = "stopped";
|
|
1492
2962
|
}
|
|
1493
2963
|
}
|
|
1494
|
-
async #
|
|
2964
|
+
async #closeConnections(deadline) {
|
|
2965
|
+
await this.#closeTransports(deadline);
|
|
2966
|
+
const options = { signal: deadline.signal };
|
|
2967
|
+
await deadline.race(Promise.resolve(this.#store.close?.(options)));
|
|
2968
|
+
}
|
|
2969
|
+
/** Publishes inside the tracer's span, with its trace headers, when the bus is observed. */
|
|
2970
|
+
#observedPublish(events, input) {
|
|
2971
|
+
const observation = this.#observation;
|
|
2972
|
+
if (observation === void 0) {
|
|
2973
|
+
return this.#publish(events, input);
|
|
2974
|
+
}
|
|
2975
|
+
return observation.publish(
|
|
2976
|
+
{ eventTypes: events.map((event) => String(event.type)) },
|
|
2977
|
+
(headers) => this.#publish(events, { ...input, options: withTraceHeaders(input.options, headers) })
|
|
2978
|
+
);
|
|
2979
|
+
}
|
|
2980
|
+
async #publish(events, {
|
|
2981
|
+
options,
|
|
2982
|
+
id
|
|
2983
|
+
}) {
|
|
1495
2984
|
if (events.length === 0) {
|
|
1496
2985
|
return [];
|
|
1497
2986
|
}
|
|
@@ -1499,10 +2988,27 @@ var RelayBus = class {
|
|
|
1499
2988
|
const delayMs = options.delay === void 0 ? 0 : toMilliseconds(options.delay, "delay");
|
|
1500
2989
|
const availableAt = new Date(now.getTime() + delayMs);
|
|
1501
2990
|
const scope = getCurrentScope();
|
|
2991
|
+
const entries = await this.#createEntries({ events, options, id, scope, now, availableAt });
|
|
2992
|
+
const deliverable = entries.filter((entry) => entry.deliveries.length > 0);
|
|
2993
|
+
if (deliverable.length > 0) {
|
|
2994
|
+
await this.#appendDeliverable({ entries: deliverable, options, scope, now, delayMs });
|
|
2995
|
+
}
|
|
2996
|
+
return entries.map((entry) => entry.event);
|
|
2997
|
+
}
|
|
2998
|
+
/** One entry per event, in order: its envelope and a delivery for every registration of its type. */
|
|
2999
|
+
async #createEntries(input) {
|
|
3000
|
+
const { events, options, id, scope, now, availableAt } = input;
|
|
1502
3001
|
const entries = [];
|
|
1503
3002
|
let correlationId = options.correlationId ?? scope?.event?.correlationId ?? scope?.event?.id;
|
|
1504
3003
|
for (const event of events) {
|
|
1505
|
-
const envelope = await this.#createEnvelope(
|
|
3004
|
+
const envelope = await this.#createEnvelope({
|
|
3005
|
+
event,
|
|
3006
|
+
options,
|
|
3007
|
+
id,
|
|
3008
|
+
scope,
|
|
3009
|
+
now,
|
|
3010
|
+
correlationId
|
|
3011
|
+
});
|
|
1506
3012
|
correlationId = envelope.correlationId;
|
|
1507
3013
|
const registrations = this.#registry.forEventType(envelope.type);
|
|
1508
3014
|
if (registrations.length === 0) {
|
|
@@ -1515,37 +3021,42 @@ var RelayBus = class {
|
|
|
1515
3021
|
)
|
|
1516
3022
|
});
|
|
1517
3023
|
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
3024
|
+
return entries;
|
|
3025
|
+
}
|
|
3026
|
+
/** Appends the entries that have deliveries, reports what was new and wakes the dispatcher when it is due now. */
|
|
3027
|
+
async #appendDeliverable(input) {
|
|
3028
|
+
const { entries, options, scope, now, delayMs } = input;
|
|
3029
|
+
const result = await this.#appendInTransaction(entries, { options, scope, now });
|
|
3030
|
+
const appended = new Set(result.appendedEventIds);
|
|
3031
|
+
const published = entries.map((entry) => entry.event).filter((envelope) => appended.has(envelope.id));
|
|
3032
|
+
if (published.length > 0) {
|
|
3033
|
+
this.#notifier.notify("onPublished", {
|
|
3034
|
+
envelopes: published,
|
|
3035
|
+
deliveries: result.appendedDeliveryCount
|
|
3036
|
+
});
|
|
3037
|
+
if (delayMs === 0) {
|
|
3038
|
+
this.#scheduleWake();
|
|
1531
3039
|
}
|
|
1532
3040
|
}
|
|
1533
|
-
return entries.map((entry) => entry.event);
|
|
1534
3041
|
}
|
|
1535
3042
|
#reportUnrouted(envelope) {
|
|
1536
3043
|
const message = `No handler or route for event type, event not stored | type=${envelope.type} id=${envelope.id}`;
|
|
1537
3044
|
if (this.#unroutedEvents === "throw") {
|
|
1538
|
-
throw new ConfigurationError(message);
|
|
3045
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(message);
|
|
1539
3046
|
}
|
|
1540
|
-
this.#logger.warn(`[RelayBus](
|
|
3047
|
+
this.#logger.warn(`[RelayBus](reportUnrouted) ${message}`);
|
|
1541
3048
|
}
|
|
1542
|
-
async #appendInTransaction(entries,
|
|
3049
|
+
async #appendInTransaction(entries, {
|
|
3050
|
+
options,
|
|
3051
|
+
scope,
|
|
3052
|
+
now
|
|
3053
|
+
}) {
|
|
1543
3054
|
const transaction = options.transaction ?? scope?.transaction ?? this.#transactionResolver?.();
|
|
1544
3055
|
if (transaction !== void 0) {
|
|
1545
3056
|
return this.#store.append(transaction, entries, now);
|
|
1546
3057
|
}
|
|
1547
3058
|
if (this.#transactionMode === "required") {
|
|
1548
|
-
throw new TransactionRequiredError(
|
|
3059
|
+
throw new chunk3OHMEMZA_cjs.TransactionRequiredError(
|
|
1549
3060
|
"publish found no transaction. Pass { transaction }, publish inside bus.transaction(), configure transactionResolver, or set transactionMode to optional"
|
|
1550
3061
|
);
|
|
1551
3062
|
}
|
|
@@ -1553,24 +3064,25 @@ var RelayBus = class {
|
|
|
1553
3064
|
(ownTransaction) => this.#store.append(ownTransaction, entries, now)
|
|
1554
3065
|
);
|
|
1555
3066
|
}
|
|
1556
|
-
async #createEnvelope(
|
|
3067
|
+
async #createEnvelope(input) {
|
|
3068
|
+
const { event, options, id, scope, now, correlationId } = input;
|
|
1557
3069
|
if (typeof event.type !== "string") {
|
|
1558
|
-
throw new ConfigurationError(
|
|
3070
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(
|
|
1559
3071
|
"publish expects an instance of a class created with defineEvent"
|
|
1560
3072
|
);
|
|
1561
3073
|
}
|
|
1562
|
-
assertEventType(event.type);
|
|
3074
|
+
chunk3OHMEMZA_cjs.assertEventType(event.type);
|
|
1563
3075
|
const schema = event.constructor.schema;
|
|
1564
|
-
const payload = await validatePayload(event.type, schema, event.payload);
|
|
3076
|
+
const payload = await chunk3OHMEMZA_cjs.validatePayload(event.type, schema, event.payload);
|
|
1565
3077
|
if (options.key !== void 0 && options.key.length === 0) {
|
|
1566
|
-
throw new ConfigurationError("key must not be empty");
|
|
3078
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError("key must not be empty");
|
|
1567
3079
|
}
|
|
1568
3080
|
const eventId = id ?? this.#generateIdentifier();
|
|
1569
3081
|
return {
|
|
1570
3082
|
id: eventId,
|
|
1571
3083
|
type: event.type,
|
|
1572
3084
|
source: this.#source,
|
|
1573
|
-
key: options.key ??
|
|
3085
|
+
key: options.key ?? chunk3OHMEMZA_cjs.keyFromDefinition(event, payload),
|
|
1574
3086
|
payload,
|
|
1575
3087
|
headers: { ...options.headers },
|
|
1576
3088
|
correlationId: correlationId ?? eventId,
|
|
@@ -1581,17 +3093,31 @@ var RelayBus = class {
|
|
|
1581
3093
|
#createDelivery(registration, availableAt) {
|
|
1582
3094
|
return { id: this.#generateIdentifier(), handler: registration.id, availableAt };
|
|
1583
3095
|
}
|
|
1584
|
-
#
|
|
1585
|
-
const
|
|
1586
|
-
const
|
|
1587
|
-
|
|
3096
|
+
#registrationsOf(eventType) {
|
|
3097
|
+
const registrations = this.#registry.forEventType(eventType);
|
|
3098
|
+
const routes = registrations.filter((registration) => registration.forwardsToTransport).length;
|
|
3099
|
+
return { handlers: registrations.length - routes, routes };
|
|
3100
|
+
}
|
|
3101
|
+
#registerRoutes(routes) {
|
|
3102
|
+
for (const route of routes) {
|
|
3103
|
+
this.#requireTransport(route.transport);
|
|
3104
|
+
}
|
|
3105
|
+
for (const group of groupRoutes(routes)) {
|
|
3106
|
+
this.#registerRoute(group);
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
/** Registers one handler for every route to a destination, so a key's events reach it in commit order. */
|
|
3110
|
+
#registerRoute(group) {
|
|
3111
|
+
const transport = this.#transports.get(group.transport);
|
|
3112
|
+
const { route, destination } = group;
|
|
1588
3113
|
this.#registry.register(
|
|
1589
3114
|
{
|
|
1590
|
-
|
|
3115
|
+
eventTypes: group.eventTypes,
|
|
3116
|
+
legacyIds: group.legacyIds,
|
|
1591
3117
|
schema: void 0,
|
|
1592
|
-
handle: (envelope) => transport.publish(envelope, destination),
|
|
3118
|
+
handle: this.#observation?.sender(transport, destination) ?? ((envelope) => transport.publish(envelope, destination)),
|
|
1593
3119
|
options: {
|
|
1594
|
-
id:
|
|
3120
|
+
id: group.id,
|
|
1595
3121
|
...route.retry === void 0 ? {} : { retry: route.retry },
|
|
1596
3122
|
...route.timeout === void 0 ? {} : { timeout: route.timeout }
|
|
1597
3123
|
},
|
|
@@ -1603,32 +3129,38 @@ var RelayBus = class {
|
|
|
1603
3129
|
#requireTransport(name) {
|
|
1604
3130
|
const transport = this.#transports.get(name);
|
|
1605
3131
|
if (transport === void 0) {
|
|
1606
|
-
throw new ConfigurationError(`No transport named '${name}' is configured`);
|
|
3132
|
+
throw new chunk3OHMEMZA_cjs.ConfigurationError(`No transport named '${name}' is configured`);
|
|
1607
3133
|
}
|
|
1608
3134
|
return transport;
|
|
1609
3135
|
}
|
|
1610
3136
|
#scheduleWake() {
|
|
1611
|
-
if (this.#state !== "running") {
|
|
3137
|
+
if (this.#state !== "running" || this.#dispatcher.watchesNewDeliveries) {
|
|
1612
3138
|
return;
|
|
1613
3139
|
}
|
|
1614
|
-
|
|
3140
|
+
chunk3OHMEMZA_cjs.schedule(this.#clock, {
|
|
3141
|
+
run: () => this.#dispatcher.wake(),
|
|
3142
|
+
delayMs: WAKE_DELAY_MS,
|
|
3143
|
+
unref: true
|
|
3144
|
+
});
|
|
1615
3145
|
}
|
|
1616
|
-
|
|
3146
|
+
/** Closes every transport, each at most until the deadline; past it they are asked to force. */
|
|
3147
|
+
async #closeTransports(deadline) {
|
|
3148
|
+
const options = { signal: deadline.signal };
|
|
1617
3149
|
for (const transport of this.#transports.values()) {
|
|
1618
3150
|
try {
|
|
1619
|
-
await transport.close();
|
|
3151
|
+
await deadline.race(transport.close(options));
|
|
1620
3152
|
} catch (error) {
|
|
1621
3153
|
this.#logger.error(
|
|
1622
|
-
`[RelayBus](closeTransports) Failed to close transport | transport=${transport.name} error=${toErrorInfo(error).message}`
|
|
3154
|
+
`[RelayBus](closeTransports) Failed to close transport | transport=${transport.name} error=${chunk3OHMEMZA_cjs.toErrorInfo(error).message}`
|
|
1623
3155
|
);
|
|
1624
3156
|
}
|
|
1625
3157
|
}
|
|
1626
3158
|
}
|
|
1627
3159
|
};
|
|
1628
|
-
function toDescriptor(registration) {
|
|
3160
|
+
function toDescriptor(registration, eventType) {
|
|
1629
3161
|
return {
|
|
1630
3162
|
id: registration.id,
|
|
1631
|
-
eventType
|
|
3163
|
+
eventType,
|
|
1632
3164
|
retry: registration.retry,
|
|
1633
3165
|
timeoutMs: registration.timeoutMs,
|
|
1634
3166
|
concurrency: registration.concurrency,
|
|
@@ -1638,122 +3170,102 @@ function toDescriptor(registration) {
|
|
|
1638
3170
|
}
|
|
1639
3171
|
function describeEvent(event) {
|
|
1640
3172
|
if (typeof event === "string") {
|
|
1641
|
-
assertEventType(event);
|
|
3173
|
+
chunk3OHMEMZA_cjs.assertEventType(event);
|
|
1642
3174
|
return { type: event, schema: void 0 };
|
|
1643
3175
|
}
|
|
1644
|
-
assertEventType(event.type);
|
|
3176
|
+
chunk3OHMEMZA_cjs.assertEventType(event.type);
|
|
1645
3177
|
return { type: event.type, schema: event.schema };
|
|
1646
3178
|
}
|
|
1647
|
-
function normalizeEnvelope(envelope) {
|
|
1648
|
-
if (typeof envelope.id !== "string" || envelope.id.length === 0) {
|
|
1649
|
-
throw new InvalidEnvelopeError("Received event has no id");
|
|
1650
|
-
}
|
|
1651
|
-
if (typeof envelope.type !== "string") {
|
|
1652
|
-
throw new InvalidEnvelopeError(`Received event ${envelope.id} has no type`);
|
|
1653
|
-
}
|
|
1654
|
-
try {
|
|
1655
|
-
assertEventType(envelope.type);
|
|
1656
|
-
} catch (error) {
|
|
1657
|
-
throw new InvalidEnvelopeError(toErrorInfo(error).message);
|
|
1658
|
-
}
|
|
1659
|
-
const occurredAt = toDate(envelope.occurredAt);
|
|
1660
|
-
if (occurredAt === void 0) {
|
|
1661
|
-
throw new InvalidEnvelopeError(`Received event ${envelope.id} has an invalid occurredAt`);
|
|
1662
|
-
}
|
|
1663
|
-
return {
|
|
1664
|
-
id: envelope.id,
|
|
1665
|
-
type: envelope.type,
|
|
1666
|
-
source: typeof envelope.source === "string" ? envelope.source : "unknown",
|
|
1667
|
-
key: optionalText(envelope.key),
|
|
1668
|
-
payload: envelope.payload,
|
|
1669
|
-
headers: { ...envelope.headers },
|
|
1670
|
-
correlationId: optionalText(envelope.correlationId),
|
|
1671
|
-
causationId: optionalText(envelope.causationId),
|
|
1672
|
-
occurredAt
|
|
1673
|
-
};
|
|
1674
|
-
}
|
|
1675
|
-
function toDate(value) {
|
|
1676
|
-
const date = typeof value === "string" ? new Date(value) : value;
|
|
1677
|
-
return date instanceof Date && !Number.isNaN(date.getTime()) ? date : void 0;
|
|
1678
|
-
}
|
|
1679
|
-
function optionalText(value) {
|
|
1680
|
-
return typeof value === "string" && value.length > 0 ? value : null;
|
|
1681
|
-
}
|
|
1682
|
-
function oneOf(value, allowed, name) {
|
|
1683
|
-
if (!allowed.includes(value)) {
|
|
1684
|
-
throw new ConfigurationError(
|
|
1685
|
-
`${name} must be one of ${allowed.map((option) => `'${option}'`).join(", ")}, received '${String(value)}'`
|
|
1686
|
-
);
|
|
1687
|
-
}
|
|
1688
|
-
return value;
|
|
1689
|
-
}
|
|
1690
|
-
function positiveInteger(value, name) {
|
|
1691
|
-
if (!Number.isInteger(value) || value < 1) {
|
|
1692
|
-
throw new ConfigurationError(`${name} must be a positive integer, received ${value}`);
|
|
1693
|
-
}
|
|
1694
|
-
return value;
|
|
1695
|
-
}
|
|
1696
|
-
function createDispatcherSettings(options, generateIdentifier) {
|
|
1697
|
-
const concurrency = positiveInteger(
|
|
1698
|
-
options?.concurrency ?? DEFAULTS.concurrency,
|
|
1699
|
-
"dispatcher.concurrency"
|
|
1700
|
-
);
|
|
1701
|
-
return {
|
|
1702
|
-
owner: `${os.hostname()}:${process.pid}:${generateIdentifier()}`,
|
|
1703
|
-
concurrency,
|
|
1704
|
-
batchSize: positiveInteger(options?.batchSize ?? concurrency, "dispatcher.batchSize"),
|
|
1705
|
-
pollIntervalMs: toTimerMilliseconds(
|
|
1706
|
-
options?.pollInterval ?? DEFAULTS.pollInterval,
|
|
1707
|
-
"dispatcher.pollInterval"
|
|
1708
|
-
),
|
|
1709
|
-
leaseDurationMs: toTimerMilliseconds(
|
|
1710
|
-
options?.leaseDuration ?? DEFAULTS.leaseDuration,
|
|
1711
|
-
"dispatcher.leaseDuration"
|
|
1712
|
-
),
|
|
1713
|
-
shutdownTimeoutMs: toTimerMilliseconds(
|
|
1714
|
-
options?.shutdownTimeout ?? DEFAULTS.shutdownTimeout,
|
|
1715
|
-
"dispatcher.shutdownTimeout"
|
|
1716
|
-
)
|
|
1717
|
-
};
|
|
1718
|
-
}
|
|
1719
|
-
function createRetentionSettings(options) {
|
|
1720
|
-
const completed = options?.completed ?? DEFAULTS.completedRetention;
|
|
1721
|
-
const dead = options?.dead ?? false;
|
|
1722
|
-
return {
|
|
1723
|
-
completedMs: completed === false ? null : toMilliseconds(completed, "retention.completed"),
|
|
1724
|
-
deadMs: dead === false ? null : toMilliseconds(dead, "retention.dead"),
|
|
1725
|
-
intervalMs: toTimerMilliseconds(
|
|
1726
|
-
options?.interval ?? DEFAULTS.retentionInterval,
|
|
1727
|
-
"retention.interval"
|
|
1728
|
-
)
|
|
1729
|
-
};
|
|
1730
|
-
}
|
|
1731
3179
|
|
|
1732
|
-
exports
|
|
3180
|
+
Object.defineProperty(exports, "AttemptsExhaustedError", {
|
|
3181
|
+
enumerable: true,
|
|
3182
|
+
get: function () { return chunk3OHMEMZA_cjs.AttemptsExhaustedError; }
|
|
3183
|
+
});
|
|
3184
|
+
Object.defineProperty(exports, "ConfigurationError", {
|
|
3185
|
+
enumerable: true,
|
|
3186
|
+
get: function () { return chunk3OHMEMZA_cjs.ConfigurationError; }
|
|
3187
|
+
});
|
|
3188
|
+
Object.defineProperty(exports, "DispatcherStoppedError", {
|
|
3189
|
+
enumerable: true,
|
|
3190
|
+
get: function () { return chunk3OHMEMZA_cjs.DispatcherStoppedError; }
|
|
3191
|
+
});
|
|
3192
|
+
Object.defineProperty(exports, "DrainIncompleteError", {
|
|
3193
|
+
enumerable: true,
|
|
3194
|
+
get: function () { return chunk3OHMEMZA_cjs.DrainIncompleteError; }
|
|
3195
|
+
});
|
|
3196
|
+
Object.defineProperty(exports, "EventValidationError", {
|
|
3197
|
+
enumerable: true,
|
|
3198
|
+
get: function () { return chunk3OHMEMZA_cjs.EventValidationError; }
|
|
3199
|
+
});
|
|
3200
|
+
Object.defineProperty(exports, "HandlerTimeoutError", {
|
|
3201
|
+
enumerable: true,
|
|
3202
|
+
get: function () { return chunk3OHMEMZA_cjs.HandlerTimeoutError; }
|
|
3203
|
+
});
|
|
3204
|
+
Object.defineProperty(exports, "InvalidEnvelopeError", {
|
|
3205
|
+
enumerable: true,
|
|
3206
|
+
get: function () { return chunk3OHMEMZA_cjs.InvalidEnvelopeError; }
|
|
3207
|
+
});
|
|
3208
|
+
Object.defineProperty(exports, "LeaseLostError", {
|
|
3209
|
+
enumerable: true,
|
|
3210
|
+
get: function () { return chunk3OHMEMZA_cjs.LeaseLostError; }
|
|
3211
|
+
});
|
|
3212
|
+
Object.defineProperty(exports, "LifecycleError", {
|
|
3213
|
+
enumerable: true,
|
|
3214
|
+
get: function () { return chunk3OHMEMZA_cjs.LifecycleError; }
|
|
3215
|
+
});
|
|
3216
|
+
Object.defineProperty(exports, "NonRetryableError", {
|
|
3217
|
+
enumerable: true,
|
|
3218
|
+
get: function () { return chunk3OHMEMZA_cjs.NonRetryableError; }
|
|
3219
|
+
});
|
|
3220
|
+
Object.defineProperty(exports, "RelayBusError", {
|
|
3221
|
+
enumerable: true,
|
|
3222
|
+
get: function () { return chunk3OHMEMZA_cjs.RelayBusError; }
|
|
3223
|
+
});
|
|
3224
|
+
Object.defineProperty(exports, "TransactionRequiredError", {
|
|
3225
|
+
enumerable: true,
|
|
3226
|
+
get: function () { return chunk3OHMEMZA_cjs.TransactionRequiredError; }
|
|
3227
|
+
});
|
|
3228
|
+
Object.defineProperty(exports, "TransactionTimeoutError", {
|
|
3229
|
+
enumerable: true,
|
|
3230
|
+
get: function () { return chunk3OHMEMZA_cjs.TransactionTimeoutError; }
|
|
3231
|
+
});
|
|
3232
|
+
Object.defineProperty(exports, "assertEventType", {
|
|
3233
|
+
enumerable: true,
|
|
3234
|
+
get: function () { return chunk3OHMEMZA_cjs.assertEventType; }
|
|
3235
|
+
});
|
|
3236
|
+
Object.defineProperty(exports, "defineEvent", {
|
|
3237
|
+
enumerable: true,
|
|
3238
|
+
get: function () { return chunk3OHMEMZA_cjs.defineEvent; }
|
|
3239
|
+
});
|
|
3240
|
+
Object.defineProperty(exports, "toErrorInfo", {
|
|
3241
|
+
enumerable: true,
|
|
3242
|
+
get: function () { return chunk3OHMEMZA_cjs.toErrorInfo; }
|
|
3243
|
+
});
|
|
3244
|
+
Object.defineProperty(exports, "validatePayload", {
|
|
3245
|
+
enumerable: true,
|
|
3246
|
+
get: function () { return chunk3OHMEMZA_cjs.validatePayload; }
|
|
3247
|
+
});
|
|
3248
|
+
exports.CatalogPublisher = CatalogPublisher;
|
|
1733
3249
|
exports.DEFAULT_RETRY_POLICY = DEFAULT_RETRY_POLICY;
|
|
1734
3250
|
exports.DEFAULT_ROUTE_RETRY_POLICY = DEFAULT_ROUTE_RETRY_POLICY;
|
|
1735
3251
|
exports.DELIVERY_STATUSES = DELIVERY_STATUSES;
|
|
1736
|
-
exports.DispatcherStoppedError = DispatcherStoppedError;
|
|
1737
|
-
exports.EventValidationError = EventValidationError;
|
|
1738
|
-
exports.HandlerTimeoutError = HandlerTimeoutError;
|
|
1739
3252
|
exports.InMemoryEventStore = InMemoryEventStore;
|
|
1740
3253
|
exports.InMemoryTransaction = InMemoryTransaction;
|
|
1741
|
-
exports.InvalidEnvelopeError = InvalidEnvelopeError;
|
|
1742
|
-
exports.LeaseLostError = LeaseLostError;
|
|
1743
3254
|
exports.MAX_TIMER_DELAY_MS = MAX_TIMER_DELAY_MS;
|
|
1744
|
-
exports.NonRetryableError = NonRetryableError;
|
|
1745
3255
|
exports.RelayBus = RelayBus;
|
|
1746
|
-
exports.
|
|
1747
|
-
exports.
|
|
1748
|
-
exports.assertEventType = assertEventType;
|
|
3256
|
+
exports.TRACEPARENT_HEADER = TRACEPARENT_HEADER;
|
|
3257
|
+
exports.TRACESTATE_HEADER = TRACESTATE_HEADER;
|
|
1749
3258
|
exports.computeRetryDelay = computeRetryDelay;
|
|
3259
|
+
exports.consume = consume;
|
|
1750
3260
|
exports.createRetryPolicy = createRetryPolicy;
|
|
1751
3261
|
exports.createUuidV7 = createUuidV7;
|
|
1752
|
-
exports.
|
|
3262
|
+
exports.defineCatalog = defineCatalog;
|
|
3263
|
+
exports.defineHandlers = defineHandlers;
|
|
3264
|
+
exports.describeCatalog = describeCatalog;
|
|
1753
3265
|
exports.isRetryableError = isRetryableError;
|
|
3266
|
+
exports.produce = produce;
|
|
1754
3267
|
exports.systemClock = systemClock;
|
|
1755
|
-
exports.toErrorInfo = toErrorInfo;
|
|
1756
3268
|
exports.toMilliseconds = toMilliseconds;
|
|
1757
|
-
exports.
|
|
3269
|
+
exports.traceContextOf = traceContextOf;
|
|
1758
3270
|
//# sourceMappingURL=index.cjs.map
|
|
1759
3271
|
//# sourceMappingURL=index.cjs.map
|