@zdavison/matador 2.0.3 → 2.0.5

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.
Files changed (76) hide show
  1. package/dist/core/fanout.test.js +0 -1
  2. package/dist/core/matador.d.ts +2 -2
  3. package/dist/core/matador.d.ts.map +1 -1
  4. package/dist/core/matador.js +1 -0
  5. package/dist/errors/index.d.ts +1 -1
  6. package/dist/errors/index.d.ts.map +1 -1
  7. package/dist/errors/index.js +1 -1
  8. package/dist/errors/retry-errors.d.ts +40 -5
  9. package/dist/errors/retry-errors.d.ts.map +1 -1
  10. package/dist/errors/retry-errors.js +56 -9
  11. package/dist/errors/retry-errors.test.d.ts +2 -0
  12. package/dist/errors/retry-errors.test.d.ts.map +1 -0
  13. package/dist/errors/retry-errors.test.js +136 -0
  14. package/dist/index.cjs +73 -28
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +5 -5
  17. package/dist/index.d.ts +5 -5
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +3 -3
  20. package/dist/index.js.map +1 -1
  21. package/dist/pipeline/pipeline.d.ts +4 -1
  22. package/dist/pipeline/pipeline.d.ts.map +1 -1
  23. package/dist/pipeline/pipeline.js +16 -4
  24. package/dist/pipeline/pipeline.test.js +48 -1
  25. package/dist/retry/standard-policy.test.js +7 -1
  26. package/dist/schema/registry.d.ts.map +1 -1
  27. package/dist/schema/registry.js +4 -1
  28. package/dist/schema/types.test.js +0 -1
  29. package/dist/topology/builder.d.ts.map +1 -1
  30. package/dist/topology/builder.js +2 -1
  31. package/dist/topology/builder.test.js +68 -0
  32. package/dist/topology/index.d.ts +2 -2
  33. package/dist/topology/index.d.ts.map +1 -1
  34. package/dist/topology/index.js +1 -1
  35. package/dist/topology/types.d.ts +53 -1
  36. package/dist/topology/types.d.ts.map +1 -1
  37. package/dist/topology/types.js +10 -0
  38. package/dist/transport/rabbitmq/rabbitmq-transport.d.ts.map +1 -1
  39. package/dist/transport/rabbitmq/rabbitmq-transport.js +30 -22
  40. package/dist/transport/rabbitmq/rabbitmq-transport.test.js +1 -1
  41. package/dist/types/dispatcher.d.ts +14 -0
  42. package/dist/types/dispatcher.d.ts.map +1 -0
  43. package/dist/types/dispatcher.js +1 -0
  44. package/dist/types/envelope.d.ts +17 -0
  45. package/dist/types/envelope.d.ts.map +1 -1
  46. package/dist/types/envelope.js +19 -0
  47. package/dist/types/index.d.ts +3 -2
  48. package/dist/types/index.d.ts.map +1 -1
  49. package/dist/types/index.js +1 -1
  50. package/dist/types/subscriber.d.ts +28 -8
  51. package/dist/types/subscriber.d.ts.map +1 -1
  52. package/dist/types/subscriber.js +0 -2
  53. package/package.json +1 -1
  54. package/src/core/fanout.test.ts +0 -1
  55. package/src/core/matador.ts +3 -1
  56. package/src/errors/index.ts +1 -0
  57. package/src/errors/retry-errors.test.ts +175 -0
  58. package/src/errors/retry-errors.ts +63 -9
  59. package/src/index.ts +11 -0
  60. package/src/pipeline/pipeline.test.ts +62 -2
  61. package/src/pipeline/pipeline.ts +28 -6
  62. package/src/retry/standard-policy.test.ts +9 -1
  63. package/src/schema/registry.ts +4 -1
  64. package/src/schema/types.test.ts +0 -1
  65. package/src/topology/builder.test.ts +90 -0
  66. package/src/topology/builder.ts +2 -1
  67. package/src/topology/index.ts +4 -0
  68. package/src/topology/types.ts +75 -1
  69. package/src/transport/rabbitmq/rabbitmq-transport.test.ts +1 -1
  70. package/src/transport/rabbitmq/rabbitmq-transport.ts +33 -25
  71. package/src/types/dispatcher.ts +18 -0
  72. package/src/types/envelope.ts +23 -0
  73. package/src/types/index.ts +5 -1
  74. package/src/types/subscriber.ts +33 -10
  75. package/test/e2e/rabbitmq-transport.e2e.test.ts +287 -0
  76. package/tsconfig.tsbuildinfo +1 -1
@@ -1,7 +1,16 @@
1
1
  import type { SubscriberContext } from '../checkpoint/index.js';
2
2
  import type { Idempotency, Importance } from './common.js';
3
+ import type { Dispatcher } from './dispatcher.js';
3
4
  import type { Envelope } from './envelope.js';
4
5
  import type { MatadorEvent } from './event.js';
6
+ /**
7
+ * Context passed to subscriber callbacks.
8
+ * Provides access to the Matador instance for sending additional events.
9
+ */
10
+ export interface CallbackContext {
11
+ /** Matador dispatcher for sending additional events from within a subscriber */
12
+ readonly matador: Dispatcher;
13
+ }
5
14
  /**
6
15
  * Helper type to get the envelope type for a subscriber callback.
7
16
  * Extracts the data type from a MatadorEvent and wraps it in an Envelope.
@@ -14,14 +23,27 @@ import type { MatadorEvent } from './event.js';
14
23
  export type EnvelopeOf<T extends MatadorEvent> = Envelope<T['data']>;
15
24
  /**
16
25
  * Callback function executed when an event is received (standard subscribers).
17
- * Receives the full envelope containing id, data, and docket.
26
+ * Receives the full envelope containing id, data, and docket, plus a context
27
+ * with access to the matador instance for sending additional events.
28
+ *
29
+ * The callback may optionally return a value, which will be included in the
30
+ * onWorkerSuccess hook context for logging or observability purposes.
18
31
  */
19
- export type StandardCallback<T = unknown> = (envelope: Envelope<T>) => Promise<void> | void;
32
+ export type StandardCallback<T = unknown> = (envelope: Envelope<T>, context: CallbackContext) => Promise<unknown> | unknown | void;
33
+ /**
34
+ * Context for resumable subscriber callbacks.
35
+ * Combines checkpoint operations (io, all) with matador access.
36
+ */
37
+ export type ResumableCallbackContext = SubscriberContext & CallbackContext;
20
38
  /**
21
39
  * Callback function for resumable subscribers.
22
- * Receives the envelope and a SubscriberContext with io() for checkpointed operations.
40
+ * Receives the envelope and a context with io() for checkpointed operations
41
+ * and matador for sending additional events.
42
+ *
43
+ * The callback may optionally return a value, which will be included in the
44
+ * onWorkerSuccess hook context for logging or observability purposes.
23
45
  */
24
- export type ResumableCallback<T = unknown> = (envelope: Envelope<T>, context: SubscriberContext) => Promise<void> | void;
46
+ export type ResumableCallback<T = unknown> = (envelope: Envelope<T>, context: ResumableCallbackContext) => Promise<unknown> | unknown | void;
25
47
  /**
26
48
  * Callback function executed when an event is received.
27
49
  * @deprecated Use StandardCallback or ResumableCallback instead.
@@ -86,7 +108,7 @@ export type Subscriber<T extends MatadorEvent> = StandardSubscriber<T> | Resumab
86
108
  * is in a remote service. Declares the subscriber contract without providing
87
109
  * the callback.
88
110
  */
89
- export interface SubscriberStub extends StandardSubscriberOptions {
111
+ export interface SubscriberStub extends Omit<StandardSubscriberOptions, 'description'> {
90
112
  /** Human-readable name for the subscriber */
91
113
  readonly name: string;
92
114
  /** Indicates this is a stub without implementation */
@@ -175,7 +197,6 @@ export declare function createSubscriber<T extends MatadorEvent>(input: CreateSu
175
197
  */
176
198
  export interface CreateSubscriberStubInput {
177
199
  readonly name: string;
178
- readonly description: string;
179
200
  readonly idempotent?: 'yes' | 'no' | 'unknown' | undefined;
180
201
  readonly importance?: Importance | undefined;
181
202
  readonly targetQueue?: string | undefined;
@@ -188,7 +209,6 @@ export interface CreateSubscriberStubInput {
188
209
  * ```typescript
189
210
  * const stub = createSubscriberStub({
190
211
  * name: 'remote-analytics',
191
- * description: 'Sends events to remote analytics service',
192
212
  * targetQueue: 'analytics-worker',
193
213
  * });
194
214
  * ```
@@ -199,7 +219,7 @@ export declare function createSubscriberStub(input: CreateSubscriberStubInput):
199
219
  */
200
220
  export interface SubscriberDefinition {
201
221
  readonly name: string;
202
- readonly description: string;
222
+ readonly description?: string | undefined;
203
223
  readonly idempotent: Idempotency;
204
224
  readonly importance: Importance;
205
225
  readonly targetQueue?: string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"subscriber.d.ts","sourceRoot":"","sources":["../../src/types/subscriber.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"subscriber.d.ts","sourceRoot":"","sources":["../../src/types/subscriber.ts"],"names":[],"mappings":"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"}
@@ -79,7 +79,6 @@ export function createSubscriber(input) {
79
79
  * ```typescript
80
80
  * const stub = createSubscriberStub({
81
81
  * name: 'remote-analytics',
82
- * description: 'Sends events to remote analytics service',
83
82
  * targetQueue: 'analytics-worker',
84
83
  * });
85
84
  * ```
@@ -87,7 +86,6 @@ export function createSubscriber(input) {
87
86
  export function createSubscriberStub(input) {
88
87
  return {
89
88
  name: input.name,
90
- description: input.description,
91
89
  isStub: true,
92
90
  idempotent: input.idempotent ?? 'unknown',
93
91
  importance: input.importance ?? 'should-investigate',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zdavison/matador",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
4
4
  "description": "A batteries-included event system.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -630,7 +630,6 @@ describe('FanoutEngine', () => {
630
630
  it('should work with subscriber stubs', async () => {
631
631
  const stub = createSubscriberStub({
632
632
  name: 'remote-subscriber',
633
- description: 'Remote subscriber stub',
634
633
  enabled: () => false,
635
634
  });
636
635
 
@@ -18,6 +18,7 @@ import { getQualifiedQueueName } from '../topology/index.js';
18
18
  import type { Subscription, Transport } from '../transport/index.js';
19
19
  import type {
20
20
  AnySubscriber,
21
+ Dispatcher,
21
22
  Event,
22
23
  EventClass,
23
24
  EventOptions,
@@ -71,7 +72,7 @@ export interface MatadorConfig {
71
72
  * - Fanout: Event sending
72
73
  * - Shutdown: Graceful termination
73
74
  */
74
- export class Matador {
75
+ export class Matador implements Dispatcher {
75
76
  private readonly transport: Transport;
76
77
  private readonly topology: Topology;
77
78
  private readonly schema;
@@ -114,6 +115,7 @@ export class Matador {
114
115
  retryPolicy: this.retryPolicy,
115
116
  hooks: this.hooks,
116
117
  checkpointStore: config.checkpointStore,
118
+ dispatcher: this,
117
119
  });
118
120
 
119
121
  // Create fanout engine
@@ -2,6 +2,7 @@ export type { HasDescription } from './has-description.js';
2
2
  export { hasDescription } from './has-description.js';
3
3
 
4
4
  export {
5
+ assertEvent,
5
6
  DontRetry,
6
7
  DoRetry,
7
8
  EventAssertionError,
@@ -0,0 +1,175 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+ import { createEnvelope } from '../types/envelope.js';
3
+ import {
4
+ assertEvent,
5
+ DontRetry,
6
+ DoRetry,
7
+ EventAssertionError,
8
+ isAssertionError,
9
+ isDontRetry,
10
+ isDoRetry,
11
+ } from './retry-errors.js';
12
+
13
+ function createTestEnvelope(data: unknown = { foo: 'bar' }) {
14
+ return createEnvelope({
15
+ data,
16
+ eventKey: 'test.event',
17
+ targetSubscriber: 'test-subscriber',
18
+ importance: 'can-ignore',
19
+ });
20
+ }
21
+
22
+ describe('retry-errors', () => {
23
+ describe('DoRetry', () => {
24
+ it('should have correct name', () => {
25
+ const error = new DoRetry('test message');
26
+ expect(error.name).toBe('DoRetry');
27
+ });
28
+
29
+ it('should have description', () => {
30
+ const error = new DoRetry('test message');
31
+ expect(error.description).toBeDefined();
32
+ });
33
+
34
+ it('should serialize to JSON', () => {
35
+ const error = new DoRetry('test message');
36
+ const json = error.toJSON();
37
+ expect(json.name).toBe('DoRetry');
38
+ expect(json.message).toBe('test message');
39
+ expect(json.description).toBeDefined();
40
+ });
41
+ });
42
+
43
+ describe('DontRetry', () => {
44
+ it('should have correct name', () => {
45
+ const error = new DontRetry('test message');
46
+ expect(error.name).toBe('DontRetry');
47
+ });
48
+
49
+ it('should have description', () => {
50
+ const error = new DontRetry('test message');
51
+ expect(error.description).toBeDefined();
52
+ });
53
+
54
+ it('should serialize to JSON', () => {
55
+ const error = new DontRetry('test message');
56
+ const json = error.toJSON();
57
+ expect(json.name).toBe('DontRetry');
58
+ expect(json.message).toBe('test message');
59
+ expect(json.description).toBeDefined();
60
+ });
61
+ });
62
+
63
+ describe('EventAssertionError', () => {
64
+ it('should have correct name', () => {
65
+ const envelope = createTestEnvelope();
66
+ const error = new EventAssertionError(envelope, 'assertion failed');
67
+ expect(error.name).toBe('EventAssertionError');
68
+ });
69
+
70
+ it('should store the envelope', () => {
71
+ const envelope = createTestEnvelope();
72
+ const error = new EventAssertionError(envelope, 'assertion failed');
73
+ expect(error.envelope).toBe(envelope);
74
+ });
75
+
76
+ it('should have description', () => {
77
+ const envelope = createTestEnvelope();
78
+ const error = new EventAssertionError(envelope, 'assertion failed');
79
+ expect(error.description).toBeDefined();
80
+ expect(error.description).toContain('NOT to retry');
81
+ });
82
+
83
+ it('should serialize to JSON with envelope', () => {
84
+ const envelope = createTestEnvelope();
85
+ const error = new EventAssertionError(envelope, 'assertion failed');
86
+ const json = error.toJSON();
87
+ expect(json.name).toBe('EventAssertionError');
88
+ expect(json.message).toBe('assertion failed');
89
+ expect(json.envelope).toBe(envelope);
90
+ expect(json.description).toBeDefined();
91
+ });
92
+ });
93
+
94
+ describe('assertEvent', () => {
95
+ it('should not throw when value is truthy', () => {
96
+ const envelope = createTestEnvelope();
97
+ expect(() => assertEvent(envelope, true, 'should be true')).not.toThrow();
98
+ expect(() =>
99
+ assertEvent(envelope, 'string', 'should be string'),
100
+ ).not.toThrow();
101
+ expect(() => assertEvent(envelope, 1, 'should be number')).not.toThrow();
102
+ expect(() => assertEvent(envelope, {}, 'should be object')).not.toThrow();
103
+ expect(() => assertEvent(envelope, [], 'should be array')).not.toThrow();
104
+ });
105
+
106
+ it('should throw EventAssertionError when value is falsy', () => {
107
+ const envelope = createTestEnvelope();
108
+
109
+ expect(() => assertEvent(envelope, false, 'value was false')).toThrow(
110
+ EventAssertionError,
111
+ );
112
+ expect(() => assertEvent(envelope, null, 'value was null')).toThrow(
113
+ EventAssertionError,
114
+ );
115
+ expect(() =>
116
+ assertEvent(envelope, undefined, 'value was undefined'),
117
+ ).toThrow(EventAssertionError);
118
+ expect(() => assertEvent(envelope, 0, 'value was zero')).toThrow(
119
+ EventAssertionError,
120
+ );
121
+ expect(() => assertEvent(envelope, '', 'value was empty string')).toThrow(
122
+ EventAssertionError,
123
+ );
124
+ });
125
+
126
+ it('should include envelope in thrown error', () => {
127
+ const envelope = createTestEnvelope();
128
+ try {
129
+ assertEvent(envelope, false, 'assertion failed');
130
+ expect.unreachable('should have thrown');
131
+ } catch (error) {
132
+ expect(error).toBeInstanceOf(EventAssertionError);
133
+ expect((error as EventAssertionError).envelope).toBe(envelope);
134
+ expect((error as EventAssertionError).message).toBe('assertion failed');
135
+ }
136
+ });
137
+
138
+ it('should include message in thrown error', () => {
139
+ const envelope = createTestEnvelope();
140
+ try {
141
+ assertEvent(envelope, null, 'userId is required');
142
+ expect.unreachable('should have thrown');
143
+ } catch (error) {
144
+ expect(error).toBeInstanceOf(EventAssertionError);
145
+ expect((error as EventAssertionError).message).toBe('userId is required');
146
+ }
147
+ });
148
+ });
149
+
150
+ describe('type guards', () => {
151
+ it('isDoRetry should identify DoRetry errors', () => {
152
+ expect(isDoRetry(new DoRetry('test'))).toBe(true);
153
+ expect(isDoRetry(new DontRetry('test'))).toBe(false);
154
+ expect(isDoRetry(new Error('test'))).toBe(false);
155
+ expect(isDoRetry(null)).toBe(false);
156
+ });
157
+
158
+ it('isDontRetry should identify DontRetry errors', () => {
159
+ expect(isDontRetry(new DontRetry('test'))).toBe(true);
160
+ expect(isDontRetry(new DoRetry('test'))).toBe(false);
161
+ expect(isDontRetry(new Error('test'))).toBe(false);
162
+ expect(isDontRetry(null)).toBe(false);
163
+ });
164
+
165
+ it('isAssertionError should identify EventAssertionError', () => {
166
+ const envelope = createTestEnvelope();
167
+ expect(isAssertionError(new EventAssertionError(envelope, 'test'))).toBe(
168
+ true,
169
+ );
170
+ expect(isAssertionError(new DoRetry('test'))).toBe(false);
171
+ expect(isAssertionError(new Error('test'))).toBe(false);
172
+ expect(isAssertionError(null)).toBe(false);
173
+ });
174
+ });
175
+ });
@@ -1,3 +1,6 @@
1
+ import assert from 'node:assert';
2
+ import type { Envelope } from '../types/envelope.js';
3
+
1
4
  /**
2
5
  * Base class for retry control errors.
3
6
  * These errors control the retry behavior of message processing.
@@ -76,24 +79,37 @@ export class DontRetry extends RetryControlError {
76
79
  }
77
80
 
78
81
  /**
79
- * Assertion error that should never be retried.
80
- * Use for programming errors and invariant violations.
82
+ * Thrown by `assertEvent` in the event of a failed assertion.
83
+ *
84
+ * This error indicates that an event was in an unexpected state when it reached the subscriber.
85
+ * Throwing this error will indicate to Matador NOT to retry the event - it will be sent
86
+ * directly to the undeliverable dead-letter queue.
81
87
  *
82
- * ACTION: Review the assertion failure message to identify the bug
83
- * in the event payload or subscriber logic.
88
+ * Use `assertEvent` in your subscribers to assert properties about an event payload.
89
+ * This is useful in scenarios where you would expect an event payload to contain a field
90
+ * based on the types, but something unexpected caused it to not be present.
91
+ *
92
+ * @see assertEvent
84
93
  */
85
94
  export class EventAssertionError extends Error {
86
95
  declare readonly name: string;
87
96
 
88
97
  readonly description =
89
- 'An event assertion failed, indicating a programming error or invariant violation. ' +
90
- 'ACTION: Review the assertion failure message to identify the bug in the ' +
91
- 'event payload or subscriber logic. These errors are never retried and go ' +
92
- 'directly to the dead-letter queue.';
98
+ 'Thrown to indicate that an event was in an unexpected state when it reached the subscriber. ' +
99
+ 'Throwing this error will indicate to Matador NOT to retry the event. ' +
100
+ 'Matador does not throw this error directly - it is thrown by assertEvent, which you can ' +
101
+ 'use in your subscribers to assert properties about an event. ' +
102
+ 'This can be useful in scenarios where you would expect an event payload to contain a field ' +
103
+ 'based on the types, but something unexpected caused it to not be present. ' +
104
+ 'Using this instead of the NodeJS assert allows Matador to not retry the event subscriber.';
93
105
 
94
- constructor(message: string) {
106
+ /** The envelope that failed the assertion */
107
+ readonly envelope: Envelope<unknown>;
108
+
109
+ constructor(envelope: Envelope<unknown>, message: string) {
95
110
  super(message);
96
111
  this.name = 'EventAssertionError';
112
+ this.envelope = envelope;
97
113
  Object.defineProperty(this, 'name', {
98
114
  value: 'EventAssertionError',
99
115
  enumerable: true,
@@ -107,6 +123,7 @@ export class EventAssertionError extends Error {
107
123
  name: this.name,
108
124
  message: this.message,
109
125
  description: this.description,
126
+ envelope: this.envelope,
110
127
  stack: this.stack,
111
128
  };
112
129
  }
@@ -132,3 +149,40 @@ export function isDontRetry(error: unknown): error is DontRetry {
132
149
  export function isAssertionError(error: unknown): error is EventAssertionError {
133
150
  return error instanceof EventAssertionError;
134
151
  }
152
+
153
+ /**
154
+ * Same as Node.js `assert`, but throws an `EventAssertionError` if the assertion fails.
155
+ *
156
+ * `EventAssertionError`s thrown within a subscriber do not cause the subscriber to be retried.
157
+ * The event will be delivered to the undeliverable dead-letter queue instead.
158
+ *
159
+ * @see https://nodejs.org/api/assert.html#assertvalue-message
160
+ * @param envelope - The envelope this assertion relates to.
161
+ * @param value - The value to assert. If falsy, the assertion fails.
162
+ * @param message - The message to include in the error if the assertion fails.
163
+ * @throws {EventAssertionError} If the assertion fails.
164
+ *
165
+ * @example
166
+ * ```typescript
167
+ * const subscriber = createSubscriber<MyEvent>({
168
+ * name: 'my-subscriber',
169
+ * description: 'Processes MyEvent',
170
+ * callback: async (envelope) => {
171
+ * // Assert that userId exists - if not, event goes to DLQ without retry
172
+ * assertEvent(envelope, envelope.data.userId, 'userId is required');
173
+ * // ... rest of processing
174
+ * },
175
+ * });
176
+ * ```
177
+ */
178
+ export function assertEvent(
179
+ envelope: Envelope<unknown>,
180
+ value: unknown,
181
+ message: string,
182
+ ): asserts value {
183
+ try {
184
+ assert(value, message);
185
+ } catch {
186
+ throw new EventAssertionError(envelope, message);
187
+ }
188
+ }
package/src/index.ts CHANGED
@@ -19,8 +19,10 @@ export {
19
19
  export type {
20
20
  AnySubscriber,
21
21
  BaseSubscriberOptions,
22
+ CallbackContext,
22
23
  CreateEnvelopeOptions,
23
24
  DeliveryMode,
25
+ Dispatcher,
24
26
  Docket,
25
27
  Envelope,
26
28
  EnvelopeOf,
@@ -33,6 +35,9 @@ export type {
33
35
  Idempotency,
34
36
  Importance,
35
37
  JsonRecord,
38
+ ResumableCallback,
39
+ ResumableCallbackContext,
40
+ StandardCallback,
36
41
  Subscriber,
37
42
  SubscriberCallback,
38
43
  SubscriberDefinition,
@@ -43,6 +48,7 @@ export type {
43
48
  } from './types/index.js';
44
49
  export {
45
50
  MatadorEvent,
51
+ createDummyEnvelope,
46
52
  createEnvelope,
47
53
  createSubscriber,
48
54
  createSubscriberStub,
@@ -91,13 +97,17 @@ export type {
91
97
  DeadLetterQueueConfig,
92
98
  QueueDefinition,
93
99
  QueueOptions,
100
+ RabbitMQQueueDefinition,
101
+ RabbitMQQueueOptions,
94
102
  RetryConfig,
95
103
  Topology,
104
+ TransportQueueOptions,
96
105
  } from './topology/index.js';
97
106
  export {
98
107
  getDeadLetterQueueName,
99
108
  getQualifiedQueueName,
100
109
  getRetryQueueName,
110
+ resolveQueueName,
101
111
  TopologyBuilder,
102
112
  TopologyValidationError,
103
113
  } from './topology/index.js';
@@ -162,6 +172,7 @@ export { ProcessingPipeline } from './pipeline/index.js';
162
172
  export type { HasDescription } from './errors/index.js';
163
173
  export {
164
174
  // Retry control errors
175
+ assertEvent,
165
176
  DontRetry,
166
177
  DoRetry,
167
178
  EventAssertionError,
@@ -10,7 +10,11 @@ import type { RetryDecision, RetryPolicy } from '../retry/index.js';
10
10
  import { StandardRetryPolicy } from '../retry/index.js';
11
11
  import type { SchemaRegistry } from '../schema/index.js';
12
12
  import type { MessageReceipt, Transport } from '../transport/index.js';
13
- import type { Envelope, SubscriberDefinition } from '../types/index.js';
13
+ import type {
14
+ Dispatcher,
15
+ Envelope,
16
+ SubscriberDefinition,
17
+ } from '../types/index.js';
14
18
  import { createEnvelope } from '../types/index.js';
15
19
  import type { PipelineConfig } from './pipeline.js';
16
20
  import { ProcessingPipeline } from './pipeline.js';
@@ -324,7 +328,13 @@ describe('ProcessingPipeline', () => {
324
328
  const result = await pipeline.process(new Uint8Array(), receipt);
325
329
 
326
330
  expect(result.success).toBe(true);
327
- expect(callbackMock).toHaveBeenCalledWith(envelope);
331
+ expect(callbackMock).toHaveBeenCalledTimes(1);
332
+ // Callback receives envelope as first argument and context (with matador) as second
333
+ const calls = callbackMock.mock.calls as unknown as [
334
+ [Envelope, { matador: Dispatcher }],
335
+ ];
336
+ expect(calls[0][0]).toEqual(envelope);
337
+ expect(calls[0][1]).toHaveProperty('matador');
328
338
  });
329
339
 
330
340
  it('should handle callback throwing error', async () => {
@@ -532,6 +542,44 @@ describe('ProcessingPipeline', () => {
532
542
  });
533
543
  });
534
544
 
545
+ it('should pass subscriber return value to onWorkerSuccess', async () => {
546
+ const envelope = createTestEnvelope();
547
+ const subscriberDef = createSubscriberDefinition();
548
+ const onWorkerSuccessMock = mock(async () => {});
549
+ const returnValue = { processed: true, count: 42 };
550
+
551
+ const config = createMockConfig({
552
+ codec: {
553
+ decode: mock(() => envelope),
554
+ },
555
+ schema: {
556
+ getSubscriberDefinition: mock(() => subscriberDef),
557
+ getExecutableSubscriber: mock(() => ({
558
+ name: 'test-subscriber',
559
+ description: 'Test subscriber',
560
+ idempotent: 'unknown' as const,
561
+ callback: mock(async () => returnValue),
562
+ })),
563
+ },
564
+ hooks: {
565
+ onWorkerSuccess: onWorkerSuccessMock,
566
+ },
567
+ });
568
+
569
+ const pipeline = new ProcessingPipeline(config);
570
+ const receipt = createReceipt();
571
+
572
+ await pipeline.process(new Uint8Array(), receipt);
573
+
574
+ expect(onWorkerSuccessMock).toHaveBeenCalledWith({
575
+ envelope,
576
+ subscriber: subscriberDef,
577
+ result: returnValue,
578
+ durationMs: expect.any(Number),
579
+ transport: 'mock',
580
+ });
581
+ });
582
+
535
583
  it('should call onWorkerError after failed callback', async () => {
536
584
  const envelope = createTestEnvelope();
537
585
  const subscriberDef = createSubscriberDefinition();
@@ -1189,6 +1237,7 @@ function createMockConfig(
1189
1237
  codec: Partial<Codec>;
1190
1238
  retryPolicy: RetryPolicy | Partial<RetryPolicy>;
1191
1239
  hooks: Partial<SafeHooks>;
1240
+ dispatcher: Partial<Dispatcher>;
1192
1241
  }> = {},
1193
1242
  ): PipelineConfig {
1194
1243
  const defaultTransport: Transport = {
@@ -1272,12 +1321,23 @@ function createMockConfig(
1272
1321
  ...overrides.hooks,
1273
1322
  } as SafeHooks;
1274
1323
 
1324
+ const defaultDispatcher: Dispatcher = {
1325
+ send: mock(async () => ({
1326
+ eventKey: 'test.event',
1327
+ subscribersSent: 0,
1328
+ subscribersSkipped: 0,
1329
+ errors: [],
1330
+ })),
1331
+ ...overrides.dispatcher,
1332
+ } as Dispatcher;
1333
+
1275
1334
  return {
1276
1335
  transport: defaultTransport,
1277
1336
  schema: { ...defaultSchema, ...overrides.schema } as SchemaRegistry,
1278
1337
  codec: defaultCodec,
1279
1338
  retryPolicy: defaultRetryPolicy,
1280
1339
  hooks: defaultHooks,
1340
+ dispatcher: defaultDispatcher,
1281
1341
  };
1282
1342
  }
1283
1343