@zdavison/matador 2.0.4 → 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 (45) hide show
  1. package/dist/core/fanout.test.js +0 -1
  2. package/dist/errors/index.d.ts +1 -1
  3. package/dist/errors/index.d.ts.map +1 -1
  4. package/dist/errors/index.js +1 -1
  5. package/dist/errors/retry-errors.d.ts +40 -5
  6. package/dist/errors/retry-errors.d.ts.map +1 -1
  7. package/dist/errors/retry-errors.js +56 -9
  8. package/dist/errors/retry-errors.test.d.ts +2 -0
  9. package/dist/errors/retry-errors.test.d.ts.map +1 -0
  10. package/dist/errors/retry-errors.test.js +136 -0
  11. package/dist/index.cjs +29 -7
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.cts +2 -2
  14. package/dist/index.d.ts +2 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -2
  17. package/dist/index.js.map +1 -1
  18. package/dist/pipeline/pipeline.test.js +33 -0
  19. package/dist/retry/standard-policy.test.js +7 -1
  20. package/dist/schema/registry.d.ts.map +1 -1
  21. package/dist/schema/registry.js +4 -1
  22. package/dist/schema/types.test.js +0 -1
  23. package/dist/types/envelope.d.ts +17 -0
  24. package/dist/types/envelope.d.ts.map +1 -1
  25. package/dist/types/envelope.js +19 -0
  26. package/dist/types/index.d.ts +1 -1
  27. package/dist/types/index.d.ts.map +1 -1
  28. package/dist/types/index.js +1 -1
  29. package/dist/types/subscriber.d.ts +10 -6
  30. package/dist/types/subscriber.d.ts.map +1 -1
  31. package/dist/types/subscriber.js +0 -2
  32. package/package.json +1 -1
  33. package/src/core/fanout.test.ts +0 -1
  34. package/src/errors/index.ts +1 -0
  35. package/src/errors/retry-errors.test.ts +175 -0
  36. package/src/errors/retry-errors.ts +63 -9
  37. package/src/index.ts +2 -0
  38. package/src/pipeline/pipeline.test.ts +38 -0
  39. package/src/retry/standard-policy.test.ts +9 -1
  40. package/src/schema/registry.ts +4 -1
  41. package/src/schema/types.test.ts +0 -1
  42. package/src/types/envelope.ts +23 -0
  43. package/src/types/index.ts +1 -1
  44. package/src/types/subscriber.ts +11 -7
  45. package/tsconfig.tsbuildinfo +1 -1
@@ -489,7 +489,6 @@ describe('FanoutEngine', () => {
489
489
  it('should work with subscriber stubs', async () => {
490
490
  const stub = createSubscriberStub({
491
491
  name: 'remote-subscriber',
492
- description: 'Remote subscriber stub',
493
492
  enabled: () => false,
494
493
  });
495
494
  schema.register(UserCreatedEvent, [stub]);
@@ -1,6 +1,6 @@
1
1
  export type { HasDescription } from './has-description.js';
2
2
  export { hasDescription } from './has-description.js';
3
- export { DontRetry, DoRetry, EventAssertionError, isAssertionError, isDontRetry, isDoRetry, RetryControlError, } from './retry-errors.js';
3
+ export { assertEvent, DontRetry, DoRetry, EventAssertionError, isAssertionError, isDontRetry, isDoRetry, RetryControlError, } from './retry-errors.js';
4
4
  export { MatadorError, isMatadorError, NotStartedError, isNotStartedError, ShutdownInProgressError, TransportNotConnectedError, isTransportNotConnectedError, TransportClosedError, TransportSendError, AllTransportsFailedError, DelayedMessagesNotSupportedError, EventNotRegisteredError, isEventNotRegisteredError, SubscriberNotRegisteredError, isSubscriberNotRegisteredError, NoSubscribersExistError, InvalidSchemaError, SubscriberIsStubError, LocalTransportCannotProcessStubError, QueueNotFoundError, InvalidEventError, MessageMaybePoisonedError, isMessageMaybePoisonedError, IdempotentMessageCannotRetryError, isIdempotentMessageCannotRetryError, TimeoutError, } from './matador-errors.js';
5
5
  export { CheckpointStoreError, DuplicateIoKeyError, isCheckpointStoreError, isDuplicateIoKeyError, } from './checkpoint-errors.js';
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EACL,SAAS,EACT,OAAO,EACP,mBAAmB,EACnB,gBAAgB,EAChB,WAAW,EACX,SAAS,EACT,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAEL,YAAY,EACZ,cAAc,EAEd,eAAe,EACf,iBAAiB,EACjB,uBAAuB,EAEvB,0BAA0B,EAC1B,4BAA4B,EAC5B,oBAAoB,EACpB,kBAAkB,EAClB,wBAAwB,EACxB,gCAAgC,EAEhC,uBAAuB,EACvB,yBAAyB,EACzB,4BAA4B,EAC5B,8BAA8B,EAC9B,uBAAuB,EACvB,kBAAkB,EAClB,qBAAqB,EACrB,oCAAoC,EAEpC,kBAAkB,EAElB,iBAAiB,EAEjB,yBAAyB,EACzB,2BAA2B,EAC3B,iCAAiC,EACjC,mCAAmC,EAEnC,YAAY,GACb,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAEL,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EACL,WAAW,EACX,SAAS,EACT,OAAO,EACP,mBAAmB,EACnB,gBAAgB,EAChB,WAAW,EACX,SAAS,EACT,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAEL,YAAY,EACZ,cAAc,EAEd,eAAe,EACf,iBAAiB,EACjB,uBAAuB,EAEvB,0BAA0B,EAC1B,4BAA4B,EAC5B,oBAAoB,EACpB,kBAAkB,EAClB,wBAAwB,EACxB,gCAAgC,EAEhC,uBAAuB,EACvB,yBAAyB,EACzB,4BAA4B,EAC5B,8BAA8B,EAC9B,uBAAuB,EACvB,kBAAkB,EAClB,qBAAqB,EACrB,oCAAoC,EAEpC,kBAAkB,EAElB,iBAAiB,EAEjB,yBAAyB,EACzB,2BAA2B,EAC3B,iCAAiC,EACjC,mCAAmC,EAEnC,YAAY,GACb,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAEL,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC"}
@@ -1,5 +1,5 @@
1
1
  export { hasDescription } from './has-description.js';
2
- export { DontRetry, DoRetry, EventAssertionError, isAssertionError, isDontRetry, isDoRetry, RetryControlError, } from './retry-errors.js';
2
+ export { assertEvent, DontRetry, DoRetry, EventAssertionError, isAssertionError, isDontRetry, isDoRetry, RetryControlError, } from './retry-errors.js';
3
3
  export {
4
4
  // Base class
5
5
  MatadorError, isMatadorError,
@@ -1,3 +1,4 @@
1
+ import type { Envelope } from '../types/envelope.js';
1
2
  /**
2
3
  * Base class for retry control errors.
3
4
  * These errors control the retry behavior of message processing.
@@ -40,16 +41,24 @@ export declare class DontRetry extends RetryControlError {
40
41
  constructor(message?: string);
41
42
  }
42
43
  /**
43
- * Assertion error that should never be retried.
44
- * Use for programming errors and invariant violations.
44
+ * Thrown by `assertEvent` in the event of a failed assertion.
45
45
  *
46
- * ACTION: Review the assertion failure message to identify the bug
47
- * in the event payload or subscriber logic.
46
+ * This error indicates that an event was in an unexpected state when it reached the subscriber.
47
+ * Throwing this error will indicate to Matador NOT to retry the event - it will be sent
48
+ * directly to the undeliverable dead-letter queue.
49
+ *
50
+ * Use `assertEvent` in your subscribers to assert properties about an event payload.
51
+ * This is useful in scenarios where you would expect an event payload to contain a field
52
+ * based on the types, but something unexpected caused it to not be present.
53
+ *
54
+ * @see assertEvent
48
55
  */
49
56
  export declare class EventAssertionError extends Error {
50
57
  readonly name: string;
51
58
  readonly description: string;
52
- constructor(message: string);
59
+ /** The envelope that failed the assertion */
60
+ readonly envelope: Envelope<unknown>;
61
+ constructor(envelope: Envelope<unknown>, message: string);
53
62
  toJSON(): Record<string, unknown>;
54
63
  }
55
64
  /**
@@ -64,4 +73,30 @@ export declare function isDontRetry(error: unknown): error is DontRetry;
64
73
  * Checks if an error is an assertion error (never retry).
65
74
  */
66
75
  export declare function isAssertionError(error: unknown): error is EventAssertionError;
76
+ /**
77
+ * Same as Node.js `assert`, but throws an `EventAssertionError` if the assertion fails.
78
+ *
79
+ * `EventAssertionError`s thrown within a subscriber do not cause the subscriber to be retried.
80
+ * The event will be delivered to the undeliverable dead-letter queue instead.
81
+ *
82
+ * @see https://nodejs.org/api/assert.html#assertvalue-message
83
+ * @param envelope - The envelope this assertion relates to.
84
+ * @param value - The value to assert. If falsy, the assertion fails.
85
+ * @param message - The message to include in the error if the assertion fails.
86
+ * @throws {EventAssertionError} If the assertion fails.
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * const subscriber = createSubscriber<MyEvent>({
91
+ * name: 'my-subscriber',
92
+ * description: 'Processes MyEvent',
93
+ * callback: async (envelope) => {
94
+ * // Assert that userId exists - if not, event goes to DLQ without retry
95
+ * assertEvent(envelope, envelope.data.userId, 'userId is required');
96
+ * // ... rest of processing
97
+ * },
98
+ * });
99
+ * ```
100
+ */
101
+ export declare function assertEvent(envelope: Envelope<unknown>, value: unknown, message: string): asserts value;
67
102
  //# sourceMappingURL=retry-errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"retry-errors.d.ts","sourceRoot":"","sources":["../../src/errors/retry-errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,8BAAsB,iBAAkB,SAAQ,KAAK;IACnD;;OAEG;IACH,SAAiB,IAAI,EAAE,MAAM,CAAC;IAE9B;;OAEG;IACH,QAAQ,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAE1B,OAAO,EAAE,MAAM;IAY3B;;OAEG;IACH,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAQlC;AAED;;;;;;GAMG;AACH,qBAAa,OAAQ,SAAQ,iBAAiB;IAC5C,QAAQ,CAAC,WAAW,SAGyD;gBAEjE,OAAO,SAA2B;CAG/C;AAED;;;;;;GAMG;AACH,qBAAa,SAAU,SAAQ,iBAAiB;IAC9C,QAAQ,CAAC,WAAW,SAIqD;gBAE7D,OAAO,SAA8B;CAGlD;AAED;;;;;;GAMG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,SAAiB,IAAI,EAAE,MAAM,CAAC;IAE9B,QAAQ,CAAC,WAAW,SAImB;gBAE3B,OAAO,EAAE,MAAM;IAW3B,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAQlC;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,CAE1D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAE9D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,mBAAmB,CAE7E"}
1
+ {"version":3,"file":"retry-errors.d.ts","sourceRoot":"","sources":["../../src/errors/retry-errors.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD;;;GAGG;AACH,8BAAsB,iBAAkB,SAAQ,KAAK;IACnD;;OAEG;IACH,SAAiB,IAAI,EAAE,MAAM,CAAC;IAE9B;;OAEG;IACH,QAAQ,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAE1B,OAAO,EAAE,MAAM;IAY3B;;OAEG;IACH,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAQlC;AAED;;;;;;GAMG;AACH,qBAAa,OAAQ,SAAQ,iBAAiB;IAC5C,QAAQ,CAAC,WAAW,SAGyD;gBAEjE,OAAO,SAA2B;CAG/C;AAED;;;;;;GAMG;AACH,qBAAa,SAAU,SAAQ,iBAAiB;IAC9C,QAAQ,CAAC,WAAW,SAIqD;gBAE7D,OAAO,SAA8B;CAGlD;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,SAAiB,IAAI,EAAE,MAAM,CAAC;IAE9B,QAAQ,CAAC,WAAW,SAO0E;IAE9F,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;gBAEzB,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM;IAYxD,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CASlC;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,CAE1D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAE9D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,mBAAmB,CAE7E;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,EAC3B,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,KAAK,CAMf"}
@@ -1,3 +1,4 @@
1
+ import assert from 'node:assert';
1
2
  /**
2
3
  * Base class for retry control errors.
3
4
  * These errors control the retry behavior of message processing.
@@ -58,20 +59,32 @@ export class DontRetry extends RetryControlError {
58
59
  }
59
60
  }
60
61
  /**
61
- * Assertion error that should never be retried.
62
- * Use for programming errors and invariant violations.
62
+ * Thrown by `assertEvent` in the event of a failed assertion.
63
63
  *
64
- * ACTION: Review the assertion failure message to identify the bug
65
- * in the event payload or subscriber logic.
64
+ * This error indicates that an event was in an unexpected state when it reached the subscriber.
65
+ * Throwing this error will indicate to Matador NOT to retry the event - it will be sent
66
+ * directly to the undeliverable dead-letter queue.
67
+ *
68
+ * Use `assertEvent` in your subscribers to assert properties about an event payload.
69
+ * This is useful in scenarios where you would expect an event payload to contain a field
70
+ * based on the types, but something unexpected caused it to not be present.
71
+ *
72
+ * @see assertEvent
66
73
  */
67
74
  export class EventAssertionError extends Error {
68
- description = 'An event assertion failed, indicating a programming error or invariant violation. ' +
69
- 'ACTION: Review the assertion failure message to identify the bug in the ' +
70
- 'event payload or subscriber logic. These errors are never retried and go ' +
71
- 'directly to the dead-letter queue.';
72
- constructor(message) {
75
+ description = 'Thrown to indicate that an event was in an unexpected state when it reached the subscriber. ' +
76
+ 'Throwing this error will indicate to Matador NOT to retry the event. ' +
77
+ 'Matador does not throw this error directly - it is thrown by assertEvent, which you can ' +
78
+ 'use in your subscribers to assert properties about an event. ' +
79
+ 'This can be useful in scenarios where you would expect an event payload to contain a field ' +
80
+ 'based on the types, but something unexpected caused it to not be present. ' +
81
+ 'Using this instead of the NodeJS assert allows Matador to not retry the event subscriber.';
82
+ /** The envelope that failed the assertion */
83
+ envelope;
84
+ constructor(envelope, message) {
73
85
  super(message);
74
86
  this.name = 'EventAssertionError';
87
+ this.envelope = envelope;
75
88
  Object.defineProperty(this, 'name', {
76
89
  value: 'EventAssertionError',
77
90
  enumerable: true,
@@ -84,6 +97,7 @@ export class EventAssertionError extends Error {
84
97
  name: this.name,
85
98
  message: this.message,
86
99
  description: this.description,
100
+ envelope: this.envelope,
87
101
  stack: this.stack,
88
102
  };
89
103
  }
@@ -106,3 +120,36 @@ export function isDontRetry(error) {
106
120
  export function isAssertionError(error) {
107
121
  return error instanceof EventAssertionError;
108
122
  }
123
+ /**
124
+ * Same as Node.js `assert`, but throws an `EventAssertionError` if the assertion fails.
125
+ *
126
+ * `EventAssertionError`s thrown within a subscriber do not cause the subscriber to be retried.
127
+ * The event will be delivered to the undeliverable dead-letter queue instead.
128
+ *
129
+ * @see https://nodejs.org/api/assert.html#assertvalue-message
130
+ * @param envelope - The envelope this assertion relates to.
131
+ * @param value - The value to assert. If falsy, the assertion fails.
132
+ * @param message - The message to include in the error if the assertion fails.
133
+ * @throws {EventAssertionError} If the assertion fails.
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * const subscriber = createSubscriber<MyEvent>({
138
+ * name: 'my-subscriber',
139
+ * description: 'Processes MyEvent',
140
+ * callback: async (envelope) => {
141
+ * // Assert that userId exists - if not, event goes to DLQ without retry
142
+ * assertEvent(envelope, envelope.data.userId, 'userId is required');
143
+ * // ... rest of processing
144
+ * },
145
+ * });
146
+ * ```
147
+ */
148
+ export function assertEvent(envelope, value, message) {
149
+ try {
150
+ assert(value, message);
151
+ }
152
+ catch {
153
+ throw new EventAssertionError(envelope, message);
154
+ }
155
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=retry-errors.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry-errors.test.d.ts","sourceRoot":"","sources":["../../src/errors/retry-errors.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,136 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+ import { createEnvelope } from '../types/envelope.js';
3
+ import { assertEvent, DontRetry, DoRetry, EventAssertionError, isAssertionError, isDontRetry, isDoRetry, } from './retry-errors.js';
4
+ function createTestEnvelope(data = { foo: 'bar' }) {
5
+ return createEnvelope({
6
+ data,
7
+ eventKey: 'test.event',
8
+ targetSubscriber: 'test-subscriber',
9
+ importance: 'can-ignore',
10
+ });
11
+ }
12
+ describe('retry-errors', () => {
13
+ describe('DoRetry', () => {
14
+ it('should have correct name', () => {
15
+ const error = new DoRetry('test message');
16
+ expect(error.name).toBe('DoRetry');
17
+ });
18
+ it('should have description', () => {
19
+ const error = new DoRetry('test message');
20
+ expect(error.description).toBeDefined();
21
+ });
22
+ it('should serialize to JSON', () => {
23
+ const error = new DoRetry('test message');
24
+ const json = error.toJSON();
25
+ expect(json.name).toBe('DoRetry');
26
+ expect(json.message).toBe('test message');
27
+ expect(json.description).toBeDefined();
28
+ });
29
+ });
30
+ describe('DontRetry', () => {
31
+ it('should have correct name', () => {
32
+ const error = new DontRetry('test message');
33
+ expect(error.name).toBe('DontRetry');
34
+ });
35
+ it('should have description', () => {
36
+ const error = new DontRetry('test message');
37
+ expect(error.description).toBeDefined();
38
+ });
39
+ it('should serialize to JSON', () => {
40
+ const error = new DontRetry('test message');
41
+ const json = error.toJSON();
42
+ expect(json.name).toBe('DontRetry');
43
+ expect(json.message).toBe('test message');
44
+ expect(json.description).toBeDefined();
45
+ });
46
+ });
47
+ describe('EventAssertionError', () => {
48
+ it('should have correct name', () => {
49
+ const envelope = createTestEnvelope();
50
+ const error = new EventAssertionError(envelope, 'assertion failed');
51
+ expect(error.name).toBe('EventAssertionError');
52
+ });
53
+ it('should store the envelope', () => {
54
+ const envelope = createTestEnvelope();
55
+ const error = new EventAssertionError(envelope, 'assertion failed');
56
+ expect(error.envelope).toBe(envelope);
57
+ });
58
+ it('should have description', () => {
59
+ const envelope = createTestEnvelope();
60
+ const error = new EventAssertionError(envelope, 'assertion failed');
61
+ expect(error.description).toBeDefined();
62
+ expect(error.description).toContain('NOT to retry');
63
+ });
64
+ it('should serialize to JSON with envelope', () => {
65
+ const envelope = createTestEnvelope();
66
+ const error = new EventAssertionError(envelope, 'assertion failed');
67
+ const json = error.toJSON();
68
+ expect(json.name).toBe('EventAssertionError');
69
+ expect(json.message).toBe('assertion failed');
70
+ expect(json.envelope).toBe(envelope);
71
+ expect(json.description).toBeDefined();
72
+ });
73
+ });
74
+ describe('assertEvent', () => {
75
+ it('should not throw when value is truthy', () => {
76
+ const envelope = createTestEnvelope();
77
+ expect(() => assertEvent(envelope, true, 'should be true')).not.toThrow();
78
+ expect(() => assertEvent(envelope, 'string', 'should be string')).not.toThrow();
79
+ expect(() => assertEvent(envelope, 1, 'should be number')).not.toThrow();
80
+ expect(() => assertEvent(envelope, {}, 'should be object')).not.toThrow();
81
+ expect(() => assertEvent(envelope, [], 'should be array')).not.toThrow();
82
+ });
83
+ it('should throw EventAssertionError when value is falsy', () => {
84
+ const envelope = createTestEnvelope();
85
+ expect(() => assertEvent(envelope, false, 'value was false')).toThrow(EventAssertionError);
86
+ expect(() => assertEvent(envelope, null, 'value was null')).toThrow(EventAssertionError);
87
+ expect(() => assertEvent(envelope, undefined, 'value was undefined')).toThrow(EventAssertionError);
88
+ expect(() => assertEvent(envelope, 0, 'value was zero')).toThrow(EventAssertionError);
89
+ expect(() => assertEvent(envelope, '', 'value was empty string')).toThrow(EventAssertionError);
90
+ });
91
+ it('should include envelope in thrown error', () => {
92
+ const envelope = createTestEnvelope();
93
+ try {
94
+ assertEvent(envelope, false, 'assertion failed');
95
+ expect.unreachable('should have thrown');
96
+ }
97
+ catch (error) {
98
+ expect(error).toBeInstanceOf(EventAssertionError);
99
+ expect(error.envelope).toBe(envelope);
100
+ expect(error.message).toBe('assertion failed');
101
+ }
102
+ });
103
+ it('should include message in thrown error', () => {
104
+ const envelope = createTestEnvelope();
105
+ try {
106
+ assertEvent(envelope, null, 'userId is required');
107
+ expect.unreachable('should have thrown');
108
+ }
109
+ catch (error) {
110
+ expect(error).toBeInstanceOf(EventAssertionError);
111
+ expect(error.message).toBe('userId is required');
112
+ }
113
+ });
114
+ });
115
+ describe('type guards', () => {
116
+ it('isDoRetry should identify DoRetry errors', () => {
117
+ expect(isDoRetry(new DoRetry('test'))).toBe(true);
118
+ expect(isDoRetry(new DontRetry('test'))).toBe(false);
119
+ expect(isDoRetry(new Error('test'))).toBe(false);
120
+ expect(isDoRetry(null)).toBe(false);
121
+ });
122
+ it('isDontRetry should identify DontRetry errors', () => {
123
+ expect(isDontRetry(new DontRetry('test'))).toBe(true);
124
+ expect(isDontRetry(new DoRetry('test'))).toBe(false);
125
+ expect(isDontRetry(new Error('test'))).toBe(false);
126
+ expect(isDontRetry(null)).toBe(false);
127
+ });
128
+ it('isAssertionError should identify EventAssertionError', () => {
129
+ const envelope = createTestEnvelope();
130
+ expect(isAssertionError(new EventAssertionError(envelope, 'test'))).toBe(true);
131
+ expect(isAssertionError(new DoRetry('test'))).toBe(false);
132
+ expect(isAssertionError(new Error('test'))).toBe(false);
133
+ expect(isAssertionError(null)).toBe(false);
134
+ });
135
+ });
136
+ });
package/dist/index.cjs CHANGED
@@ -1,17 +1,17 @@
1
1
  'use strict';
2
2
 
3
+ var assert = require('assert');
3
4
  var amqplib = require('amqplib');
4
5
 
5
6
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
6
7
 
8
+ var assert__default = /*#__PURE__*/_interopDefault(assert);
7
9
  var amqplib__default = /*#__PURE__*/_interopDefault(amqplib);
8
10
 
9
11
  // src/errors/has-description.ts
10
12
  function hasDescription(error) {
11
13
  return typeof error === "object" && error !== null && "description" in error && typeof error.description === "string";
12
14
  }
13
-
14
- // src/errors/retry-errors.ts
15
15
  var RetryControlError = class extends Error {
16
16
  constructor(message) {
17
17
  super(message);
@@ -48,10 +48,13 @@ var DontRetry = class extends RetryControlError {
48
48
  }
49
49
  };
50
50
  var EventAssertionError = class extends Error {
51
- description = "An event assertion failed, indicating a programming error or invariant violation. ACTION: Review the assertion failure message to identify the bug in the event payload or subscriber logic. These errors are never retried and go directly to the dead-letter queue.";
52
- constructor(message) {
51
+ description = "Thrown to indicate that an event was in an unexpected state when it reached the subscriber. Throwing this error will indicate to Matador NOT to retry the event. Matador does not throw this error directly - it is thrown by assertEvent, which you can use in your subscribers to assert properties about an event. This can be useful in scenarios where you would expect an event payload to contain a field based on the types, but something unexpected caused it to not be present. Using this instead of the NodeJS assert allows Matador to not retry the event subscriber.";
52
+ /** The envelope that failed the assertion */
53
+ envelope;
54
+ constructor(envelope, message) {
53
55
  super(message);
54
56
  this.name = "EventAssertionError";
57
+ this.envelope = envelope;
55
58
  Object.defineProperty(this, "name", {
56
59
  value: "EventAssertionError",
57
60
  enumerable: true,
@@ -64,6 +67,7 @@ var EventAssertionError = class extends Error {
64
67
  name: this.name,
65
68
  message: this.message,
66
69
  description: this.description,
70
+ envelope: this.envelope,
67
71
  stack: this.stack
68
72
  };
69
73
  }
@@ -77,6 +81,13 @@ function isDontRetry(error) {
77
81
  function isAssertionError(error) {
78
82
  return error instanceof EventAssertionError;
79
83
  }
84
+ function assertEvent(envelope, value, message) {
85
+ try {
86
+ assert__default.default(value, message);
87
+ } catch {
88
+ throw new EventAssertionError(envelope, message);
89
+ }
90
+ }
80
91
 
81
92
  // src/errors/matador-errors.ts
82
93
  var MatadorError = class extends Error {
@@ -534,6 +545,14 @@ function createEnvelope(options) {
534
545
  }
535
546
  };
536
547
  }
548
+ function createDummyEnvelope(event) {
549
+ return createEnvelope({
550
+ data: event.data,
551
+ eventKey: event.constructor.key,
552
+ targetSubscriber: "dummy-subscriber",
553
+ importance: "can-ignore"
554
+ });
555
+ }
537
556
 
538
557
  // src/types/event.ts
539
558
  var MatadorEvent = class {
@@ -581,7 +600,6 @@ function createSubscriber(input) {
581
600
  function createSubscriberStub(input) {
582
601
  return {
583
602
  name: input.name,
584
- description: input.description,
585
603
  isStub: true,
586
604
  idempotent: input.idempotent ?? "unknown",
587
605
  importance: input.importance ?? "should-investigate",
@@ -1834,9 +1852,11 @@ var SchemaRegistry = class {
1834
1852
  if (!subscriber) return void 0;
1835
1853
  const def = {
1836
1854
  name: subscriber.name,
1837
- description: subscriber.description,
1838
1855
  idempotent: subscriber.idempotent ?? "unknown",
1839
- importance: subscriber.importance ?? "should-investigate"
1856
+ importance: subscriber.importance ?? "should-investigate",
1857
+ ..."description" in subscriber && subscriber.description !== void 0 && {
1858
+ description: subscriber.description
1859
+ }
1840
1860
  };
1841
1861
  if (subscriber.targetQueue !== void 0) {
1842
1862
  def.targetQueue = subscriber.targetQueue;
@@ -3166,8 +3186,10 @@ exports.TopologyValidationError = TopologyValidationError;
3166
3186
  exports.TransportClosedError = TransportClosedError;
3167
3187
  exports.TransportNotConnectedError = TransportNotConnectedError;
3168
3188
  exports.TransportSendError = TransportSendError;
3189
+ exports.assertEvent = assertEvent;
3169
3190
  exports.bind = bind;
3170
3191
  exports.consoleLogger = consoleLogger;
3192
+ exports.createDummyEnvelope = createDummyEnvelope;
3171
3193
  exports.createEnvelope = createEnvelope;
3172
3194
  exports.createSubscriber = createSubscriber;
3173
3195
  exports.createSubscriberStub = createSubscriberStub;