@relaybus/core 0.3.1 → 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.
- package/README.md +6 -1
- package/dist/index.cjs +77 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +78 -14
- package/dist/index.js.map +1 -1
- package/dist/internal/index.cjs +165 -31
- package/dist/internal/index.cjs.map +1 -1
- package/dist/internal/index.d.cts +54 -9
- package/dist/internal/index.d.ts +54 -9
- package/dist/internal/index.js +165 -31
- package/dist/internal/index.js.map +1 -1
- package/dist/{transport-BlfzIBZD.d.cts → transport-DIFdeXQY.d.cts} +2 -1
- package/dist/{transport-BlfzIBZD.d.ts → transport-DIFdeXQY.d.ts} +2 -1
- package/package.json +1 -1
package/dist/internal/index.cjs
CHANGED
|
@@ -98,6 +98,7 @@ var OPTIONAL_NAMES = [
|
|
|
98
98
|
"correlationId",
|
|
99
99
|
"causationId"
|
|
100
100
|
];
|
|
101
|
+
var TIME_FORMATS = ["rfc3339", "epoch-millis", "epoch-seconds"];
|
|
101
102
|
var ARRAY_INDEX = /^(?:0|[1-9]\d*)$/;
|
|
102
103
|
function compile(mapping, transportIds) {
|
|
103
104
|
if (!isJsonObject(mapping)) {
|
|
@@ -110,16 +111,55 @@ function compile(mapping, transportIds) {
|
|
|
110
111
|
optional[name] = toLocation(field, name);
|
|
111
112
|
}
|
|
112
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
|
+
}
|
|
113
119
|
const plan = {
|
|
114
120
|
type: compileType(mapping.type),
|
|
115
121
|
id: compileId(mapping.id, transportIds),
|
|
122
|
+
idWhenMissing: isJsonObject(mapping.id) && "whenMissing" in mapping.id,
|
|
116
123
|
optional,
|
|
117
|
-
payload
|
|
118
|
-
headers: compileHeaders(mapping.headers),
|
|
119
|
-
|
|
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)
|
|
120
131
|
};
|
|
121
132
|
return assertDisjoint(plan);
|
|
122
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
|
+
}
|
|
123
163
|
function compileType(type) {
|
|
124
164
|
if (isJsonObject(type) && typeof type["fixed"] === "string") {
|
|
125
165
|
chunkJ76M7XTS_cjs.assertEventType(type["fixed"]);
|
|
@@ -134,11 +174,15 @@ function compileId(id, transportIds) {
|
|
|
134
174
|
if (typeof id === "string" && transportIds.includes(id)) {
|
|
135
175
|
return void 0;
|
|
136
176
|
}
|
|
177
|
+
const names = transportIds.map((name) => `'${name}'`).join(", ");
|
|
137
178
|
if (!isJsonObject(id)) {
|
|
138
179
|
throw new chunkJ76M7XTS_cjs.ConfigurationError(
|
|
139
|
-
`jsonEnvelope needs an id: { body }, { header } or one of ${
|
|
180
|
+
`jsonEnvelope needs an id: { body }, { header } or one of ${names}`
|
|
140
181
|
);
|
|
141
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
|
+
}
|
|
142
186
|
return toLocation(id, "id");
|
|
143
187
|
}
|
|
144
188
|
function compilePayload(payload) {
|
|
@@ -187,17 +231,25 @@ function assertDisjoint(plan) {
|
|
|
187
231
|
const locations = [plan.type, plan.id, ...Object.values(plan.optional)].filter(
|
|
188
232
|
(location) => location !== void 0 && !("fixed" in location)
|
|
189
233
|
);
|
|
190
|
-
const headers =
|
|
234
|
+
const headers = [
|
|
235
|
+
...locations.flatMap((location) => "header" in location ? [location.header] : []),
|
|
236
|
+
...plan.extraHeaders.map(([name]) => name)
|
|
237
|
+
];
|
|
191
238
|
const duplicate = headers.find((name, index) => headers.indexOf(name) !== index);
|
|
192
239
|
if (duplicate !== void 0) {
|
|
193
240
|
throw new chunkJ76M7XTS_cjs.ConfigurationError(`jsonEnvelope maps header '${duplicate}' twice`);
|
|
194
241
|
}
|
|
195
242
|
const paths = [
|
|
196
243
|
...locations.flatMap((location) => "path" in location ? [location.path] : []),
|
|
197
|
-
...[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)
|
|
198
246
|
];
|
|
199
247
|
assertNoNestedPaths(paths);
|
|
200
|
-
return {
|
|
248
|
+
return {
|
|
249
|
+
...plan,
|
|
250
|
+
mappedHeaders: new Set(headers),
|
|
251
|
+
topLevelNames: new Set(paths.map((path) => path[0]))
|
|
252
|
+
};
|
|
201
253
|
}
|
|
202
254
|
function assertNoNestedPaths(paths) {
|
|
203
255
|
paths.forEach((path, index) => {
|
|
@@ -219,6 +271,8 @@ function assertCompatiblePaths(path, other) {
|
|
|
219
271
|
|
|
220
272
|
// src/internal/json-envelope-read.ts
|
|
221
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;
|
|
222
276
|
function readMessage(message, context) {
|
|
223
277
|
const { plan, failures } = context;
|
|
224
278
|
if (plan.payload !== void 0 && !isJsonObject(message.body)) {
|
|
@@ -226,7 +280,7 @@ function readMessage(message, context) {
|
|
|
226
280
|
}
|
|
227
281
|
const read = (location) => location === void 0 ? void 0 : readText(message, { location, failures });
|
|
228
282
|
const fields = {
|
|
229
|
-
id:
|
|
283
|
+
id: readId(read(plan.id), context),
|
|
230
284
|
type: readType(message, context),
|
|
231
285
|
payload: readPayload(message.body, context),
|
|
232
286
|
key: read(plan.optional.key),
|
|
@@ -234,12 +288,18 @@ function readMessage(message, context) {
|
|
|
234
288
|
headers: readEventHeaders(message, plan),
|
|
235
289
|
correlationId: read(plan.optional.correlationId) ?? null,
|
|
236
290
|
causationId: read(plan.optional.causationId) ?? null,
|
|
237
|
-
occurredAt: readTime(read(plan.optional.occurredAt),
|
|
291
|
+
occurredAt: readTime(read(plan.optional.occurredAt), context)
|
|
238
292
|
};
|
|
239
293
|
return Object.fromEntries(
|
|
240
294
|
Object.entries(fields).filter((entry) => entry[1] !== void 0)
|
|
241
295
|
);
|
|
242
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
|
+
}
|
|
243
303
|
function readType(message, { plan, failures }) {
|
|
244
304
|
if ("fixed" in plan.type) {
|
|
245
305
|
return plan.type.fixed;
|
|
@@ -264,22 +324,39 @@ function readPayload(body, { plan, failures }) {
|
|
|
264
324
|
return payload;
|
|
265
325
|
}
|
|
266
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
|
+
}
|
|
267
334
|
if (plan.headers === void 0) {
|
|
268
335
|
return Object.fromEntries(
|
|
269
336
|
Object.entries(message.headers).filter(([name]) => !plan.mappedHeaders.has(name))
|
|
270
337
|
);
|
|
271
338
|
}
|
|
272
339
|
const headers = valueAt(message.body, plan.headers);
|
|
273
|
-
return isJsonObject(headers) ? Object.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
+
);
|
|
278
346
|
}
|
|
279
|
-
function readTime(text, failures) {
|
|
347
|
+
function readTime(text, { plan, failures }) {
|
|
280
348
|
if (text === void 0) {
|
|
281
349
|
return void 0;
|
|
282
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
|
+
}
|
|
283
360
|
const time = new Date(text);
|
|
284
361
|
if (!RFC3339_PATTERN.test(text) || Number.isNaN(time.getTime())) {
|
|
285
362
|
throw failures.invalid(`The event time is not an RFC 3339 timestamp: '${text}'`);
|
|
@@ -321,44 +398,68 @@ function valueAt(body, path) {
|
|
|
321
398
|
|
|
322
399
|
// src/internal/json-envelope-write.ts
|
|
323
400
|
var MAXIMUM_RFC3339_YEAR = 9999;
|
|
401
|
+
var MILLISECONDS_PER_SECOND2 = 1e3;
|
|
324
402
|
function writeMessage(envelope, { plan, failures }) {
|
|
325
403
|
const root = {};
|
|
326
404
|
const headers = [];
|
|
327
405
|
const put = (location, value) => {
|
|
328
406
|
if ("header" in location) {
|
|
329
|
-
headers.push([location.header, value]);
|
|
407
|
+
headers.push([location.header, String(value)]);
|
|
330
408
|
} else {
|
|
331
409
|
setAt(root, { path: location.path, value });
|
|
332
410
|
}
|
|
333
411
|
};
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
`Event ${envelope.id} has type '${envelope.type}', but this route carries only '${plan.type.fixed}'`
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
} else {
|
|
341
|
-
put(plan.type, envelope.type);
|
|
412
|
+
const typeAt = typeLocation(envelope, { plan, failures });
|
|
413
|
+
if (typeAt !== void 0) {
|
|
414
|
+
put(typeAt, envelope.type);
|
|
342
415
|
}
|
|
343
416
|
if (plan.id !== void 0) {
|
|
344
417
|
put(plan.id, envelope.id);
|
|
345
418
|
}
|
|
346
|
-
for (const [name, value] of optionalValues(envelope, failures)) {
|
|
419
|
+
for (const [name, value] of optionalValues(envelope, { plan, failures })) {
|
|
347
420
|
const location = plan.optional[name];
|
|
348
421
|
if (location !== void 0 && value !== null) {
|
|
349
422
|
put(location, value);
|
|
350
423
|
}
|
|
351
424
|
}
|
|
425
|
+
const body = writeBody(envelope, { plan, root });
|
|
426
|
+
if (plan.topLevelHeaders) {
|
|
427
|
+
spreadHeaders(envelope, { plan, failures, root: body });
|
|
428
|
+
}
|
|
352
429
|
return {
|
|
353
|
-
body
|
|
354
|
-
headers: Object.fromEntries([
|
|
430
|
+
body,
|
|
431
|
+
headers: Object.fromEntries([
|
|
432
|
+
...eventHeaderEntries(envelope, { plan, failures }),
|
|
433
|
+
...headers,
|
|
434
|
+
...extraHeaderEntries(envelope, plan)
|
|
435
|
+
])
|
|
355
436
|
};
|
|
356
437
|
}
|
|
357
|
-
function
|
|
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 }) {
|
|
358
456
|
return [
|
|
359
457
|
["key", envelope.key],
|
|
360
458
|
["source", envelope.source],
|
|
361
|
-
[
|
|
459
|
+
[
|
|
460
|
+
"occurredAt",
|
|
461
|
+
timeValue(formatTime(envelope, failures), { envelope, format: plan.timeFormat })
|
|
462
|
+
],
|
|
362
463
|
["correlationId", envelope.correlationId],
|
|
363
464
|
["causationId", envelope.causationId]
|
|
364
465
|
];
|
|
@@ -374,10 +475,43 @@ function writeBody(envelope, { plan, root }) {
|
|
|
374
475
|
value: Object.fromEntries(Object.entries(envelope.headers))
|
|
375
476
|
});
|
|
376
477
|
}
|
|
377
|
-
|
|
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;
|
|
378
512
|
}
|
|
379
513
|
function eventHeaderEntries(envelope, { plan, failures }) {
|
|
380
|
-
if (plan.headers !== void 0) {
|
|
514
|
+
if (plan.headers !== void 0 || plan.topLevelHeaders) {
|
|
381
515
|
return [];
|
|
382
516
|
}
|
|
383
517
|
const entries = Object.entries(envelope.headers);
|