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