@zdavison/matador 2.0.11 → 3.0.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.
@@ -120,6 +120,19 @@ export declare class Matador implements Dispatcher {
120
120
  * Waits for all handlers to become idle.
121
121
  */
122
122
  waitForIdle(timeoutMs?: number): Promise<boolean>;
123
+ /**
124
+ * Stops receiving new messages without performing full shutdown.
125
+ * After calling this, no new messages will be delivered from RabbitMQ.
126
+ * The transport remains connected for later shutdown.
127
+ *
128
+ * Use this when you need to coordinate shutdown across multiple systems:
129
+ * 1. Call stopReceiving() to stop new message delivery
130
+ * 2. Wait for both Matador and your other systems to idle
131
+ * 3. Call shutdown() to disconnect (or let NestJS lifecycle handle it)
132
+ *
133
+ * @returns true if receiving was stopped, false if not started or already stopped
134
+ */
135
+ stopReceiving(): Promise<boolean>;
123
136
  /**
124
137
  * Gracefully shuts down Matador.
125
138
  */
@@ -128,6 +141,6 @@ export declare class Matador implements Dispatcher {
128
141
  * Checks if transport is connected.
129
142
  */
130
143
  isConnected(): boolean;
131
- private stopReceiving;
144
+ private unsubscribeAll;
132
145
  }
133
146
  //# sourceMappingURL=matador.d.ts.map
@@ -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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"}
@@ -68,7 +68,7 @@ export class Matador {
68
68
  defaultQueue,
69
69
  });
70
70
  // Create shutdown manager
71
- this.shutdownManager = new ShutdownManager(() => this.fanout.eventsBeingEnqueuedCount, () => this.stopReceiving(), () => this.transport.disconnect(), config.shutdownConfig);
71
+ this.shutdownManager = new ShutdownManager(() => this.fanout.eventsBeingEnqueuedCount, () => this.unsubscribeAll(), () => this.transport.disconnect(), config.shutdownConfig);
72
72
  }
73
73
  /**
74
74
  * Registers an event class with its subscribers.
@@ -203,6 +203,24 @@ export class Matador {
203
203
  }
204
204
  return true;
205
205
  }
206
+ /**
207
+ * Stops receiving new messages without performing full shutdown.
208
+ * After calling this, no new messages will be delivered from RabbitMQ.
209
+ * The transport remains connected for later shutdown.
210
+ *
211
+ * Use this when you need to coordinate shutdown across multiple systems:
212
+ * 1. Call stopReceiving() to stop new message delivery
213
+ * 2. Wait for both Matador and your other systems to idle
214
+ * 3. Call shutdown() to disconnect (or let NestJS lifecycle handle it)
215
+ *
216
+ * @returns true if receiving was stopped, false if not started or already stopped
217
+ */
218
+ async stopReceiving() {
219
+ if (!this.started) {
220
+ return false;
221
+ }
222
+ return this.shutdownManager.publicStopReceiving();
223
+ }
206
224
  /**
207
225
  * Gracefully shuts down Matador.
208
226
  */
@@ -219,7 +237,7 @@ export class Matador {
219
237
  isConnected() {
220
238
  return this.transport.isConnected();
221
239
  }
222
- async stopReceiving() {
240
+ async unsubscribeAll() {
223
241
  for (const subscription of this.subscriptions) {
224
242
  await subscription.unsubscribe();
225
243
  }
@@ -361,6 +361,106 @@ describe('Matador', () => {
361
361
  expect(matador.send(event)).rejects.toThrow();
362
362
  });
363
363
  });
364
+ describe('stopReceiving', () => {
365
+ it('should return false when not started', async () => {
366
+ const topology = TopologyBuilder.create()
367
+ .withNamespace('test')
368
+ .addQueue('events')
369
+ .build();
370
+ const subscriber = createSubscriber({
371
+ name: 'handle-user',
372
+ description: 'Handles user events',
373
+ callback: async () => { },
374
+ });
375
+ const schema = {
376
+ [UserCreatedEvent.key]: [UserCreatedEvent, [subscriber]],
377
+ };
378
+ matador = new Matador({ transport, topology, schema });
379
+ const result = await matador.stopReceiving();
380
+ expect(result).toBe(false);
381
+ });
382
+ it('should return true when started and stop receiving messages', async () => {
383
+ const topology = TopologyBuilder.create()
384
+ .withNamespace('test')
385
+ .addQueue('events')
386
+ .build();
387
+ const subscriber = createSubscriber({
388
+ name: 'handle-user',
389
+ description: 'Handles user events',
390
+ callback: async () => { },
391
+ });
392
+ const schema = {
393
+ [UserCreatedEvent.key]: [UserCreatedEvent, [subscriber]],
394
+ };
395
+ matador = new Matador({ transport, topology, schema });
396
+ await matador.start();
397
+ const result = await matador.stopReceiving();
398
+ expect(result).toBe(true);
399
+ expect(matador.isConnected()).toBe(true);
400
+ });
401
+ it('should return false when called twice', async () => {
402
+ const topology = TopologyBuilder.create()
403
+ .withNamespace('test')
404
+ .addQueue('events')
405
+ .build();
406
+ const subscriber = createSubscriber({
407
+ name: 'handle-user',
408
+ description: 'Handles user events',
409
+ callback: async () => { },
410
+ });
411
+ const schema = {
412
+ [UserCreatedEvent.key]: [UserCreatedEvent, [subscriber]],
413
+ };
414
+ matador = new Matador({ transport, topology, schema });
415
+ await matador.start();
416
+ const result1 = await matador.stopReceiving();
417
+ const result2 = await matador.stopReceiving();
418
+ expect(result1).toBe(true);
419
+ expect(result2).toBe(false);
420
+ });
421
+ it('should allow shutdown after stopReceiving', async () => {
422
+ const topology = TopologyBuilder.create()
423
+ .withNamespace('test')
424
+ .addQueue('events')
425
+ .build();
426
+ const subscriber = createSubscriber({
427
+ name: 'handle-user',
428
+ description: 'Handles user events',
429
+ callback: async () => { },
430
+ });
431
+ const schema = {
432
+ [UserCreatedEvent.key]: [UserCreatedEvent, [subscriber]],
433
+ };
434
+ matador = new Matador({ transport, topology, schema });
435
+ await matador.start();
436
+ await matador.stopReceiving();
437
+ await matador.shutdown();
438
+ expect(matador.isConnected()).toBe(false);
439
+ });
440
+ it('should allow sending events after stopReceiving but before shutdown', async () => {
441
+ const topology = TopologyBuilder.create()
442
+ .withNamespace('test')
443
+ .addQueue('events')
444
+ .build();
445
+ const subscriber = createSubscriber({
446
+ name: 'handle-user',
447
+ description: 'Handles user events',
448
+ callback: async () => { },
449
+ });
450
+ const schema = {
451
+ [UserCreatedEvent.key]: [UserCreatedEvent, [subscriber]],
452
+ };
453
+ matador = new Matador({ transport, topology, schema });
454
+ await matador.start();
455
+ await matador.stopReceiving();
456
+ const event = new UserCreatedEvent({
457
+ userId: '123',
458
+ email: 'test@example.com',
459
+ });
460
+ const result = await matador.send(event);
461
+ expect(result.subscribersSent).toBe(1);
462
+ });
463
+ });
364
464
  describe('idle state', () => {
365
465
  it('should report idle when no processing', async () => {
366
466
  const topology = TopologyBuilder.create()
@@ -2,7 +2,7 @@ import { type Logger } from '../hooks/index.js';
2
2
  /**
3
3
  * Shutdown state.
4
4
  */
5
- export type ShutdownState = 'running' | 'stopping-receive' | 'waiting-handlers' | 'stopping-enqueue' | 'disconnecting' | 'stopped';
5
+ export type ShutdownState = 'running' | 'receive-stopped' | 'stopping-receive' | 'waiting-handlers' | 'stopping-enqueue' | 'disconnecting' | 'stopped';
6
6
  /**
7
7
  * Configuration for shutdown manager.
8
8
  */
@@ -64,6 +64,17 @@ export declare class ShutdownManager {
64
64
  * Decrement processing counter.
65
65
  */
66
66
  decrementProcessing(): void;
67
+ /**
68
+ * Stops receiving new messages without performing full shutdown.
69
+ * After calling this, no new messages will be delivered from the transport.
70
+ * The transport remains connected for later shutdown.
71
+ *
72
+ * Use this when you need to stop receiving before waiting for idle,
73
+ * for example to coordinate waiting across multiple systems.
74
+ *
75
+ * @returns true if receiving was stopped, false if already stopped or shutting down
76
+ */
77
+ publicStopReceiving(): Promise<boolean>;
67
78
  /**
68
79
  * Performs graceful shutdown.
69
80
  */
@@ -1 +1 @@
1
- {"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../../src/core/shutdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAiB,MAAM,mBAAmB,CAAC;AAE/D;;GAEG;AACH,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,kBAAkB,GAClB,kBAAkB,GAClB,kBAAkB,GAClB,eAAe,GACf,SAAS,CAAC;AAEd;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IAEzC,yDAAyD;IACzD,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IAErC,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB,EAAE,cAInC,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,qBAAa,eAAe;IAOxB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IARtC,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,oBAAoB,CAAK;IACjC,OAAO,CAAC,gBAAgB,CAAQ;gBAGb,eAAe,EAAE,MAAM,MAAM,EAC7B,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAClC,mBAAmB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EACzD,MAAM,GAAE,OAAO,CAAC,cAAc,CAAM;IAKtC;;OAEG;IACH,IAAI,KAAK,IAAI,aAAa,CAEzB;IAED;;OAEG;IACH,IAAI,gBAAgB,IAAI,OAAO,CAE9B;IAED;;OAEG;IACH,gBAAgB,IAAI,aAAa;IASjC;;OAEG;IACH,mBAAmB,IAAI,IAAI;IAI3B;;OAEG;IACH,mBAAmB,IAAI,IAAI;IAI3B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB/B;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;YAOlB,WAAW;IAezB,OAAO,CAAC,KAAK;CAGd"}
1
+ {"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../../src/core/shutdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAiB,MAAM,mBAAmB,CAAC;AAE/D;;GAEG;AACH,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,iBAAiB,GACjB,kBAAkB,GAClB,kBAAkB,GAClB,kBAAkB,GAClB,eAAe,GACf,SAAS,CAAC;AAEd;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IAEzC,yDAAyD;IACzD,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IAErC,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB,EAAE,cAInC,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,qBAAa,eAAe;IAOxB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IARtC,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,oBAAoB,CAAK;IACjC,OAAO,CAAC,gBAAgB,CAAQ;gBAGb,eAAe,EAAE,MAAM,MAAM,EAC7B,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAClC,mBAAmB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EACzD,MAAM,GAAE,OAAO,CAAC,cAAc,CAAM;IAKtC;;OAEG;IACH,IAAI,KAAK,IAAI,aAAa,CAEzB;IAED;;OAEG;IACH,IAAI,gBAAgB,IAAI,OAAO,CAE9B;IAED;;OAEG;IACH,gBAAgB,IAAI,aAAa;IASjC;;OAEG;IACH,mBAAmB,IAAI,IAAI;IAI3B;;OAEG;IACH,mBAAmB,IAAI,IAAI;IAI3B;;;;;;;;;OASG;IACG,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC;IAW7C;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IA2B/B;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;YAOlB,WAAW;IAezB,OAAO,CAAC,KAAK;CAGd"}
@@ -66,15 +66,37 @@ export class ShutdownManager {
66
66
  this.eventsBeingProcessed--;
67
67
  }
68
68
  /**
69
- * Performs graceful shutdown.
69
+ * Stops receiving new messages without performing full shutdown.
70
+ * After calling this, no new messages will be delivered from the transport.
71
+ * The transport remains connected for later shutdown.
72
+ *
73
+ * Use this when you need to stop receiving before waiting for idle,
74
+ * for example to coordinate waiting across multiple systems.
75
+ *
76
+ * @returns true if receiving was stopped, false if already stopped or shutting down
70
77
  */
71
- async shutdown() {
78
+ async publicStopReceiving() {
72
79
  if (this._state !== 'running') {
73
- return;
80
+ return false;
74
81
  }
75
- // 1. Stop receiving new messages
76
82
  this._state = 'stopping-receive';
77
83
  await this.stopReceiving();
84
+ this._state = 'receive-stopped';
85
+ return true;
86
+ }
87
+ /**
88
+ * Performs graceful shutdown.
89
+ */
90
+ async shutdown() {
91
+ // Allow shutdown from either 'running' or 'receive-stopped' states
92
+ if (this._state !== 'running' && this._state !== 'receive-stopped') {
93
+ return;
94
+ }
95
+ // 1. Stop receiving new messages (skip if already stopped)
96
+ if (this._state === 'running') {
97
+ this._state = 'stopping-receive';
98
+ await this.stopReceiving();
99
+ }
78
100
  // 2. Wait for handlers to idle
79
101
  this._state = 'waiting-handlers';
80
102
  await this.waitForIdle();
@@ -278,6 +278,81 @@ describe('ShutdownManager', () => {
278
278
  expect(stopReceiving).toHaveBeenCalledTimes(1);
279
279
  });
280
280
  });
281
+ describe('publicStopReceiving', () => {
282
+ beforeEach(() => {
283
+ manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport);
284
+ });
285
+ it('should return true when called from running state', async () => {
286
+ const result = await manager.publicStopReceiving();
287
+ expect(result).toBe(true);
288
+ });
289
+ it('should transition to receive-stopped state', async () => {
290
+ await manager.publicStopReceiving();
291
+ expect(manager.state).toBe('receive-stopped');
292
+ });
293
+ it('should call stopReceiving callback', async () => {
294
+ await manager.publicStopReceiving();
295
+ expect(stopReceiving).toHaveBeenCalledTimes(1);
296
+ });
297
+ it('should return false when called after already stopped receiving', async () => {
298
+ await manager.publicStopReceiving();
299
+ const result = await manager.publicStopReceiving();
300
+ expect(result).toBe(false);
301
+ expect(stopReceiving).toHaveBeenCalledTimes(1);
302
+ });
303
+ it('should return false when called after shutdown started', async () => {
304
+ manager.incrementProcessing();
305
+ const shutdownPromise = manager.shutdown();
306
+ await new Promise((resolve) => setTimeout(resolve, 10));
307
+ const result = await manager.publicStopReceiving();
308
+ expect(result).toBe(false);
309
+ manager.decrementProcessing();
310
+ await shutdownPromise;
311
+ });
312
+ it('should still allow enqueue after stopReceiving', async () => {
313
+ await manager.publicStopReceiving();
314
+ expect(manager.isEnqueueAllowed).toBe(true);
315
+ });
316
+ it('should allow shutdown to complete after publicStopReceiving', async () => {
317
+ await manager.publicStopReceiving();
318
+ await manager.shutdown();
319
+ expect(manager.state).toBe('stopped');
320
+ expect(stopReceiving).toHaveBeenCalledTimes(1);
321
+ expect(disconnectTransport).toHaveBeenCalledTimes(1);
322
+ });
323
+ it('should skip stopReceiving during shutdown if already called', async () => {
324
+ await manager.publicStopReceiving();
325
+ expect(stopReceiving).toHaveBeenCalledTimes(1);
326
+ await manager.shutdown();
327
+ expect(stopReceiving).toHaveBeenCalledTimes(1);
328
+ });
329
+ it('should allow waiting for idle after stopReceiving', async () => {
330
+ manager.incrementProcessing();
331
+ await manager.publicStopReceiving();
332
+ expect(manager.state).toBe('receive-stopped');
333
+ expect(manager.getHandlersState().isIdle).toBe(false);
334
+ manager.decrementProcessing();
335
+ expect(manager.getHandlersState().isIdle).toBe(true);
336
+ });
337
+ it('should transition through correct states when shutdown follows publicStopReceiving', async () => {
338
+ const states = [];
339
+ disconnectTransport = mock(async () => {
340
+ states.push(manager.state);
341
+ });
342
+ manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport);
343
+ states.push(manager.state);
344
+ await manager.publicStopReceiving();
345
+ states.push(manager.state);
346
+ await manager.shutdown();
347
+ states.push(manager.state);
348
+ expect(states).toEqual([
349
+ 'running',
350
+ 'receive-stopped',
351
+ 'disconnecting',
352
+ 'stopped',
353
+ ]);
354
+ });
355
+ });
281
356
  describe('forceStop', () => {
282
357
  beforeEach(() => {
283
358
  manager = new ShutdownManager(getEnqueueCount, stopReceiving, disconnectTransport);
package/dist/index.cjs CHANGED
@@ -942,14 +942,35 @@ var ShutdownManager = class {
942
942
  this.eventsBeingProcessed--;
943
943
  }
944
944
  /**
945
- * Performs graceful shutdown.
945
+ * Stops receiving new messages without performing full shutdown.
946
+ * After calling this, no new messages will be delivered from the transport.
947
+ * The transport remains connected for later shutdown.
948
+ *
949
+ * Use this when you need to stop receiving before waiting for idle,
950
+ * for example to coordinate waiting across multiple systems.
951
+ *
952
+ * @returns true if receiving was stopped, false if already stopped or shutting down
946
953
  */
947
- async shutdown() {
954
+ async publicStopReceiving() {
948
955
  if (this._state !== "running") {
949
- return;
956
+ return false;
950
957
  }
951
958
  this._state = "stopping-receive";
952
959
  await this.stopReceiving();
960
+ this._state = "receive-stopped";
961
+ return true;
962
+ }
963
+ /**
964
+ * Performs graceful shutdown.
965
+ */
966
+ async shutdown() {
967
+ if (this._state !== "running" && this._state !== "receive-stopped") {
968
+ return;
969
+ }
970
+ if (this._state === "running") {
971
+ this._state = "stopping-receive";
972
+ await this.stopReceiving();
973
+ }
953
974
  this._state = "waiting-handlers";
954
975
  await this.waitForIdle();
955
976
  this._state = "stopping-enqueue";
@@ -1988,7 +2009,7 @@ var Matador = class {
1988
2009
  });
1989
2010
  this.shutdownManager = new ShutdownManager(
1990
2011
  () => this.fanout.eventsBeingEnqueuedCount,
1991
- () => this.stopReceiving(),
2012
+ () => this.unsubscribeAll(),
1992
2013
  () => this.transport.disconnect(),
1993
2014
  config.shutdownConfig
1994
2015
  );
@@ -2127,6 +2148,24 @@ var Matador = class {
2127
2148
  }
2128
2149
  return true;
2129
2150
  }
2151
+ /**
2152
+ * Stops receiving new messages without performing full shutdown.
2153
+ * After calling this, no new messages will be delivered from RabbitMQ.
2154
+ * The transport remains connected for later shutdown.
2155
+ *
2156
+ * Use this when you need to coordinate shutdown across multiple systems:
2157
+ * 1. Call stopReceiving() to stop new message delivery
2158
+ * 2. Wait for both Matador and your other systems to idle
2159
+ * 3. Call shutdown() to disconnect (or let NestJS lifecycle handle it)
2160
+ *
2161
+ * @returns true if receiving was stopped, false if not started or already stopped
2162
+ */
2163
+ async stopReceiving() {
2164
+ if (!this.started) {
2165
+ return false;
2166
+ }
2167
+ return this.shutdownManager.publicStopReceiving();
2168
+ }
2130
2169
  /**
2131
2170
  * Gracefully shuts down Matador.
2132
2171
  */
@@ -2143,7 +2182,7 @@ var Matador = class {
2143
2182
  isConnected() {
2144
2183
  return this.transport.isConnected();
2145
2184
  }
2146
- async stopReceiving() {
2185
+ async unsubscribeAll() {
2147
2186
  for (const subscription of this.subscriptions) {
2148
2187
  await subscription.unsubscribe();
2149
2188
  }