@zdavison/matador 4.0.3 → 4.1.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.
@@ -1 +1 @@
1
- {"version":3,"file":"matador.d.ts","sourceRoot":"","sources":["../../src/core/matador.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"matador.d.ts","sourceRoot":"","sources":["../../src/core/matador.ts"],"names":[],"mappings":"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"}
@@ -141,6 +141,10 @@ export class Matador {
141
141
  else {
142
142
  this.hooks.logger.info('[Matador] 🟡 Worker not subscribing to any queues (consumeFrom is empty).');
143
143
  }
144
+ // Mark as started
145
+ // Needed for the subscribe calls below, as we might start getting messages
146
+ // that could send events before the code here finishes subscribing to all the queues
147
+ this.started = true;
144
148
  for (const queueName of this.consumeFrom) {
145
149
  const qualifiedName = resolveTargetQueueName(this.topology, queueName);
146
150
  const queueDef = findQueueDefinition(this.topology, queueName);
@@ -158,7 +162,6 @@ export class Matador {
158
162
  : undefined);
159
163
  this.subscriptions.push(subscription);
160
164
  }
161
- this.started = true;
162
165
  }
163
166
  async send(eventOrClass, dataOrOptions, maybeOptions) {
164
167
  if (!this.started) {
@@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, mock } from 'bun:test';
2
2
  import { SomeSendError, TransportSendError } from '../errors/index.js';
3
3
  import { TopologyBuilder } from '../topology/builder.js';
4
4
  import { LocalTransport, MultiTransport } from '../transport/index.js';
5
- import { MatadorEvent, createSubscriber } from '../types/index.js';
5
+ import { MatadorEvent, createDummyEnvelope, createSubscriber, } from '../types/index.js';
6
6
  import { Matador } from './matador.js';
7
7
  class UserCreatedEvent extends MatadorEvent {
8
8
  data;
@@ -623,6 +623,99 @@ describe('Matador', () => {
623
623
  expect(matador.isConnected()).toBe(true);
624
624
  });
625
625
  });
626
+ describe('start() race between subscribing and marking started', () => {
627
+ it('should allow send() from a subscriber callback fired while start() is still subscribing later queues', async () => {
628
+ const topology = TopologyBuilder.create()
629
+ .withNamespace('test')
630
+ .addQueue('events')
631
+ .addQueue('notifications')
632
+ .build();
633
+ let sendError;
634
+ const userSub = createSubscriber({
635
+ name: 'handle-user',
636
+ description: 'Handles user events',
637
+ callback: async (_envelope, context) => {
638
+ try {
639
+ await context.matador.send(OrderPlacedEvent, {
640
+ orderId: 'order-1',
641
+ amount: 100,
642
+ });
643
+ }
644
+ catch (err) {
645
+ sendError = err;
646
+ }
647
+ },
648
+ });
649
+ const orderSub = createSubscriber({
650
+ name: 'handle-order',
651
+ description: 'Handles order events',
652
+ callback: async () => { },
653
+ });
654
+ const schema = {
655
+ [UserCreatedEvent.key]: [UserCreatedEvent, [userSub]],
656
+ [OrderPlacedEvent.key]: [OrderPlacedEvent, [orderSub]],
657
+ };
658
+ // Simulates a broker delivering a message on the first subscribed
659
+ // queue before subscribe() has resolved for a later queue - the
660
+ // exact window in which `started` used to still be false.
661
+ let capturedHandler;
662
+ let releaseSecondSubscribe;
663
+ const secondSubscribeGate = new Promise((resolve) => {
664
+ releaseSecondSubscribe = resolve;
665
+ });
666
+ const raceTransport = {
667
+ name: 'race-transport',
668
+ capabilities: {
669
+ deliveryModes: ['at-least-once'],
670
+ delayedMessages: false,
671
+ deadLetterRouting: 'native',
672
+ attemptTracking: true,
673
+ concurrencyModel: 'prefetch',
674
+ ordering: 'none',
675
+ priorities: false,
676
+ },
677
+ isConnected: () => true,
678
+ connect: mock(async () => { }),
679
+ disconnect: mock(async () => { }),
680
+ send: mock(async () => 'race-transport'),
681
+ subscribe: mock(async (queueName, handler) => {
682
+ if (queueName === 'matador.test.notifications') {
683
+ await secondSubscribeGate;
684
+ }
685
+ else {
686
+ capturedHandler = handler;
687
+ }
688
+ return { unsubscribe: mock(async () => { }), isActive: true };
689
+ }),
690
+ applyTopology: mock(async () => { }),
691
+ complete: mock(async () => { }),
692
+ };
693
+ matador = new Matador({
694
+ transport: raceTransport,
695
+ topology,
696
+ schema,
697
+ consumeFrom: ['events', 'notifications'],
698
+ });
699
+ const startPromise = matador.start();
700
+ // Wait for the first queue's subscribe() to resolve and capture its
701
+ // handler, while the second queue's subscribe() is still pending.
702
+ while (!capturedHandler) {
703
+ await new Promise((resolve) => setTimeout(resolve, 0));
704
+ }
705
+ const envelope = createDummyEnvelope(new UserCreatedEvent({ userId: '123', email: 'test@example.com' }), { targetSubscriber: 'handle-user' });
706
+ await capturedHandler?.(envelope, {
707
+ handle: 'msg-1',
708
+ redelivered: false,
709
+ attemptNumber: 1,
710
+ deliveryCount: 1,
711
+ sourceQueue: 'matador.test.events',
712
+ sourceTransport: 'race-transport',
713
+ });
714
+ expect(sendError).toBeUndefined();
715
+ releaseSecondSubscribe?.();
716
+ await startPromise;
717
+ });
718
+ });
626
719
  describe('self-dispatch during graceful shutdown (stopReceiving)', () => {
627
720
  it('LOCAL transport only: a self-dispatched follow-up should still be delivered after stopReceiving()', async () => {
628
721
  const topology = TopologyBuilder.create()
package/dist/index.cjs CHANGED
@@ -1893,10 +1893,20 @@ var ProcessingPipeline = class {
1893
1893
  durationMs: performance.now() - startTime
1894
1894
  };
1895
1895
  }
1896
+ const isResumable = isResumableSubscriber(subscriber);
1897
+ const shouldProcessResult = await this.handleShouldProcess(
1898
+ envelope,
1899
+ subscriberDef,
1900
+ isResumable,
1901
+ receipt,
1902
+ startTime
1903
+ );
1904
+ if (!shouldProcessResult.success) {
1905
+ return shouldProcessResult;
1906
+ }
1896
1907
  let result;
1897
1908
  let error;
1898
1909
  let context;
1899
- const isResumable = isResumableSubscriber(subscriber);
1900
1910
  let existingCheckpoint;
1901
1911
  if (isResumable) {
1902
1912
  existingCheckpoint = await this.checkpointStore.get(envelope.id);
@@ -1972,10 +1982,65 @@ var ProcessingPipeline = class {
1972
1982
  subscriber: subscriberDef,
1973
1983
  receipt
1974
1984
  });
1985
+ return await this.handleFailure(
1986
+ error,
1987
+ decision,
1988
+ envelope,
1989
+ subscriberDef,
1990
+ isResumable,
1991
+ receipt,
1992
+ durationMs
1993
+ );
1994
+ }
1995
+ /**
1996
+ * Runs the retry policy's pre-processing check.
1997
+ *
1998
+ * This returns a terminal `ProcessResult` if the message should be skipped (dead-lettered/discarded) without ever invoking the callback,
1999
+ * or a success result if shouldProcess passes and we should continue processing the message.
2000
+ * @param envelope - The message envelope
2001
+ * @param subscriberDef - The subscriber definition
2002
+ * @param isResumable - Whether the subscriber is resumable
2003
+ * @param receipt - The message receipt
2004
+ * @param startTime - The start time of the processing
2005
+ * @returns A `ProcessResult` indicating if we should continue with processing the message, or fail it preemptively
2006
+ */
2007
+ async handleShouldProcess(envelope, subscriberDef, isResumable, receipt, startTime) {
2008
+ const decision = this.retryPolicy.shouldProcess({ envelope, receipt });
2009
+ if (decision.action === "process") {
2010
+ return {
2011
+ success: true,
2012
+ durationMs: 0
2013
+ };
2014
+ }
2015
+ const durationMs = performance.now() - startTime;
2016
+ const error = new Error(decision.reason);
2017
+ return await this.handleFailure(
2018
+ error,
2019
+ decision,
2020
+ envelope,
2021
+ subscriberDef,
2022
+ isResumable,
2023
+ receipt,
2024
+ durationMs
2025
+ );
2026
+ }
2027
+ /**
2028
+ * Performs failure handling and cleanup based on retry decision (e.g. dead-letter).
2029
+ *
2030
+ * @param error - The error from the processing attempt
2031
+ * @param decision - The decision from executing the appropriate retry policy check
2032
+ * @param envelope - The message envelope
2033
+ * @param subscriberDef - The subscriber definition
2034
+ * @param isResumable - Whether the subscriber is resumable
2035
+ * @param receipt - The message receipt
2036
+ * @param durationMs - The duration of processing
2037
+ * @returns A `ProcessResult` indicating failure
2038
+ */
2039
+ async handleFailure(error, decision, envelope, subscriberDef, isResumable, receipt, durationMs) {
1975
2040
  envelope.docket.lastError = error.message;
1976
2041
  envelope.docket.firstError ??= error.message;
1977
- if (decision.action === "dead-letter" && context) {
1978
- await context.clear();
2042
+ if (decision.action === "dead-letter" && isResumable) {
2043
+ await this.checkpointStore.delete(envelope.id);
1979
2044
  await this.hooks.onCheckpointCleared?.({
1980
2045
  envelope,
1981
2046
  subscriber: subscriberDef,
@@ -2081,15 +2146,31 @@ var StandardRetryPolicy = class {
2081
2146
  constructor(config = {}) {
2082
2147
  this.config = { ...defaultRetryConfig, ...config };
2083
2148
  }
2149
+ /**
2150
+ * Check if the message should be dead-lettered before the subscriber callback is invoked
2151
+ *
2152
+ * Uses the same delivery-count threshold as `shouldRetry` poison check, so an
2153
+ * already-poisoned message is dead-lettered without ever running the callback again.
2154
+ *
2155
+ * @param context - The shouldProcess context.
2156
+ * @returns A 'process' decision if the message is not poisoned; a dead-letter decision otherwise.
2157
+ */
2158
+ shouldProcess(context) {
2159
+ const poisonError = this.checkPoisoned(context.envelope, context.receipt);
2160
+ if (poisonError) {
2161
+ return {
2162
+ action: "dead-letter",
2163
+ queue: "undeliverable",
2164
+ reason: poisonError.message
2165
+ };
2166
+ }
2167
+ return { action: "process" };
2168
+ }
2084
2169
  shouldRetry(context) {
2085
2170
  const { envelope, error, subscriber, receipt } = context;
2086
2171
  const errorMessage = error.message;
2087
- if (receipt.deliveryCount >= this.config.maxDeliveries) {
2088
- const poisonError = new MessageMaybePoisonedError(
2089
- envelope.id,
2090
- receipt.deliveryCount,
2091
- this.config.maxDeliveries
2092
- );
2172
+ const poisonError = this.checkPoisoned(envelope, receipt);
2173
+ if (poisonError) {
2093
2174
  return {
2094
2175
  action: "dead-letter",
2095
2176
  queue: "undeliverable",
@@ -2151,6 +2232,23 @@ var StandardRetryPolicy = class {
2151
2232
  const delay = this.config.baseDelay * this.config.backoffMultiplier ** (attempt - 1);
2152
2233
  return Math.min(delay, this.config.maxDelay);
2153
2234
  }
2235
+ /**
2236
+ * Checks the native delivery count against the poison threshold
2237
+ *
2238
+ * @param envelope - The message envelope.
2239
+ * @param receipt - The message receipt.
2240
+ * @returns A MessageMaybePoisonedError if the message is poisoned; null otherwise
2241
+ */
2242
+ checkPoisoned(envelope, receipt) {
2243
+ if (receipt.deliveryCount >= this.config.maxDeliveries) {
2244
+ return new MessageMaybePoisonedError(
2245
+ envelope.id,
2246
+ receipt.deliveryCount,
2247
+ this.config.maxDeliveries
2248
+ );
2249
+ }
2250
+ return null;
2251
+ }
2154
2252
  };
2155
2253
 
2156
2254
  // src/schema/types.ts
@@ -2461,6 +2559,7 @@ var Matador = class {
2461
2559
  "[Matador] \u{1F7E1} Worker not subscribing to any queues (consumeFrom is empty)."
2462
2560
  );
2463
2561
  }
2562
+ this.started = true;
2464
2563
  for (const queueName of this.consumeFrom) {
2465
2564
  const qualifiedName = resolveTargetQueueName(this.topology, queueName);
2466
2565
  const queueDef = findQueueDefinition(this.topology, queueName);
@@ -2479,7 +2578,6 @@ var Matador = class {
2479
2578
  );
2480
2579
  this.subscriptions.push(subscription);
2481
2580
  }
2482
- this.started = true;
2483
2581
  }
2484
2582
  async send(eventOrClass, dataOrOptions, maybeOptions) {
2485
2583
  if (!this.started) {
@@ -3626,6 +3724,9 @@ var RabbitMQTransport = class {
3626
3724
  if (queueDef.consumerTimeout) {
3627
3725
  queueOptions.arguments["x-consumer-timeout"] = queueDef.consumerTimeout;
3628
3726
  }
3727
+ if (queueDef.singleActiveConsumer) {
3728
+ queueOptions.arguments["x-single-active-consumer"] = true;
3729
+ }
3629
3730
  return queueOptions;
3630
3731
  }
3631
3732
  async assertWorkQueue(channel, topology, queueDef) {