@relaybus/core 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,10 @@
1
- import { ConfigurationError, assertEventType } from '../chunk-WS274DL3.js';
2
- export { assertPositiveInteger, schedule, sleep } from '../chunk-WS274DL3.js';
1
+ import { PositiveInteger, ConfigurationError, assertEventType, DeliveryLeaseIdentity } from '../chunk-NOR5JHVN.js';
2
+ export { CompositeKey, DeliveryLeaseIdentity, LeaseSettlement, Milliseconds, PositiveInteger, schedule, sleep } from '../chunk-NOR5JHVN.js';
3
+
4
+ // src/internal/validation.ts
5
+ function assertPositiveInteger(value, name) {
6
+ return PositiveInteger.of(value, name);
7
+ }
3
8
 
4
9
  // src/internal/field-extractors.ts
5
10
  function fromHeader(name) {
@@ -84,7 +89,7 @@ function writeStructuredCloudEvent(envelope, { time, data }) {
84
89
  return `${JSON.stringify(attributes).slice(0, -1)},"data":${data}}`;
85
90
  }
86
91
 
87
- // src/internal/json-envelope.ts
92
+ // src/internal/json-envelope-plan.ts
88
93
  var OPTIONAL_NAMES = [
89
94
  "key",
90
95
  "source",
@@ -92,19 +97,8 @@ var OPTIONAL_NAMES = [
92
97
  "correlationId",
93
98
  "causationId"
94
99
  ];
95
- var MAXIMUM_RFC3339_YEAR = 9999;
96
- var RFC3339_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/i;
100
+ var TIME_FORMATS = ["rfc3339", "epoch-millis", "epoch-seconds"];
97
101
  var ARRAY_INDEX = /^(?:0|[1-9]\d*)$/;
98
- function createJsonEnvelopeFormat(mapping, {
99
- transportIds,
100
- failures
101
- }) {
102
- const plan = compile(mapping, transportIds);
103
- return {
104
- write: (envelope) => writeMessage(envelope, { plan, failures }),
105
- read: (message) => readMessage(message, { plan, failures })
106
- };
107
- }
108
102
  function compile(mapping, transportIds) {
109
103
  if (!isJsonObject(mapping)) {
110
104
  throw new ConfigurationError("jsonEnvelope needs a mapping object");
@@ -116,16 +110,55 @@ function compile(mapping, transportIds) {
116
110
  optional[name] = toLocation(field, name);
117
111
  }
118
112
  }
113
+ const payload = compilePayload(mapping.payload);
114
+ const topLevelHeaders = mapping.headers === "top-level-fields";
115
+ if (topLevelHeaders && payload === void 0) {
116
+ throw new ConfigurationError("jsonEnvelope headers 'top-level-fields' needs a body payload");
117
+ }
119
118
  const plan = {
120
119
  type: compileType(mapping.type),
121
120
  id: compileId(mapping.id, transportIds),
121
+ idWhenMissing: isJsonObject(mapping.id) && "whenMissing" in mapping.id,
122
122
  optional,
123
- payload: compilePayload(mapping.payload),
124
- headers: compileHeaders(mapping.headers),
125
- mappedHeaders: /* @__PURE__ */ new Set()
123
+ payload,
124
+ headers: topLevelHeaders ? void 0 : compileHeaders(mapping.headers),
125
+ topLevelHeaders,
126
+ topLevelNames: /* @__PURE__ */ new Set(),
127
+ mappedHeaders: /* @__PURE__ */ new Set(),
128
+ timeFormat: compileTimeFormat(mapping.occurredAt),
129
+ ...compileExtras(mapping, payload !== void 0)
126
130
  };
127
131
  return assertDisjoint(plan);
128
132
  }
133
+ function compileTimeFormat(field) {
134
+ const format = isJsonObject(field) ? field["format"] : void 0;
135
+ if (format === void 0) {
136
+ return "rfc3339";
137
+ }
138
+ if (!TIME_FORMATS.includes(format)) {
139
+ throw new ConfigurationError(
140
+ `jsonEnvelope occurredAt format must be one of ${TIME_FORMATS.map((name) => `'${name}'`).join(", ")}`
141
+ );
142
+ }
143
+ return format;
144
+ }
145
+ function compileExtras(mapping, hasBody) {
146
+ const extra = mapping.extra ?? {};
147
+ const bodyOrder = mapping.bodyOrder ?? [];
148
+ if (!hasBody && (extra.body !== void 0 || bodyOrder.length > 0)) {
149
+ throw new ConfigurationError(
150
+ "jsonEnvelope with a whole-body payload has no body for extra values or a body order"
151
+ );
152
+ }
153
+ return {
154
+ extraBody: Object.entries(extra.body ?? {}).map(([path, value]) => ({
155
+ path: toPath(path, "extra body value"),
156
+ value
157
+ })),
158
+ extraHeaders: Object.entries(extra.headers ?? {}),
159
+ bodyOrder
160
+ };
161
+ }
129
162
  function compileType(type) {
130
163
  if (isJsonObject(type) && typeof type["fixed"] === "string") {
131
164
  assertEventType(type["fixed"]);
@@ -140,11 +173,15 @@ function compileId(id, transportIds) {
140
173
  if (typeof id === "string" && transportIds.includes(id)) {
141
174
  return void 0;
142
175
  }
176
+ const names = transportIds.map((name) => `'${name}'`).join(", ");
143
177
  if (!isJsonObject(id)) {
144
178
  throw new ConfigurationError(
145
- `jsonEnvelope needs an id: { body }, { header } or one of ${transportIds.map((name) => `'${name}'`).join(", ")}`
179
+ `jsonEnvelope needs an id: { body }, { header } or one of ${names}`
146
180
  );
147
181
  }
182
+ if ("whenMissing" in id && !transportIds.includes(id["whenMissing"])) {
183
+ throw new ConfigurationError(`jsonEnvelope id whenMissing must be one of ${names}`);
184
+ }
148
185
  return toLocation(id, "id");
149
186
  }
150
187
  function compilePayload(payload) {
@@ -193,17 +230,25 @@ function assertDisjoint(plan) {
193
230
  const locations = [plan.type, plan.id, ...Object.values(plan.optional)].filter(
194
231
  (location) => location !== void 0 && !("fixed" in location)
195
232
  );
196
- const headers = locations.flatMap((location) => "header" in location ? [location.header] : []);
233
+ const headers = [
234
+ ...locations.flatMap((location) => "header" in location ? [location.header] : []),
235
+ ...plan.extraHeaders.map(([name]) => name)
236
+ ];
197
237
  const duplicate = headers.find((name, index) => headers.indexOf(name) !== index);
198
238
  if (duplicate !== void 0) {
199
239
  throw new ConfigurationError(`jsonEnvelope maps header '${duplicate}' twice`);
200
240
  }
201
241
  const paths = [
202
242
  ...locations.flatMap((location) => "path" in location ? [location.path] : []),
203
- ...[plan.payload, plan.headers].filter((path) => path !== void 0)
243
+ ...[plan.payload, plan.headers].filter((path) => path !== void 0),
244
+ ...plan.extraBody.map(({ path }) => path)
204
245
  ];
205
246
  assertNoNestedPaths(paths);
206
- return { ...plan, mappedHeaders: new Set(headers) };
247
+ return {
248
+ ...plan,
249
+ mappedHeaders: new Set(headers),
250
+ topLevelNames: new Set(paths.map((path) => path[0]))
251
+ };
207
252
  }
208
253
  function assertNoNestedPaths(paths) {
209
254
  paths.forEach((path, index) => {
@@ -222,44 +267,198 @@ function assertCompatiblePaths(path, other) {
222
267
  throw new ConfigurationError(`jsonEnvelope body paths ${both} mix an array index and a name`);
223
268
  }
224
269
  }
270
+
271
+ // src/internal/json-envelope-read.ts
272
+ var RFC3339_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/i;
273
+ var EPOCH_PATTERN = /^\d+$/;
274
+ var MILLISECONDS_PER_SECOND = 1e3;
275
+ function readMessage(message, context) {
276
+ const { plan, failures } = context;
277
+ if (plan.payload !== void 0 && !isJsonObject(message.body)) {
278
+ throw failures.invalid("The message body must be a JSON object");
279
+ }
280
+ const read = (location) => location === void 0 ? void 0 : readText(message, { location, failures });
281
+ const fields = {
282
+ id: readId(read(plan.id), context),
283
+ type: readType(message, context),
284
+ payload: readPayload(message.body, context),
285
+ key: read(plan.optional.key),
286
+ source: read(plan.optional.source),
287
+ headers: readEventHeaders(message, plan),
288
+ correlationId: read(plan.optional.correlationId) ?? null,
289
+ causationId: read(plan.optional.causationId) ?? null,
290
+ occurredAt: readTime(read(plan.optional.occurredAt), context)
291
+ };
292
+ return Object.fromEntries(
293
+ Object.entries(fields).filter((entry) => entry[1] !== void 0)
294
+ );
295
+ }
296
+ function readId(id, { plan, failures }) {
297
+ if (plan.id === void 0 || plan.idWhenMissing) {
298
+ return id;
299
+ }
300
+ return requireText(id, { location: plan.id, failures });
301
+ }
302
+ function readType(message, { plan, failures }) {
303
+ if ("fixed" in plan.type) {
304
+ return plan.type.fixed;
305
+ }
306
+ const location = plan.type;
307
+ const type = requireText(readText(message, { location, failures }), { location, failures });
308
+ try {
309
+ assertEventType(type);
310
+ } catch (error) {
311
+ throw failures.invalid(`The event type is not a valid event type: ${error.message}`);
312
+ }
313
+ return type;
314
+ }
315
+ function readPayload(body, { plan, failures }) {
316
+ if (plan.payload === void 0) {
317
+ return body;
318
+ }
319
+ const payload = valueAt(body, plan.payload);
320
+ if (payload === void 0) {
321
+ throw failures.invalid(`The message has no payload at '${plan.payload.join(".")}'`);
322
+ }
323
+ return payload;
324
+ }
325
+ function readEventHeaders(message, plan) {
326
+ if (plan.topLevelHeaders) {
327
+ return textEntries(
328
+ Object.entries(message.body).filter(
329
+ ([name]) => !plan.topLevelNames.has(name)
330
+ )
331
+ );
332
+ }
333
+ if (plan.headers === void 0) {
334
+ return Object.fromEntries(
335
+ Object.entries(message.headers).filter(([name]) => !plan.mappedHeaders.has(name))
336
+ );
337
+ }
338
+ const headers = valueAt(message.body, plan.headers);
339
+ return isJsonObject(headers) ? textEntries(Object.entries(headers)) : {};
340
+ }
341
+ function textEntries(entries) {
342
+ return Object.fromEntries(
343
+ entries.filter((entry) => typeof entry[1] === "string")
344
+ );
345
+ }
346
+ function readTime(text, { plan, failures }) {
347
+ if (text === void 0) {
348
+ return void 0;
349
+ }
350
+ if (plan.timeFormat !== "rfc3339") {
351
+ const time2 = new Date(
352
+ Number(text) * (plan.timeFormat === "epoch-seconds" ? MILLISECONDS_PER_SECOND : 1)
353
+ );
354
+ if (!EPOCH_PATTERN.test(text) || Number.isNaN(time2.getTime())) {
355
+ throw failures.invalid(`The event time is not a whole ${plan.timeFormat} number: '${text}'`);
356
+ }
357
+ return time2;
358
+ }
359
+ const time = new Date(text);
360
+ if (!RFC3339_PATTERN.test(text) || Number.isNaN(time.getTime())) {
361
+ throw failures.invalid(`The event time is not an RFC 3339 timestamp: '${text}'`);
362
+ }
363
+ return time;
364
+ }
365
+ function readText(message, { location, failures }) {
366
+ const value = "header" in location ? Object.hasOwn(message.headers, location.header) ? message.headers[location.header] : void 0 : valueAt(message.body, location.path);
367
+ if (value === void 0 || value === null || value === "") {
368
+ return void 0;
369
+ }
370
+ if (typeof value === "string") {
371
+ return value;
372
+ }
373
+ if (typeof value === "number" && Number.isSafeInteger(value)) {
374
+ return String(value);
375
+ }
376
+ throw failures.invalid(`The ${location.label} at ${describe(location)} must be a string`);
377
+ }
378
+ function requireText(value, { location, failures }) {
379
+ if (value === void 0) {
380
+ throw failures.invalid(`The message has no ${location.label} at ${describe(location)}`);
381
+ }
382
+ return value;
383
+ }
384
+ function describe(location) {
385
+ return "header" in location ? `header '${location.header}'` : `'${location.path.join(".")}'`;
386
+ }
387
+ function valueAt(body, path) {
388
+ let value = body;
389
+ for (const segment of path) {
390
+ if (typeof value !== "object" || value === null || !Object.hasOwn(value, segment)) {
391
+ return void 0;
392
+ }
393
+ value = value[segment];
394
+ }
395
+ return value;
396
+ }
397
+
398
+ // src/internal/json-envelope-write.ts
399
+ var MAXIMUM_RFC3339_YEAR = 9999;
400
+ var MILLISECONDS_PER_SECOND2 = 1e3;
225
401
  function writeMessage(envelope, { plan, failures }) {
226
402
  const root = {};
227
403
  const headers = [];
228
404
  const put = (location, value) => {
229
405
  if ("header" in location) {
230
- headers.push([location.header, value]);
406
+ headers.push([location.header, String(value)]);
231
407
  } else {
232
408
  setAt(root, { path: location.path, value });
233
409
  }
234
410
  };
235
- if ("fixed" in plan.type) {
236
- if (envelope.type !== plan.type.fixed) {
237
- throw failures.unencodable(
238
- `Event ${envelope.id} has type '${envelope.type}', but this route carries only '${plan.type.fixed}'`
239
- );
240
- }
241
- } else {
242
- put(plan.type, envelope.type);
411
+ const typeAt = typeLocation(envelope, { plan, failures });
412
+ if (typeAt !== void 0) {
413
+ put(typeAt, envelope.type);
243
414
  }
244
415
  if (plan.id !== void 0) {
245
416
  put(plan.id, envelope.id);
246
417
  }
247
- for (const [name, value] of optionalValues(envelope, failures)) {
418
+ for (const [name, value] of optionalValues(envelope, { plan, failures })) {
248
419
  const location = plan.optional[name];
249
420
  if (location !== void 0 && value !== null) {
250
421
  put(location, value);
251
422
  }
252
423
  }
424
+ const body = writeBody(envelope, { plan, root });
425
+ if (plan.topLevelHeaders) {
426
+ spreadHeaders(envelope, { plan, failures, root: body });
427
+ }
253
428
  return {
254
- body: writeBody(envelope, { plan, root }),
255
- headers: Object.fromEntries([...eventHeaderEntries(envelope, { plan, failures }), ...headers])
429
+ body,
430
+ headers: Object.fromEntries([
431
+ ...eventHeaderEntries(envelope, { plan, failures }),
432
+ ...headers,
433
+ ...extraHeaderEntries(envelope, plan)
434
+ ])
256
435
  };
257
436
  }
258
- function optionalValues(envelope, failures) {
437
+ function typeLocation(envelope, { plan, failures }) {
438
+ if (!("fixed" in plan.type)) {
439
+ return plan.type;
440
+ }
441
+ if (envelope.type !== plan.type.fixed) {
442
+ throw failures.unencodable(
443
+ `Event ${envelope.id} has type '${envelope.type}', but this route carries only '${plan.type.fixed}'`
444
+ );
445
+ }
446
+ return void 0;
447
+ }
448
+ function extraHeaderEntries(envelope, plan) {
449
+ return plan.extraHeaders.map(([name, value]) => [
450
+ name,
451
+ typeof value === "function" ? value(envelope) : value
452
+ ]);
453
+ }
454
+ function optionalValues(envelope, { plan, failures }) {
259
455
  return [
260
456
  ["key", envelope.key],
261
457
  ["source", envelope.source],
262
- ["occurredAt", formatTime(envelope, failures)],
458
+ [
459
+ "occurredAt",
460
+ timeValue(formatTime(envelope, failures), { envelope, format: plan.timeFormat })
461
+ ],
263
462
  ["correlationId", envelope.correlationId],
264
463
  ["causationId", envelope.causationId]
265
464
  ];
@@ -275,10 +474,43 @@ function writeBody(envelope, { plan, root }) {
275
474
  value: Object.fromEntries(Object.entries(envelope.headers))
276
475
  });
277
476
  }
278
- return root;
477
+ for (const { path, value } of plan.extraBody) {
478
+ setAt(root, { path, value: typeof value === "function" ? value(envelope) : value });
479
+ }
480
+ return ordered(root, plan.bodyOrder);
481
+ }
482
+ function spreadHeaders(envelope, { plan, failures, root }) {
483
+ for (const [name, value] of Object.entries(envelope.headers)) {
484
+ if (plan.topLevelNames.has(name)) {
485
+ throw failures.unencodable(
486
+ `Event ${envelope.id} has a header '${name}', which the mapping uses for another body field`
487
+ );
488
+ }
489
+ setAt(root, { path: [name], value });
490
+ }
491
+ }
492
+ function timeValue(text, { envelope, format }) {
493
+ const milliseconds = envelope.occurredAt.getTime();
494
+ if (format === "epoch-millis") {
495
+ return milliseconds;
496
+ }
497
+ return format === "epoch-seconds" ? Math.floor(milliseconds / MILLISECONDS_PER_SECOND2) : text;
498
+ }
499
+ function ordered(root, order) {
500
+ if (order.length === 0) {
501
+ return root;
502
+ }
503
+ const result = {};
504
+ const names = [...order.filter((name) => Object.hasOwn(root, name)), ...Object.keys(root)];
505
+ for (const name of names) {
506
+ if (!Object.hasOwn(result, name)) {
507
+ defineValue(result, { name, value: root[name] });
508
+ }
509
+ }
510
+ return result;
279
511
  }
280
512
  function eventHeaderEntries(envelope, { plan, failures }) {
281
- if (plan.headers !== void 0) {
513
+ if (plan.headers !== void 0 || plan.topLevelHeaders) {
282
514
  return [];
283
515
  }
284
516
  const entries = Object.entries(envelope.headers);
@@ -320,106 +552,200 @@ function defineValue(target, { name, value }) {
320
552
  configurable: true
321
553
  });
322
554
  }
323
- function readMessage(message, context) {
324
- const { plan, failures } = context;
325
- if (plan.payload !== void 0 && !isJsonObject(message.body)) {
326
- throw failures.invalid("The message body must be a JSON object");
327
- }
328
- const read = (location) => location === void 0 ? void 0 : readText(message, { location, failures });
329
- const fields = {
330
- id: plan.id === void 0 ? void 0 : requireText(read(plan.id), { location: plan.id, failures }),
331
- type: readType(message, context),
332
- payload: readPayload(message.body, context),
333
- key: read(plan.optional.key),
334
- source: read(plan.optional.source),
335
- headers: readEventHeaders(message, plan),
336
- correlationId: read(plan.optional.correlationId) ?? null,
337
- causationId: read(plan.optional.causationId) ?? null,
338
- occurredAt: readTime(read(plan.optional.occurredAt), failures)
555
+
556
+ // src/internal/json-envelope.ts
557
+ function createJsonEnvelopeFormat(mapping, {
558
+ transportIds,
559
+ failures
560
+ }) {
561
+ const plan = compile(mapping, transportIds);
562
+ return {
563
+ write: (envelope) => writeMessage(envelope, { plan, failures }),
564
+ read: (message) => readMessage(message, { plan, failures })
339
565
  };
340
- return Object.fromEntries(
341
- Object.entries(fields).filter((entry) => entry[1] !== void 0)
342
- );
343
566
  }
344
- function readType(message, { plan, failures }) {
345
- if ("fixed" in plan.type) {
346
- return plan.type.fixed;
347
- }
348
- const location = plan.type;
349
- const type = requireText(readText(message, { location, failures }), { location, failures });
350
- try {
351
- assertEventType(type);
352
- } catch (error) {
353
- throw failures.invalid(`The event type is not a valid event type: ${error.message}`);
354
- }
355
- return type;
356
- }
357
- function readPayload(body, { plan, failures }) {
358
- if (plan.payload === void 0) {
359
- return body;
567
+
568
+ // src/store-kit/handed-out-ledger.ts
569
+ var HandedOutLedger = class {
570
+ #entries = /* @__PURE__ */ new Map();
571
+ register(lease, entry) {
572
+ this.#entries.set(DeliveryLeaseIdentity.of(lease.deliveryId, lease.leaseToken), entry);
573
+ }
574
+ /** Whether the claim with this identity ({@link DeliveryLeaseIdentity.of}) is handed out. */
575
+ has(identity) {
576
+ return this.#entries.has(identity);
577
+ }
578
+ forget(lease) {
579
+ this.#entries.delete(DeliveryLeaseIdentity.of(lease.deliveryId, lease.leaseToken));
580
+ }
581
+ /** Forgets every entry `settled` says is settled. */
582
+ forgetWhere(settled) {
583
+ for (const [identity, entry] of this.#entries) {
584
+ if (settled(entry, identity)) {
585
+ this.#entries.delete(identity);
586
+ }
587
+ }
360
588
  }
361
- const payload = valueAt(body, plan.payload);
362
- if (payload === void 0) {
363
- throw failures.invalid(`The message has no payload at '${plan.payload.join(".")}'`);
589
+ /** The entries handed out to `owner`. */
590
+ entriesOf(owner) {
591
+ return [...this.#entries.values()].filter((entry) => entry.owner === owner);
364
592
  }
365
- return payload;
366
- }
367
- function readEventHeaders(message, plan) {
368
- if (plan.headers === void 0) {
369
- return Object.fromEntries(
370
- Object.entries(message.headers).filter(([name]) => !plan.mappedHeaders.has(name))
593
+ };
594
+
595
+ // src/store-kit/owner-serialized-claims.ts
596
+ var OwnerSerializedClaims = class {
597
+ /** The last claim of each owner still running; the next one waits for it. */
598
+ #queues = /* @__PURE__ */ new Map();
599
+ claim(request) {
600
+ if (this.claimsNothing(request)) {
601
+ return Promise.resolve([]);
602
+ }
603
+ return this.#serialized(request.leaseOwner, () => this.claimInTurn(request));
604
+ }
605
+ /** Whether the request cannot claim anything: no handlers, or a limit that is not above 0. */
606
+ claimsNothing(request) {
607
+ return request.handlers.length === 0 || !(request.limit > 0);
608
+ }
609
+ #serialized(owner, work) {
610
+ const previous = this.#queues.get(owner) ?? Promise.resolve();
611
+ const run = previous.then(work, work);
612
+ const tail = run.then(
613
+ () => void 0,
614
+ () => void 0
371
615
  );
616
+ this.#queues.set(owner, tail);
617
+ void tail.then(() => {
618
+ if (this.#queues.get(owner) === tail) {
619
+ this.#queues.delete(owner);
620
+ }
621
+ });
622
+ return run;
372
623
  }
373
- const headers = valueAt(message.body, plan.headers);
374
- return isJsonObject(headers) ? Object.fromEntries(
375
- Object.entries(headers).filter(
376
- (entry) => typeof entry[1] === "string"
377
- )
378
- ) : {};
379
- }
380
- function readTime(text, failures) {
381
- if (text === void 0) {
382
- return void 0;
383
- }
384
- const time = new Date(text);
385
- if (!RFC3339_PATTERN.test(text) || Number.isNaN(time.getTime())) {
386
- throw failures.invalid(`The event time is not an RFC 3339 timestamp: '${text}'`);
387
- }
388
- return time;
389
- }
390
- function readText(message, { location, failures }) {
391
- const value = "header" in location ? Object.hasOwn(message.headers, location.header) ? message.headers[location.header] : void 0 : valueAt(message.body, location.path);
392
- if (value === void 0 || value === null || value === "") {
393
- return void 0;
624
+ };
625
+
626
+ // src/transport-kit/decoded-event-reader.ts
627
+ function observeRejection(value) {
628
+ if (typeof value === "object" && value !== null && "then" in value) {
629
+ Promise.resolve(value).catch(() => void 0);
630
+ }
631
+ }
632
+ var DecodedEventReader = class {
633
+ /**
634
+ * Fills the fields a decoder left out and guards against a decoder that returned no object,
635
+ * which would otherwise surface as a retried `TypeError` instead of a malformed message.
636
+ */
637
+ complete(decoded, message) {
638
+ if (typeof decoded !== "object" || decoded === null) {
639
+ throw this.invalid("The decoder must return an event object synchronously");
640
+ }
641
+ if ("then" in decoded) {
642
+ observeRejection(decoded);
643
+ throw this.invalid("The decoder must return an event object synchronously");
644
+ }
645
+ const event = decoded;
646
+ return {
647
+ id: event.id,
648
+ type: event.type,
649
+ source: event.source ?? this.defaultSource(message),
650
+ key: event.key === void 0 ? this.defaultKey(message) : event.key,
651
+ payload: event.payload,
652
+ headers: event.headers ?? {},
653
+ correlationId: event.correlationId ?? null,
654
+ causationId: event.causationId ?? null,
655
+ occurredAt: event.occurredAt ?? this.defaultTime(message)
656
+ };
657
+ }
658
+ /** The text an option gives for `plain`; undefined when it gives none or `''`. */
659
+ text(option, { plain, name }) {
660
+ const value = typeof option === "function" ? option(plain) : option;
661
+ if (value === void 0 || value === null) {
662
+ return void 0;
663
+ }
664
+ if (typeof value !== "string") {
665
+ observeRejection(value);
666
+ throw this.invalid(`The ${name} option returned ${typeof value}, expected a string`);
667
+ }
668
+ return value.length === 0 ? void 0 : value;
394
669
  }
395
- if (typeof value === "string") {
670
+ };
671
+
672
+ // src/transport-kit/subscribing-transport.ts
673
+ var SubscribingTransport = class {
674
+ /**
675
+ * Subscribes to `destinations`. Not async itself: the subscription's own promise is returned
676
+ * as it is, so its checks reject rather than throw.
677
+ */
678
+ // eslint-disable-next-line relaybus/max-two-params -- the Transport.subscribe signature
679
+ subscribe(destinations, onMessage, hooks) {
680
+ return this.openSubscription({ destinations, onMessage, hooks });
681
+ }
682
+ };
683
+
684
+ // src/values/bounded-integer.ts
685
+ var BoundedInteger = class {
686
+ /** Returns `value` when it is an integer within the bounds, else throws a {@link ConfigurationError}. */
687
+ static of(value, { name, minimum, maximum }) {
688
+ if (!Number.isInteger(value) || value < minimum || value > maximum) {
689
+ throw new ConfigurationError(
690
+ `${name} must be an integer from ${minimum} to ${maximum}, received ${value}`
691
+ );
692
+ }
396
693
  return value;
397
694
  }
398
- if (typeof value === "number" && Number.isSafeInteger(value)) {
399
- return String(value);
695
+ };
696
+
697
+ // src/values/exponential-backoff.ts
698
+ var ExponentialBackoff = class {
699
+ initialMs;
700
+ maxMs;
701
+ constructor({ initialMs, maxMs }) {
702
+ this.initialMs = initialMs;
703
+ this.maxMs = maxMs;
704
+ }
705
+ /** The delay after failed attempt `attempt` (1-based). */
706
+ delayFor(attempt) {
707
+ return Math.min(this.initialMs * 2 ** (attempt - 1), this.maxMs);
708
+ }
709
+ };
710
+
711
+ // src/values/lane-lease-token.ts
712
+ var LANE_DIGITS = 3;
713
+ var LaneLeaseTokenFormat = class {
714
+ #pattern;
715
+ #laneCount;
716
+ constructor(limits) {
717
+ this.#laneCount = limits.laneCount;
718
+ this.#pattern = new RegExp(
719
+ `^(\\d{1,${LANE_DIGITS}}):(\\d{1,${limits.epochDigits}}):(\\d{1,${limits.claimsDigits}})$`
720
+ );
400
721
  }
401
- throw failures.invalid(`The ${location.label} at ${describe(location)} must be a string`);
402
- }
403
- function requireText(value, { location, failures }) {
404
- if (value === void 0) {
405
- throw failures.invalid(`The message has no ${location.label} at ${describe(location)}`);
722
+ format(lane, { epoch, claims }) {
723
+ return `${lane}:${epoch}:${claims}`;
406
724
  }
407
- return value;
408
- }
409
- function describe(location) {
410
- return "header" in location ? `header '${location.header}'` : `'${location.path.join(".")}'`;
411
- }
412
- function valueAt(body, path) {
413
- let value = body;
414
- for (const segment of path) {
415
- if (typeof value !== "object" || value === null || !Object.hasOwn(value, segment)) {
725
+ /** The parts of `token`; undefined when the store cannot have issued it. */
726
+ parse(token) {
727
+ const match = this.#pattern.exec(token);
728
+ if (match === null || Number(match[1]) >= this.#laneCount) {
416
729
  return void 0;
417
730
  }
418
- value = value[segment];
731
+ return { lane: Number(match[1]), epoch: match[2], claims: Number(match[3]) };
419
732
  }
420
- return value;
421
- }
733
+ };
734
+
735
+ // src/values/non-empty-text.ts
736
+ var NonEmptyText = class _NonEmptyText {
737
+ static of(value, name) {
738
+ if (value.length === 0) {
739
+ throw new ConfigurationError(`${name} must not be empty`);
740
+ }
741
+ return value;
742
+ }
743
+ /** Like {@link NonEmptyText.of}, for an option that may be left out. */
744
+ static optional(value, name) {
745
+ return value === void 0 ? void 0 : _NonEmptyText.of(value, name);
746
+ }
747
+ };
422
748
 
423
- export { createCloudEventReaders, createJsonEnvelopeFormat, fromHeader, fromPayloadField, isJsonObject, mediaTypeOf, writeStructuredCloudEvent };
749
+ export { BoundedInteger, DecodedEventReader, ExponentialBackoff, HandedOutLedger, LaneLeaseTokenFormat, NonEmptyText, OwnerSerializedClaims, SubscribingTransport, assertPositiveInteger, createCloudEventReaders, createJsonEnvelopeFormat, fromHeader, fromPayloadField, isJsonObject, mediaTypeOf, observeRejection, writeStructuredCloudEvent };
424
750
  //# sourceMappingURL=index.js.map
425
751
  //# sourceMappingURL=index.js.map