@relaybus/core 0.4.0 → 0.4.1

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.
@@ -89,6 +89,55 @@ function writeStructuredCloudEvent(envelope, { time, data }) {
89
89
  return `${JSON.stringify(attributes).slice(0, -1)},"data":${data}}`;
90
90
  }
91
91
 
92
+ // src/internal/json-envelope-wire-types.ts
93
+ function compileWireTypes(wireTypes, { fixed }) {
94
+ if (wireTypes === void 0) {
95
+ return { wireTypes: /* @__PURE__ */ new Map(), eventTypes: /* @__PURE__ */ new Map() };
96
+ }
97
+ if (!isJsonObject(wireTypes) || fixed) {
98
+ throw new ConfigurationError(
99
+ "jsonEnvelope wireTypes must be an object of event type to wire name, with a type at { body } or { header }"
100
+ );
101
+ }
102
+ const entries = Object.entries(wireTypes);
103
+ const eventTypes = /* @__PURE__ */ new Map();
104
+ for (const [type, wire] of entries) {
105
+ assertEventType(type);
106
+ if (typeof wire !== "string" || wire.length === 0 || eventTypes.has(wire)) {
107
+ throw new ConfigurationError(
108
+ `jsonEnvelope wireTypes needs a non-empty wire name for '${type}' that no other type uses`
109
+ );
110
+ }
111
+ eventTypes.set(wire, type);
112
+ }
113
+ return { wireTypes: new Map(entries), eventTypes };
114
+ }
115
+ function wireTypeOf(envelope, { plan, failures }) {
116
+ const wire = plan.wireTypes.get(envelope.type);
117
+ if (wire !== void 0) {
118
+ return wire;
119
+ }
120
+ const other = plan.eventTypes.get(envelope.type);
121
+ if (other !== void 0) {
122
+ throw failures.unencodable(
123
+ `Event ${envelope.id} has type '${envelope.type}', which wireTypes uses as the wire name of '${other}'`
124
+ );
125
+ }
126
+ return envelope.type;
127
+ }
128
+ function eventTypeOf(wire, { plan, failures }) {
129
+ const type = plan.eventTypes.get(wire);
130
+ if (type !== void 0) {
131
+ return type;
132
+ }
133
+ try {
134
+ assertEventType(wire);
135
+ } catch (error) {
136
+ throw failures.invalid(`The event type is not a valid event type: ${error.message}`);
137
+ }
138
+ return wire;
139
+ }
140
+
92
141
  // src/internal/json-envelope-plan.ts
93
142
  var OPTIONAL_NAMES = [
94
143
  "key",
@@ -115,8 +164,9 @@ function compile(mapping, transportIds) {
115
164
  if (topLevelHeaders && payload === void 0) {
116
165
  throw new ConfigurationError("jsonEnvelope headers 'top-level-fields' needs a body payload");
117
166
  }
167
+ const type = compileType(mapping.type);
118
168
  const plan = {
119
- type: compileType(mapping.type),
169
+ type,
120
170
  id: compileId(mapping.id, transportIds),
121
171
  idWhenMissing: isJsonObject(mapping.id) && "whenMissing" in mapping.id,
122
172
  optional,
@@ -126,6 +176,8 @@ function compile(mapping, transportIds) {
126
176
  topLevelNames: /* @__PURE__ */ new Set(),
127
177
  mappedHeaders: /* @__PURE__ */ new Set(),
128
178
  timeFormat: compileTimeFormat(mapping.occurredAt),
179
+ otherFields: compileOtherFields(mapping, payload !== void 0),
180
+ ...compileWireTypes(mapping.wireTypes, { fixed: "fixed" in type }),
129
181
  ...compileExtras(mapping, payload !== void 0)
130
182
  };
131
183
  return assertDisjoint(plan);
@@ -159,6 +211,20 @@ function compileExtras(mapping, hasBody) {
159
211
  bodyOrder
160
212
  };
161
213
  }
214
+ function compileOtherFields(mapping, hasBody) {
215
+ const otherFields = mapping.otherFields;
216
+ if (otherFields === void 0) {
217
+ return void 0;
218
+ }
219
+ if (!hasBody) {
220
+ throw new ConfigurationError("jsonEnvelope otherFields needs a body payload");
221
+ }
222
+ const field = isJsonObject(otherFields) ? otherFields["payloadField"] : void 0;
223
+ if (typeof field !== "string" || field.length === 0) {
224
+ throw new ConfigurationError("jsonEnvelope otherFields must be { payloadField: '<name>' }");
225
+ }
226
+ return field;
227
+ }
162
228
  function compileType(type) {
163
229
  if (isJsonObject(type) && typeof type["fixed"] === "string") {
164
230
  assertEventType(type["fixed"]);
@@ -268,6 +334,56 @@ function assertCompatiblePaths(path, other) {
268
334
  }
269
335
  }
270
336
 
337
+ // src/internal/json-envelope-other-fields.ts
338
+ function unusedFields(body, { plan }) {
339
+ return Object.entries(body).filter(
340
+ ([name, value]) => !plan.topLevelNames.has(name) && !(plan.topLevelHeaders && typeof value === "string")
341
+ );
342
+ }
343
+ function withOtherFields(payload, { body, context }) {
344
+ const field = context.plan.otherFields;
345
+ if (field === void 0) {
346
+ return payload;
347
+ }
348
+ const fields = unusedFields(body, context);
349
+ if (!isJsonObject(payload) && fields.length === 0) {
350
+ return payload;
351
+ }
352
+ if (!isJsonObject(payload) || Object.hasOwn(payload, field)) {
353
+ throw context.failures.invalid(
354
+ `otherFields keeps the unused top-level fields under the payload field '${field}', but the payload is not an object without that field`
355
+ );
356
+ }
357
+ return Object.fromEntries([...Object.entries(payload), [field, Object.fromEntries(fields)]]);
358
+ }
359
+ function splitOtherFields(envelope, context) {
360
+ const field = context.plan.otherFields;
361
+ const { payload } = envelope;
362
+ if (field === void 0 || !isJsonObject(payload) || !Object.hasOwn(payload, field)) {
363
+ return { payload, fields: [] };
364
+ }
365
+ const others = payload[field];
366
+ if (!isJsonObject(others)) {
367
+ throw context.failures.unencodable(
368
+ `Event ${envelope.id} has a payload field '${field}' that is not an object of top-level fields`
369
+ );
370
+ }
371
+ const fields = Object.entries(others);
372
+ const refused = fields.find((entry) => isRefused(entry, context));
373
+ if (refused !== void 0) {
374
+ throw context.failures.unencodable(
375
+ `Event ${envelope.id} keeps a top-level field '${refused[0]}' under '${field}' that the mapping reads as another value`
376
+ );
377
+ }
378
+ return {
379
+ payload: Object.fromEntries(Object.entries(payload).filter(([name]) => name !== field)),
380
+ fields
381
+ };
382
+ }
383
+ function isRefused([name, value], { plan }) {
384
+ return plan.topLevelNames.has(name) || plan.topLevelHeaders && typeof value === "string";
385
+ }
386
+
271
387
  // src/internal/json-envelope-read.ts
272
388
  var RFC3339_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/i;
273
389
  var EPOCH_PATTERN = /^\d+$/;
@@ -304,13 +420,8 @@ function readType(message, { plan, failures }) {
304
420
  return plan.type.fixed;
305
421
  }
306
422
  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;
423
+ const wire = requireText(readText(message, { location, failures }), { location, failures });
424
+ return eventTypeOf(wire, { plan, failures });
314
425
  }
315
426
  function readPayload(body, { plan, failures }) {
316
427
  if (plan.payload === void 0) {
@@ -320,7 +431,10 @@ function readPayload(body, { plan, failures }) {
320
431
  if (payload === void 0) {
321
432
  throw failures.invalid(`The message has no payload at '${plan.payload.join(".")}'`);
322
433
  }
323
- return payload;
434
+ return withOtherFields(payload, {
435
+ body,
436
+ context: { plan, failures }
437
+ });
324
438
  }
325
439
  function readEventHeaders(message, plan) {
326
440
  if (plan.topLevelHeaders) {
@@ -410,7 +524,7 @@ function writeMessage(envelope, { plan, failures }) {
410
524
  };
411
525
  const typeAt = typeLocation(envelope, { plan, failures });
412
526
  if (typeAt !== void 0) {
413
- put(typeAt, envelope.type);
527
+ put(typeAt, wireTypeOf(envelope, { plan, failures }));
414
528
  }
415
529
  if (plan.id !== void 0) {
416
530
  put(plan.id, envelope.id);
@@ -421,12 +535,8 @@ function writeMessage(envelope, { plan, failures }) {
421
535
  put(location, value);
422
536
  }
423
537
  }
424
- const body = writeBody(envelope, { plan, root });
425
- if (plan.topLevelHeaders) {
426
- spreadHeaders(envelope, { plan, failures, root: body });
427
- }
428
538
  return {
429
- body,
539
+ body: writeBody(envelope, { plan, failures, root }),
430
540
  headers: Object.fromEntries([
431
541
  ...eventHeaderEntries(envelope, { plan, failures }),
432
542
  ...headers,
@@ -463,11 +573,12 @@ function optionalValues(envelope, { plan, failures }) {
463
573
  ["causationId", envelope.causationId]
464
574
  ];
465
575
  }
466
- function writeBody(envelope, { plan, root }) {
576
+ function writeBody(envelope, { plan, failures, root }) {
577
+ const { payload, fields } = splitOtherFields(envelope, { plan, failures });
467
578
  if (plan.payload === void 0) {
468
- return envelope.payload;
579
+ return payload;
469
580
  }
470
- setAt(root, { path: plan.payload, value: envelope.payload });
581
+ setAt(root, { path: plan.payload, value: payload });
471
582
  if (plan.headers !== void 0) {
472
583
  setAt(root, {
473
584
  path: plan.headers,
@@ -477,6 +588,10 @@ function writeBody(envelope, { plan, root }) {
477
588
  for (const { path, value } of plan.extraBody) {
478
589
  setAt(root, { path, value: typeof value === "function" ? value(envelope) : value });
479
590
  }
591
+ if (plan.topLevelHeaders) {
592
+ spreadHeaders(envelope, { plan, failures, root });
593
+ }
594
+ spreadOtherFields(envelope, { failures, fields, root });
480
595
  return ordered(root, plan.bodyOrder);
481
596
  }
482
597
  function spreadHeaders(envelope, { plan, failures, root }) {
@@ -489,6 +604,20 @@ function spreadHeaders(envelope, { plan, failures, root }) {
489
604
  setAt(root, { path: [name], value });
490
605
  }
491
606
  }
607
+ function spreadOtherFields(envelope, {
608
+ failures,
609
+ fields,
610
+ root
611
+ }) {
612
+ for (const [name, value] of fields) {
613
+ if (Object.hasOwn(root, name)) {
614
+ throw failures.unencodable(
615
+ `Event ${envelope.id} keeps a top-level field '${name}' that an event header also writes`
616
+ );
617
+ }
618
+ setAt(root, { path: [name], value });
619
+ }
620
+ }
492
621
  function timeValue(text, { envelope, format }) {
493
622
  const milliseconds = envelope.occurredAt.getTime();
494
623
  if (format === "epoch-millis") {