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