@relaybus/rabbitmq 0.0.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/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 = decodeEnvelope(message);
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