@relaybus/rabbitmq 0.1.0 → 0.2.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 +123 -23
- package/dist/index.cjs +459 -189
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +150 -2
- package/dist/index.d.ts +150 -2
- package/dist/index.js +457 -191
- package/dist/index.js.map +1 -1
- package/package.json +22 -9
package/dist/index.cjs
CHANGED
|
@@ -180,9 +180,417 @@ var ConnectionSupervisor = class {
|
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
};
|
|
183
|
+
|
|
184
|
+
// src/json-payload.ts
|
|
185
|
+
function assertJsonPayload(payload, eventId) {
|
|
186
|
+
visit(payload, "$", /* @__PURE__ */ new Set(), eventId);
|
|
187
|
+
}
|
|
188
|
+
function visit(value, path, ancestors, eventId) {
|
|
189
|
+
switch (typeof value) {
|
|
190
|
+
case "string":
|
|
191
|
+
case "boolean":
|
|
192
|
+
return;
|
|
193
|
+
case "number":
|
|
194
|
+
if (!Number.isFinite(value)) {
|
|
195
|
+
throw refuse(eventId, path, `is ${value}, which JSON turns into null`);
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
case "object":
|
|
199
|
+
if (value !== null) {
|
|
200
|
+
visitObject(value, path, ancestors, eventId);
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
default:
|
|
204
|
+
throw refuse(eventId, path, `is ${typeof value}, which JSON cannot represent`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function visitObject(value, path, ancestors, eventId) {
|
|
208
|
+
if (ancestors.has(value)) {
|
|
209
|
+
throw refuse(eventId, path, "is a circular reference");
|
|
210
|
+
}
|
|
211
|
+
ancestors.add(value);
|
|
212
|
+
if (Array.isArray(value)) {
|
|
213
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
214
|
+
if (!(index in value)) {
|
|
215
|
+
throw refuse(eventId, `${path}[${index}]`, "is an array hole, which JSON turns into null");
|
|
216
|
+
}
|
|
217
|
+
visit(value[index], `${path}[${index}]`, ancestors, eventId);
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
const prototype = Object.getPrototypeOf(value);
|
|
221
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
222
|
+
throw refuse(eventId, path, "is not a plain object (class instance, Date, Map or Set)");
|
|
223
|
+
}
|
|
224
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
225
|
+
throw refuse(eventId, path, "has a symbol key, which JSON drops");
|
|
226
|
+
}
|
|
227
|
+
for (const key of Object.keys(value)) {
|
|
228
|
+
visit(value[key], `${path}.${key}`, ancestors, eventId);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
ancestors.delete(value);
|
|
232
|
+
}
|
|
233
|
+
function refuse(eventId, path, reason) {
|
|
234
|
+
return new UnserializablePayloadError(
|
|
235
|
+
`Event ${eventId} has a payload JSON cannot represent faithfully: ${path} ${reason}`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/wire-format.ts
|
|
240
|
+
var ATTRIBUTE_PREFIX = "cloudEvents:";
|
|
241
|
+
var EVENT_HEADER_PREFIX = "relaybus:";
|
|
242
|
+
var SPEC_VERSION = "1.0";
|
|
243
|
+
var JSON_CONTENT_TYPE = "application/json";
|
|
244
|
+
var SHORT_STRING_LIMIT_BYTES = 255;
|
|
245
|
+
var RFC3339_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/i;
|
|
246
|
+
function encodeEnvelope(envelope) {
|
|
247
|
+
assertJsonPayload(envelope.payload, envelope.id);
|
|
248
|
+
assertShortString("Event id", envelope.id);
|
|
249
|
+
const headers = {
|
|
250
|
+
[`${ATTRIBUTE_PREFIX}specversion`]: SPEC_VERSION,
|
|
251
|
+
[`${ATTRIBUTE_PREFIX}id`]: envelope.id,
|
|
252
|
+
[`${ATTRIBUTE_PREFIX}type`]: envelope.type,
|
|
253
|
+
[`${ATTRIBUTE_PREFIX}source`]: envelope.source,
|
|
254
|
+
[`${ATTRIBUTE_PREFIX}time`]: envelope.occurredAt.toISOString(),
|
|
255
|
+
[`${ATTRIBUTE_PREFIX}datacontenttype`]: JSON_CONTENT_TYPE
|
|
256
|
+
};
|
|
257
|
+
if (envelope.key !== null) {
|
|
258
|
+
headers[`${ATTRIBUTE_PREFIX}subject`] = envelope.key;
|
|
259
|
+
}
|
|
260
|
+
if (envelope.causationId !== null) {
|
|
261
|
+
headers[`${ATTRIBUTE_PREFIX}causationid`] = envelope.causationId;
|
|
262
|
+
}
|
|
263
|
+
if (envelope.correlationId !== null) {
|
|
264
|
+
headers[`${ATTRIBUTE_PREFIX}correlationid`] = envelope.correlationId;
|
|
265
|
+
}
|
|
266
|
+
for (const [name, value] of Object.entries(envelope.headers)) {
|
|
267
|
+
assertShortString(
|
|
268
|
+
`Header name '${EVENT_HEADER_PREFIX}${name}'`,
|
|
269
|
+
`${EVENT_HEADER_PREFIX}${name}`
|
|
270
|
+
);
|
|
271
|
+
headers[`${EVENT_HEADER_PREFIX}${name}`] = value;
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
content: Buffer.from(JSON.stringify(envelope.payload), "utf8"),
|
|
275
|
+
options: {
|
|
276
|
+
contentType: JSON_CONTENT_TYPE,
|
|
277
|
+
persistent: true,
|
|
278
|
+
messageId: envelope.id,
|
|
279
|
+
timestamp: Math.floor(envelope.occurredAt.getTime() / 1e3),
|
|
280
|
+
...mirror("type", envelope.type),
|
|
281
|
+
...mirror("appId", envelope.source),
|
|
282
|
+
...envelope.correlationId === null ? {} : mirror("correlationId", envelope.correlationId),
|
|
283
|
+
headers
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function assertShortString(label, value) {
|
|
288
|
+
const bytes = Buffer.byteLength(value, "utf8");
|
|
289
|
+
if (bytes > SHORT_STRING_LIMIT_BYTES) {
|
|
290
|
+
throw new AmqpLimitExceededError(
|
|
291
|
+
`${label} is ${bytes} bytes, above the AMQP limit of ${SHORT_STRING_LIMIT_BYTES} bytes`
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function mirror(name, value) {
|
|
296
|
+
return Buffer.byteLength(value, "utf8") > SHORT_STRING_LIMIT_BYTES ? {} : { [name]: value };
|
|
297
|
+
}
|
|
298
|
+
function decodeEnvelope(message) {
|
|
299
|
+
const { properties } = message;
|
|
300
|
+
const attributes = properties.headers ?? {};
|
|
301
|
+
if (attributes[`${ATTRIBUTE_PREFIX}specversion`] !== SPEC_VERSION) {
|
|
302
|
+
throw new MalformedMessageError(
|
|
303
|
+
`Message is not a CloudEvents ${SPEC_VERSION} event (header ${ATTRIBUTE_PREFIX}specversion)`
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
id: required("id", text(attributes[`${ATTRIBUTE_PREFIX}id`]) ?? text(properties.messageId)),
|
|
308
|
+
type: required("type", text(attributes[`${ATTRIBUTE_PREFIX}type`]) ?? text(properties.type)),
|
|
309
|
+
source: required(
|
|
310
|
+
"source",
|
|
311
|
+
text(attributes[`${ATTRIBUTE_PREFIX}source`]) ?? text(properties.appId)
|
|
312
|
+
),
|
|
313
|
+
key: text(attributes[`${ATTRIBUTE_PREFIX}subject`]) ?? null,
|
|
314
|
+
payload: parsePayload(message.content),
|
|
315
|
+
headers: extractEventHeaders(attributes),
|
|
316
|
+
correlationId: text(attributes[`${ATTRIBUTE_PREFIX}correlationid`]) ?? text(properties.correlationId) ?? null,
|
|
317
|
+
causationId: text(attributes[`${ATTRIBUTE_PREFIX}causationid`]) ?? null,
|
|
318
|
+
occurredAt: parseTime(attributes[`${ATTRIBUTE_PREFIX}time`], properties.timestamp)
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function extractEventHeaders(attributes) {
|
|
322
|
+
const headers = {};
|
|
323
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
324
|
+
if (name.startsWith(EVENT_HEADER_PREFIX) && typeof value === "string") {
|
|
325
|
+
Object.defineProperty(headers, name.slice(EVENT_HEADER_PREFIX.length), {
|
|
326
|
+
value,
|
|
327
|
+
enumerable: true,
|
|
328
|
+
writable: true,
|
|
329
|
+
configurable: true
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return headers;
|
|
334
|
+
}
|
|
335
|
+
function parsePayload(content) {
|
|
336
|
+
try {
|
|
337
|
+
return JSON.parse(content.toString("utf8"));
|
|
338
|
+
} catch {
|
|
339
|
+
throw new MalformedMessageError("Message body is not valid JSON");
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function parseTime(rawTime, timestampSeconds) {
|
|
343
|
+
if (rawTime === void 0) {
|
|
344
|
+
const fallback = typeof timestampSeconds === "number" ? new Date(timestampSeconds * 1e3) : void 0;
|
|
345
|
+
if (fallback === void 0 || Number.isNaN(fallback.getTime())) {
|
|
346
|
+
throw new MalformedMessageError("Message has no valid time (header cloudEvents:time)");
|
|
347
|
+
}
|
|
348
|
+
return fallback;
|
|
349
|
+
}
|
|
350
|
+
const occurredAt = typeof rawTime === "string" && RFC3339_PATTERN.test(rawTime) ? new Date(rawTime) : void 0;
|
|
351
|
+
if (occurredAt === void 0 || Number.isNaN(occurredAt.getTime())) {
|
|
352
|
+
throw new MalformedMessageError(
|
|
353
|
+
"Message time (header cloudEvents:time) is not an RFC 3339 timestamp"
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
return occurredAt;
|
|
357
|
+
}
|
|
358
|
+
function text(value) {
|
|
359
|
+
return typeof value === "string" ? value : void 0;
|
|
360
|
+
}
|
|
361
|
+
function required(attribute, value) {
|
|
362
|
+
if (value === void 0 || value.length === 0) {
|
|
363
|
+
throw new MalformedMessageError(
|
|
364
|
+
`Message has no ${attribute} (header ${ATTRIBUTE_PREFIX}${attribute})`
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
return value;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// src/inbound-decoders.ts
|
|
371
|
+
var STRUCTURED_MEDIA_TYPE = "application/cloudevents+json";
|
|
372
|
+
var strictUtf8 = new TextDecoder("utf-8", { fatal: true });
|
|
373
|
+
var SPEC_VERSION2 = "1.0";
|
|
374
|
+
var SPEC_VERSION_HEADER = "cloudEvents:specversion";
|
|
375
|
+
var INTERNAL_HEADER_PREFIXES = ["x-relaybus-", "x-first-death-", "x-last-death-"];
|
|
376
|
+
var STRUCTURED_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
377
|
+
"specversion",
|
|
378
|
+
"id",
|
|
379
|
+
"type",
|
|
380
|
+
"source",
|
|
381
|
+
"time",
|
|
382
|
+
"subject",
|
|
383
|
+
"datacontenttype",
|
|
384
|
+
"dataschema",
|
|
385
|
+
"data",
|
|
386
|
+
"data_base64",
|
|
387
|
+
"correlationid",
|
|
388
|
+
"causationid"
|
|
389
|
+
]);
|
|
390
|
+
function decodeCloudEvent(message) {
|
|
391
|
+
if (mediaTypeOf(message.properties.contentType) !== STRUCTURED_MEDIA_TYPE) {
|
|
392
|
+
return decodeEnvelope(message);
|
|
393
|
+
}
|
|
394
|
+
const body = parseJsonBody(message.content);
|
|
395
|
+
if (!isJsonObject(body)) {
|
|
396
|
+
throw new MalformedMessageError("A structured CloudEvent must be a JSON object");
|
|
397
|
+
}
|
|
398
|
+
if (body["specversion"] !== SPEC_VERSION2) {
|
|
399
|
+
throw new MalformedMessageError(`specversion must be '${SPEC_VERSION2}'`);
|
|
400
|
+
}
|
|
401
|
+
const type = requiredAttribute(body, "type");
|
|
402
|
+
assertValidType(type, "type");
|
|
403
|
+
assertJsonDataContentType(optionalAttribute(body, "datacontenttype"));
|
|
404
|
+
if (Object.hasOwn(body, "data_base64")) {
|
|
405
|
+
throw new MalformedMessageError(
|
|
406
|
+
"data_base64 is not supported: relaybus payloads are JSON, send them as data"
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
const time = optionalAttribute(body, "time");
|
|
410
|
+
return {
|
|
411
|
+
id: requiredAttribute(body, "id"),
|
|
412
|
+
type,
|
|
413
|
+
source: requiredAttribute(body, "source"),
|
|
414
|
+
key: optionalAttribute(body, "subject") ?? null,
|
|
415
|
+
payload: Object.hasOwn(body, "data") ? body["data"] : null,
|
|
416
|
+
headers: { ...readForeignHeaders(message.properties), ...extensionAttributes(body) },
|
|
417
|
+
correlationId: optionalAttribute(body, "correlationid") ?? null,
|
|
418
|
+
causationId: optionalAttribute(body, "causationid") ?? null,
|
|
419
|
+
occurredAt: time === void 0 ? propertyTimeOrNow(message.properties) : parseTime2(time)
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
function plainJsonDecoder(options) {
|
|
423
|
+
if (typeof options.type === "string") {
|
|
424
|
+
core.assertEventType(options.type);
|
|
425
|
+
}
|
|
426
|
+
return (message) => {
|
|
427
|
+
const { properties } = message;
|
|
428
|
+
if (properties.headers?.[SPEC_VERSION_HEADER] !== void 0 || mediaTypeOf(properties.contentType) === STRUCTURED_MEDIA_TYPE) {
|
|
429
|
+
return decodeCloudEvent(message);
|
|
430
|
+
}
|
|
431
|
+
const plain = {
|
|
432
|
+
routingKey: message.fields.routingKey,
|
|
433
|
+
properties,
|
|
434
|
+
headers: readForeignHeaders(properties),
|
|
435
|
+
payload: parseJsonBody(message.content)
|
|
436
|
+
};
|
|
437
|
+
const type = resolveText(options.type, plain, "type");
|
|
438
|
+
if (type === void 0) {
|
|
439
|
+
throw new MalformedMessageError("Message has no event type: the type option found none");
|
|
440
|
+
}
|
|
441
|
+
assertValidType(type, "The event type");
|
|
442
|
+
const id = resolveText(options.id, plain, "id") ?? nonEmptyText(properties.messageId);
|
|
443
|
+
if (id === void 0) {
|
|
444
|
+
throw new MalformedMessageError(
|
|
445
|
+
"Message has no id: set the messageId property on the producer or configure the id option"
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
return {
|
|
449
|
+
id,
|
|
450
|
+
type,
|
|
451
|
+
source: resolveText(options.source, plain, "source") ?? defaultSource(message),
|
|
452
|
+
key: resolveText(options.key, plain, "key") ?? null,
|
|
453
|
+
payload: plain.payload,
|
|
454
|
+
headers: plain.headers,
|
|
455
|
+
correlationId: null,
|
|
456
|
+
causationId: null,
|
|
457
|
+
occurredAt: resolveOccurredAt(options.occurredAt, plain)
|
|
458
|
+
};
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
function completeDecodedEvent(decoded, message) {
|
|
462
|
+
if (typeof decoded !== "object" || decoded === null) {
|
|
463
|
+
throw new MalformedMessageError("The decoder must return an event object synchronously");
|
|
464
|
+
}
|
|
465
|
+
if ("then" in decoded) {
|
|
466
|
+
observeRejection(decoded);
|
|
467
|
+
throw new MalformedMessageError("The decoder must return an event object synchronously");
|
|
468
|
+
}
|
|
469
|
+
const event = decoded;
|
|
470
|
+
return {
|
|
471
|
+
id: event.id,
|
|
472
|
+
type: event.type,
|
|
473
|
+
source: event.source ?? defaultSource(message),
|
|
474
|
+
key: event.key ?? null,
|
|
475
|
+
payload: event.payload,
|
|
476
|
+
headers: event.headers ?? {},
|
|
477
|
+
correlationId: event.correlationId ?? null,
|
|
478
|
+
causationId: event.causationId ?? null,
|
|
479
|
+
occurredAt: event.occurredAt ?? propertyTimeOrNow(message.properties)
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function parseJsonBody(content) {
|
|
483
|
+
let text2;
|
|
484
|
+
try {
|
|
485
|
+
text2 = strictUtf8.decode(content);
|
|
486
|
+
} catch {
|
|
487
|
+
throw new MalformedMessageError("Message body is not valid UTF-8");
|
|
488
|
+
}
|
|
489
|
+
try {
|
|
490
|
+
return JSON.parse(text2);
|
|
491
|
+
} catch {
|
|
492
|
+
throw new MalformedMessageError("Message body is not valid JSON");
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function mediaTypeOf(contentType) {
|
|
496
|
+
return typeof contentType === "string" ? contentType.split(";")[0].trim().toLowerCase() : void 0;
|
|
497
|
+
}
|
|
498
|
+
function isJsonObject(value) {
|
|
499
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
500
|
+
}
|
|
501
|
+
function nonEmptyText(value) {
|
|
502
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
503
|
+
}
|
|
504
|
+
function defaultSource(message) {
|
|
505
|
+
return nonEmptyText(message.properties.appId) ?? message.fields.routingKey;
|
|
506
|
+
}
|
|
507
|
+
function optionalAttribute(body, name) {
|
|
508
|
+
const value = body[name];
|
|
509
|
+
if (value !== void 0 && typeof value !== "string") {
|
|
510
|
+
throw new MalformedMessageError(`Attribute '${name}' must be a string`);
|
|
511
|
+
}
|
|
512
|
+
return value;
|
|
513
|
+
}
|
|
514
|
+
function requiredAttribute(body, name) {
|
|
515
|
+
const value = optionalAttribute(body, name);
|
|
516
|
+
if (value === void 0 || value.length === 0) {
|
|
517
|
+
throw new MalformedMessageError(`Attribute '${name}' is missing or empty`);
|
|
518
|
+
}
|
|
519
|
+
return value;
|
|
520
|
+
}
|
|
521
|
+
function assertValidType(type, label) {
|
|
522
|
+
try {
|
|
523
|
+
core.assertEventType(type);
|
|
524
|
+
} catch (error) {
|
|
525
|
+
throw new MalformedMessageError(
|
|
526
|
+
`${label} is not a valid event type: ${error.message}`
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function assertJsonDataContentType(dataContentType) {
|
|
531
|
+
const mediaType = mediaTypeOf(dataContentType);
|
|
532
|
+
if (mediaType !== void 0 && mediaType !== "application/json" && mediaType !== "text/json" && !mediaType.endsWith("+json")) {
|
|
533
|
+
throw new MalformedMessageError(`datacontenttype must be JSON, received '${dataContentType}'`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function parseTime2(text2) {
|
|
537
|
+
const time = new Date(text2);
|
|
538
|
+
if (!RFC3339_PATTERN.test(text2) || Number.isNaN(time.getTime())) {
|
|
539
|
+
throw new MalformedMessageError(`time is not an RFC 3339 timestamp: '${text2}'`);
|
|
540
|
+
}
|
|
541
|
+
return time;
|
|
542
|
+
}
|
|
543
|
+
function propertyTimeOrNow(properties) {
|
|
544
|
+
const { timestamp } = properties;
|
|
545
|
+
return typeof timestamp === "number" && Number.isFinite(timestamp) ? new Date(timestamp * 1e3) : /* @__PURE__ */ new Date();
|
|
546
|
+
}
|
|
547
|
+
function extensionAttributes(body) {
|
|
548
|
+
return Object.fromEntries(
|
|
549
|
+
Object.entries(body).filter(
|
|
550
|
+
(entry) => !STRUCTURED_ATTRIBUTES.has(entry[0]) && typeof entry[1] === "string"
|
|
551
|
+
)
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
function readForeignHeaders(properties) {
|
|
555
|
+
return Object.fromEntries(
|
|
556
|
+
Object.entries(properties.headers ?? {}).filter(
|
|
557
|
+
(entry) => typeof entry[1] === "string" && !INTERNAL_HEADER_PREFIXES.some((prefix) => entry[0].startsWith(prefix))
|
|
558
|
+
)
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
function resolveText(option, message, name) {
|
|
562
|
+
const value = typeof option === "function" ? option(message) : option;
|
|
563
|
+
if (value === void 0 || value === null) {
|
|
564
|
+
return void 0;
|
|
565
|
+
}
|
|
566
|
+
if (typeof value !== "string") {
|
|
567
|
+
observeRejection(value);
|
|
568
|
+
throw new MalformedMessageError(
|
|
569
|
+
`The ${name} option returned ${typeof value}, expected a string`
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
return value.length === 0 ? void 0 : value;
|
|
573
|
+
}
|
|
574
|
+
function resolveOccurredAt(option, message) {
|
|
575
|
+
const value = option?.(message);
|
|
576
|
+
if (value === void 0) {
|
|
577
|
+
return propertyTimeOrNow(message.properties);
|
|
578
|
+
}
|
|
579
|
+
if (!(value instanceof Date) || Number.isNaN(value.getTime())) {
|
|
580
|
+
observeRejection(value);
|
|
581
|
+
throw new MalformedMessageError("The occurredAt option returned no valid Date");
|
|
582
|
+
}
|
|
583
|
+
return value;
|
|
584
|
+
}
|
|
585
|
+
function observeRejection(value) {
|
|
586
|
+
if (typeof value === "object" && value !== null && "then" in value) {
|
|
587
|
+
Promise.resolve(value).catch(() => void 0);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
183
590
|
var REDELIVERIES_HEADER = "x-relaybus-redeliveries";
|
|
184
591
|
var FIRST_FAILURE_HEADER = "x-relaybus-first-failure-at";
|
|
185
592
|
var DEAD_LETTER_REASON_HEADER = "x-relaybus-dead-letter-reason";
|
|
593
|
+
var ORIGINAL_ROUTING_KEY_HEADER = "x-relaybus-routing-key";
|
|
186
594
|
var RELAYBUS_STATE_HEADERS = [REDELIVERIES_HEADER, FIRST_FAILURE_HEADER];
|
|
187
595
|
var BROKER_COUNTER_HEADERS = ["x-delivery-count", "x-acquired-count"];
|
|
188
596
|
var MAXIMUM_LADDER_TIERS = 16;
|
|
@@ -219,7 +627,7 @@ function chooseRetryTier(ladder, delayMs, random) {
|
|
|
219
627
|
return random() < (delayMs - lower) / (upper - lower) ? upper : lower;
|
|
220
628
|
}
|
|
221
629
|
function readCounterHeader(message, name) {
|
|
222
|
-
const value = message.properties.headers[name];
|
|
630
|
+
const value = message.properties.headers?.[name];
|
|
223
631
|
const parsed = typeof value === "string" && /^\d+$/.test(value) ? Number(value) : value;
|
|
224
632
|
return typeof parsed === "number" && Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : void 0;
|
|
225
633
|
}
|
|
@@ -248,6 +656,14 @@ function copyForRepublish(message, headerChanges) {
|
|
|
248
656
|
}
|
|
249
657
|
};
|
|
250
658
|
}
|
|
659
|
+
function originalRoutingKey(message) {
|
|
660
|
+
const original = message.properties.headers?.[ORIGINAL_ROUTING_KEY_HEADER];
|
|
661
|
+
return typeof original === "string" ? original : message.fields.routingKey;
|
|
662
|
+
}
|
|
663
|
+
function withOriginalRoutingKey(message) {
|
|
664
|
+
const routingKey = originalRoutingKey(message);
|
|
665
|
+
return routingKey === message.fields.routingKey ? message : { ...message, fields: { ...message.fields, routingKey } };
|
|
666
|
+
}
|
|
251
667
|
|
|
252
668
|
// src/options.ts
|
|
253
669
|
var DEFAULT_HANDLER_RETRY = {
|
|
@@ -316,6 +732,7 @@ function resolveSettings(options) {
|
|
|
316
732
|
allowUnroutable: options.allowUnroutable ?? false,
|
|
317
733
|
publishTimeoutMs: positiveDuration(options.publishTimeout ?? "30s", "publishTimeout"),
|
|
318
734
|
shutdownTimeoutMs: core.toMilliseconds(options.shutdownTimeout ?? "30s", "shutdownTimeout"),
|
|
735
|
+
decode: resolveDecode(options.decode),
|
|
319
736
|
logger: options.logger ?? console,
|
|
320
737
|
random: options.random ?? Math.random
|
|
321
738
|
};
|
|
@@ -331,6 +748,12 @@ function resolveQueueName(settings) {
|
|
|
331
748
|
}
|
|
332
749
|
return `${settings.queuePrefix}.${settings.consumerGroup}`;
|
|
333
750
|
}
|
|
751
|
+
function resolveDecode(decode) {
|
|
752
|
+
if (decode === void 0) {
|
|
753
|
+
return decodeEnvelope;
|
|
754
|
+
}
|
|
755
|
+
return (message) => completeDecodedEvent(decode(message), message);
|
|
756
|
+
}
|
|
334
757
|
function resolveConnect(options) {
|
|
335
758
|
const { url, connect } = options;
|
|
336
759
|
if (url === void 0 === (connect === void 0)) {
|
|
@@ -418,192 +841,6 @@ async function assertConsumerTopology(channel, topology) {
|
|
|
418
841
|
}
|
|
419
842
|
}
|
|
420
843
|
|
|
421
|
-
// src/json-payload.ts
|
|
422
|
-
function assertJsonPayload(payload, eventId) {
|
|
423
|
-
visit(payload, "$", /* @__PURE__ */ new Set(), eventId);
|
|
424
|
-
}
|
|
425
|
-
function visit(value, path, ancestors, eventId) {
|
|
426
|
-
switch (typeof value) {
|
|
427
|
-
case "string":
|
|
428
|
-
case "boolean":
|
|
429
|
-
return;
|
|
430
|
-
case "number":
|
|
431
|
-
if (!Number.isFinite(value)) {
|
|
432
|
-
throw refuse(eventId, path, `is ${value}, which JSON turns into null`);
|
|
433
|
-
}
|
|
434
|
-
return;
|
|
435
|
-
case "object":
|
|
436
|
-
if (value !== null) {
|
|
437
|
-
visitObject(value, path, ancestors, eventId);
|
|
438
|
-
}
|
|
439
|
-
return;
|
|
440
|
-
default:
|
|
441
|
-
throw refuse(eventId, path, `is ${typeof value}, which JSON cannot represent`);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
function visitObject(value, path, ancestors, eventId) {
|
|
445
|
-
if (ancestors.has(value)) {
|
|
446
|
-
throw refuse(eventId, path, "is a circular reference");
|
|
447
|
-
}
|
|
448
|
-
ancestors.add(value);
|
|
449
|
-
if (Array.isArray(value)) {
|
|
450
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
451
|
-
if (!(index in value)) {
|
|
452
|
-
throw refuse(eventId, `${path}[${index}]`, "is an array hole, which JSON turns into null");
|
|
453
|
-
}
|
|
454
|
-
visit(value[index], `${path}[${index}]`, ancestors, eventId);
|
|
455
|
-
}
|
|
456
|
-
} else {
|
|
457
|
-
const prototype = Object.getPrototypeOf(value);
|
|
458
|
-
if (prototype !== Object.prototype && prototype !== null) {
|
|
459
|
-
throw refuse(eventId, path, "is not a plain object (class instance, Date, Map or Set)");
|
|
460
|
-
}
|
|
461
|
-
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
462
|
-
throw refuse(eventId, path, "has a symbol key, which JSON drops");
|
|
463
|
-
}
|
|
464
|
-
for (const key of Object.keys(value)) {
|
|
465
|
-
visit(value[key], `${path}.${key}`, ancestors, eventId);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
ancestors.delete(value);
|
|
469
|
-
}
|
|
470
|
-
function refuse(eventId, path, reason) {
|
|
471
|
-
return new UnserializablePayloadError(
|
|
472
|
-
`Event ${eventId} has a payload JSON cannot represent faithfully: ${path} ${reason}`
|
|
473
|
-
);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// src/wire-format.ts
|
|
477
|
-
var ATTRIBUTE_PREFIX = "cloudEvents:";
|
|
478
|
-
var EVENT_HEADER_PREFIX = "relaybus:";
|
|
479
|
-
var SPEC_VERSION = "1.0";
|
|
480
|
-
var JSON_CONTENT_TYPE = "application/json";
|
|
481
|
-
var SHORT_STRING_LIMIT_BYTES = 255;
|
|
482
|
-
var RFC3339_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/i;
|
|
483
|
-
function encodeEnvelope(envelope) {
|
|
484
|
-
assertJsonPayload(envelope.payload, envelope.id);
|
|
485
|
-
assertShortString("Event id", envelope.id);
|
|
486
|
-
const headers = {
|
|
487
|
-
[`${ATTRIBUTE_PREFIX}specversion`]: SPEC_VERSION,
|
|
488
|
-
[`${ATTRIBUTE_PREFIX}id`]: envelope.id,
|
|
489
|
-
[`${ATTRIBUTE_PREFIX}type`]: envelope.type,
|
|
490
|
-
[`${ATTRIBUTE_PREFIX}source`]: envelope.source,
|
|
491
|
-
[`${ATTRIBUTE_PREFIX}time`]: envelope.occurredAt.toISOString(),
|
|
492
|
-
[`${ATTRIBUTE_PREFIX}datacontenttype`]: JSON_CONTENT_TYPE
|
|
493
|
-
};
|
|
494
|
-
if (envelope.key !== null) {
|
|
495
|
-
headers[`${ATTRIBUTE_PREFIX}subject`] = envelope.key;
|
|
496
|
-
}
|
|
497
|
-
if (envelope.causationId !== null) {
|
|
498
|
-
headers[`${ATTRIBUTE_PREFIX}causationid`] = envelope.causationId;
|
|
499
|
-
}
|
|
500
|
-
if (envelope.correlationId !== null) {
|
|
501
|
-
headers[`${ATTRIBUTE_PREFIX}correlationid`] = envelope.correlationId;
|
|
502
|
-
}
|
|
503
|
-
for (const [name, value] of Object.entries(envelope.headers)) {
|
|
504
|
-
assertShortString(
|
|
505
|
-
`Header name '${EVENT_HEADER_PREFIX}${name}'`,
|
|
506
|
-
`${EVENT_HEADER_PREFIX}${name}`
|
|
507
|
-
);
|
|
508
|
-
headers[`${EVENT_HEADER_PREFIX}${name}`] = value;
|
|
509
|
-
}
|
|
510
|
-
return {
|
|
511
|
-
content: Buffer.from(JSON.stringify(envelope.payload), "utf8"),
|
|
512
|
-
options: {
|
|
513
|
-
contentType: JSON_CONTENT_TYPE,
|
|
514
|
-
persistent: true,
|
|
515
|
-
messageId: envelope.id,
|
|
516
|
-
timestamp: Math.floor(envelope.occurredAt.getTime() / 1e3),
|
|
517
|
-
...mirror("type", envelope.type),
|
|
518
|
-
...mirror("appId", envelope.source),
|
|
519
|
-
...envelope.correlationId === null ? {} : mirror("correlationId", envelope.correlationId),
|
|
520
|
-
headers
|
|
521
|
-
}
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
function assertShortString(label, value) {
|
|
525
|
-
const bytes = Buffer.byteLength(value, "utf8");
|
|
526
|
-
if (bytes > SHORT_STRING_LIMIT_BYTES) {
|
|
527
|
-
throw new AmqpLimitExceededError(
|
|
528
|
-
`${label} is ${bytes} bytes, above the AMQP limit of ${SHORT_STRING_LIMIT_BYTES} bytes`
|
|
529
|
-
);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
function mirror(name, value) {
|
|
533
|
-
return Buffer.byteLength(value, "utf8") > SHORT_STRING_LIMIT_BYTES ? {} : { [name]: value };
|
|
534
|
-
}
|
|
535
|
-
function decodeEnvelope(message) {
|
|
536
|
-
const { properties } = message;
|
|
537
|
-
const attributes = properties.headers ?? {};
|
|
538
|
-
if (attributes[`${ATTRIBUTE_PREFIX}specversion`] !== SPEC_VERSION) {
|
|
539
|
-
throw new MalformedMessageError(
|
|
540
|
-
`Message is not a CloudEvents ${SPEC_VERSION} event (header ${ATTRIBUTE_PREFIX}specversion)`
|
|
541
|
-
);
|
|
542
|
-
}
|
|
543
|
-
return {
|
|
544
|
-
id: required("id", text(attributes[`${ATTRIBUTE_PREFIX}id`]) ?? text(properties.messageId)),
|
|
545
|
-
type: required("type", text(attributes[`${ATTRIBUTE_PREFIX}type`]) ?? text(properties.type)),
|
|
546
|
-
source: required(
|
|
547
|
-
"source",
|
|
548
|
-
text(attributes[`${ATTRIBUTE_PREFIX}source`]) ?? text(properties.appId)
|
|
549
|
-
),
|
|
550
|
-
key: text(attributes[`${ATTRIBUTE_PREFIX}subject`]) ?? null,
|
|
551
|
-
payload: parsePayload(message.content),
|
|
552
|
-
headers: extractEventHeaders(attributes),
|
|
553
|
-
correlationId: text(attributes[`${ATTRIBUTE_PREFIX}correlationid`]) ?? text(properties.correlationId) ?? null,
|
|
554
|
-
causationId: text(attributes[`${ATTRIBUTE_PREFIX}causationid`]) ?? null,
|
|
555
|
-
occurredAt: parseTime(attributes[`${ATTRIBUTE_PREFIX}time`], properties.timestamp)
|
|
556
|
-
};
|
|
557
|
-
}
|
|
558
|
-
function extractEventHeaders(attributes) {
|
|
559
|
-
const headers = {};
|
|
560
|
-
for (const [name, value] of Object.entries(attributes)) {
|
|
561
|
-
if (name.startsWith(EVENT_HEADER_PREFIX) && typeof value === "string") {
|
|
562
|
-
Object.defineProperty(headers, name.slice(EVENT_HEADER_PREFIX.length), {
|
|
563
|
-
value,
|
|
564
|
-
enumerable: true,
|
|
565
|
-
writable: true,
|
|
566
|
-
configurable: true
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
return headers;
|
|
571
|
-
}
|
|
572
|
-
function parsePayload(content) {
|
|
573
|
-
try {
|
|
574
|
-
return JSON.parse(content.toString("utf8"));
|
|
575
|
-
} catch {
|
|
576
|
-
throw new MalformedMessageError("Message body is not valid JSON");
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
function parseTime(rawTime, timestampSeconds) {
|
|
580
|
-
if (rawTime === void 0) {
|
|
581
|
-
const fallback = typeof timestampSeconds === "number" ? new Date(timestampSeconds * 1e3) : void 0;
|
|
582
|
-
if (fallback === void 0 || Number.isNaN(fallback.getTime())) {
|
|
583
|
-
throw new MalformedMessageError("Message has no valid time (header cloudEvents:time)");
|
|
584
|
-
}
|
|
585
|
-
return fallback;
|
|
586
|
-
}
|
|
587
|
-
const occurredAt = typeof rawTime === "string" && RFC3339_PATTERN.test(rawTime) ? new Date(rawTime) : void 0;
|
|
588
|
-
if (occurredAt === void 0 || Number.isNaN(occurredAt.getTime())) {
|
|
589
|
-
throw new MalformedMessageError(
|
|
590
|
-
"Message time (header cloudEvents:time) is not an RFC 3339 timestamp"
|
|
591
|
-
);
|
|
592
|
-
}
|
|
593
|
-
return occurredAt;
|
|
594
|
-
}
|
|
595
|
-
function text(value) {
|
|
596
|
-
return typeof value === "string" ? value : void 0;
|
|
597
|
-
}
|
|
598
|
-
function required(attribute, value) {
|
|
599
|
-
if (value === void 0 || value.length === 0) {
|
|
600
|
-
throw new MalformedMessageError(
|
|
601
|
-
`Message has no ${attribute} (header ${ATTRIBUTE_PREFIX}${attribute})`
|
|
602
|
-
);
|
|
603
|
-
}
|
|
604
|
-
return value;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
844
|
// src/queue-consumer.ts
|
|
608
845
|
var QueueConsumer = class {
|
|
609
846
|
#settings;
|
|
@@ -702,7 +939,7 @@ var QueueConsumer = class {
|
|
|
702
939
|
const { queue, handlerRetry } = this.#settings;
|
|
703
940
|
let envelope;
|
|
704
941
|
try {
|
|
705
|
-
envelope =
|
|
942
|
+
envelope = this.#settings.decode(withOriginalRoutingKey(message));
|
|
706
943
|
} catch (error) {
|
|
707
944
|
this.#settings.logger.error(
|
|
708
945
|
`[QueueConsumer](process) Dead-lettering malformed message | queue=${queue} routingKey=${message.fields.routingKey} error=${core.toErrorInfo(error).message}`
|
|
@@ -782,7 +1019,8 @@ var QueueConsumer = class {
|
|
|
782
1019
|
async #redeliverLater(channel, message, delayMs, redeliveries, firstFailureAt) {
|
|
783
1020
|
const { content, options } = copyForRepublish(message, {
|
|
784
1021
|
[REDELIVERIES_HEADER]: String(redeliveries),
|
|
785
|
-
[FIRST_FAILURE_HEADER]: String(firstFailureAt)
|
|
1022
|
+
[FIRST_FAILURE_HEADER]: String(firstFailureAt),
|
|
1023
|
+
[ORIGINAL_ROUTING_KEY_HEADER]: originalRoutingKey(message)
|
|
786
1024
|
});
|
|
787
1025
|
try {
|
|
788
1026
|
await this.#settings.republish(
|
|
@@ -998,6 +1236,7 @@ var RabbitMqTransport = class {
|
|
|
998
1236
|
republish: (exchange, routingKey, content, options) => this.#publishConfirmed(exchange, routingKey, content, options, true),
|
|
999
1237
|
shutdownTimeoutMs: this.#settings.shutdownTimeoutMs,
|
|
1000
1238
|
onMessage,
|
|
1239
|
+
decode: this.#settings.decode,
|
|
1001
1240
|
onConsumerLost: (reason) => this.#supervisor.recover(reason),
|
|
1002
1241
|
logger: this.#settings.logger,
|
|
1003
1242
|
random: this.#settings.random
|
|
@@ -1101,6 +1340,33 @@ function takeUnroutable(unroutable, key) {
|
|
|
1101
1340
|
}
|
|
1102
1341
|
return true;
|
|
1103
1342
|
}
|
|
1343
|
+
function fromHeader(name) {
|
|
1344
|
+
if (name.length === 0) {
|
|
1345
|
+
throw new core.ConfigurationError("fromHeader needs a header name");
|
|
1346
|
+
}
|
|
1347
|
+
return (message) => Object.hasOwn(message.headers, name) ? message.headers[name] : void 0;
|
|
1348
|
+
}
|
|
1349
|
+
function fromPayloadField(path) {
|
|
1350
|
+
const segments = path.split(".");
|
|
1351
|
+
if (segments.some((segment) => segment.length === 0)) {
|
|
1352
|
+
throw new core.ConfigurationError(
|
|
1353
|
+
`fromPayloadField needs a dotted path of non-empty names, received '${path}'`
|
|
1354
|
+
);
|
|
1355
|
+
}
|
|
1356
|
+
return (message) => {
|
|
1357
|
+
let value = message.payload;
|
|
1358
|
+
for (const segment of segments) {
|
|
1359
|
+
if (typeof value !== "object" || value === null || !Object.hasOwn(value, segment)) {
|
|
1360
|
+
return void 0;
|
|
1361
|
+
}
|
|
1362
|
+
value = value[segment];
|
|
1363
|
+
}
|
|
1364
|
+
if (typeof value === "string") {
|
|
1365
|
+
return value;
|
|
1366
|
+
}
|
|
1367
|
+
return Number.isSafeInteger(value) ? String(value) : void 0;
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1104
1370
|
|
|
1105
1371
|
exports.AmqpLimitExceededError = AmqpLimitExceededError;
|
|
1106
1372
|
exports.MalformedMessageError = MalformedMessageError;
|
|
@@ -1109,5 +1375,9 @@ exports.RabbitMqTransport = RabbitMqTransport;
|
|
|
1109
1375
|
exports.RabbitMqUnavailableError = RabbitMqUnavailableError;
|
|
1110
1376
|
exports.UnroutableMessageError = UnroutableMessageError;
|
|
1111
1377
|
exports.UnserializablePayloadError = UnserializablePayloadError;
|
|
1378
|
+
exports.decodeCloudEvent = decodeCloudEvent;
|
|
1379
|
+
exports.fromHeader = fromHeader;
|
|
1380
|
+
exports.fromPayloadField = fromPayloadField;
|
|
1381
|
+
exports.plainJsonDecoder = plainJsonDecoder;
|
|
1112
1382
|
//# sourceMappingURL=index.cjs.map
|
|
1113
1383
|
//# sourceMappingURL=index.cjs.map
|