@zdavison/matador 2.0.4 → 2.0.6
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/core/fanout.test.js +0 -1
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/errors/retry-errors.d.ts +40 -5
- package/dist/errors/retry-errors.d.ts.map +1 -1
- package/dist/errors/retry-errors.js +56 -9
- package/dist/errors/retry-errors.test.d.ts +2 -0
- package/dist/errors/retry-errors.test.d.ts.map +1 -0
- package/dist/errors/retry-errors.test.js +136 -0
- package/dist/index.cjs +29 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/pipeline/pipeline.test.js +33 -0
- package/dist/retry/standard-policy.test.js +7 -1
- package/dist/schema/registry.d.ts.map +1 -1
- package/dist/schema/registry.js +4 -1
- package/dist/schema/types.test.js +0 -1
- package/dist/types/envelope.d.ts +12 -0
- package/dist/types/envelope.d.ts.map +1 -1
- package/dist/types/envelope.js +19 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types/subscriber.d.ts +10 -6
- package/dist/types/subscriber.d.ts.map +1 -1
- package/dist/types/subscriber.js +0 -2
- package/package.json +1 -1
- package/src/core/fanout.test.ts +0 -1
- package/src/errors/index.ts +1 -0
- package/src/errors/retry-errors.test.ts +175 -0
- package/src/errors/retry-errors.ts +63 -9
- package/src/index.ts +2 -0
- package/src/pipeline/pipeline.test.ts +38 -0
- package/src/retry/standard-policy.test.ts +9 -1
- package/src/schema/registry.ts +4 -1
- package/src/schema/types.test.ts +0 -1
- package/src/types/envelope.ts +20 -0
- package/src/types/index.ts +1 -1
- package/src/types/subscriber.ts +11 -7
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -426,6 +426,39 @@ describe('ProcessingPipeline', () => {
|
|
|
426
426
|
transport: 'mock',
|
|
427
427
|
});
|
|
428
428
|
});
|
|
429
|
+
it('should pass subscriber return value to onWorkerSuccess', async () => {
|
|
430
|
+
const envelope = createTestEnvelope();
|
|
431
|
+
const subscriberDef = createSubscriberDefinition();
|
|
432
|
+
const onWorkerSuccessMock = mock(async () => { });
|
|
433
|
+
const returnValue = { processed: true, count: 42 };
|
|
434
|
+
const config = createMockConfig({
|
|
435
|
+
codec: {
|
|
436
|
+
decode: mock(() => envelope),
|
|
437
|
+
},
|
|
438
|
+
schema: {
|
|
439
|
+
getSubscriberDefinition: mock(() => subscriberDef),
|
|
440
|
+
getExecutableSubscriber: mock(() => ({
|
|
441
|
+
name: 'test-subscriber',
|
|
442
|
+
description: 'Test subscriber',
|
|
443
|
+
idempotent: 'unknown',
|
|
444
|
+
callback: mock(async () => returnValue),
|
|
445
|
+
})),
|
|
446
|
+
},
|
|
447
|
+
hooks: {
|
|
448
|
+
onWorkerSuccess: onWorkerSuccessMock,
|
|
449
|
+
},
|
|
450
|
+
});
|
|
451
|
+
const pipeline = new ProcessingPipeline(config);
|
|
452
|
+
const receipt = createReceipt();
|
|
453
|
+
await pipeline.process(new Uint8Array(), receipt);
|
|
454
|
+
expect(onWorkerSuccessMock).toHaveBeenCalledWith({
|
|
455
|
+
envelope,
|
|
456
|
+
subscriber: subscriberDef,
|
|
457
|
+
result: returnValue,
|
|
458
|
+
durationMs: expect.any(Number),
|
|
459
|
+
transport: 'mock',
|
|
460
|
+
});
|
|
461
|
+
});
|
|
429
462
|
it('should call onWorkerError after failed callback', async () => {
|
|
430
463
|
const envelope = createTestEnvelope();
|
|
431
464
|
const subscriberDef = createSubscriberDefinition();
|
|
@@ -6,7 +6,13 @@ describe('StandardRetryPolicy', () => {
|
|
|
6
6
|
describe('shouldRetry', () => {
|
|
7
7
|
it('should dead-letter on EventAssertionError', () => {
|
|
8
8
|
const policy = new StandardRetryPolicy();
|
|
9
|
-
const
|
|
9
|
+
const envelope = createEnvelope({
|
|
10
|
+
eventKey: 'test.event',
|
|
11
|
+
targetSubscriber: 'test-subscriber',
|
|
12
|
+
data: { test: 'data' },
|
|
13
|
+
importance: 'should-investigate',
|
|
14
|
+
});
|
|
15
|
+
const context = createContext(new EventAssertionError(envelope, 'Invalid event'));
|
|
10
16
|
const decision = policy.shouldRetry(context);
|
|
11
17
|
expect(decision.action).toBe('dead-letter');
|
|
12
18
|
assertDeadLetter(decision);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/schema/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EACV,YAAY,EACZ,UAAU,EACV,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EACV,eAAe,EAGf,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,qBAAa,WAAY,SAAQ,KAAM,YAAW,cAAc;IAC9D,QAAQ,CAAC,WAAW,SAIM;gBAEd,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;IAC1D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IAErD;;OAEG;IACH,QAAQ,CAAC,CAAC,EACR,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EACzB,WAAW,EAAE,SAAS,aAAa,EAAE,EACrC,OAAO,GAAE,eAAoB,GAC5B,IAAI;IA6BP;;OAEG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAalD;;OAEG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,aAAa,EAAE;IAK1D;;OAEG;IACH,aAAa,CACX,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,GACrB,aAAa,GAAG,SAAS;IAK5B;;OAEG;IACH,uBAAuB,CACrB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,GACrB,oBAAoB,GAAG,SAAS;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/schema/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EACV,YAAY,EACZ,UAAU,EACV,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EACV,eAAe,EAGf,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,qBAAa,WAAY,SAAQ,KAAM,YAAW,cAAc;IAC9D,QAAQ,CAAC,WAAW,SAIM;gBAEd,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;IAC1D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IAErD;;OAEG;IACH,QAAQ,CAAC,CAAC,EACR,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EACzB,WAAW,EAAE,SAAS,aAAa,EAAE,EACrC,OAAO,GAAE,eAAoB,GAC5B,IAAI;IA6BP;;OAEG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAalD;;OAEG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,aAAa,EAAE;IAK1D;;OAEG;IACH,aAAa,CACX,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,GACrB,aAAa,GAAG,SAAS;IAK5B;;OAEG;IACH,uBAAuB,CACrB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,GACrB,oBAAoB,GAAG,SAAS;IAqBnC;;OAEG;IACH,uBAAuB,CACrB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,GACrB,UAAU,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS;IAMhD;;OAEG;IACH,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAMtD;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAI9B;;OAEG;IACH,YAAY,IAAI,SAAS,MAAM,EAAE;IAIjC;;OAEG;IACH,QAAQ,IAAI,sBAAsB;IAkClC;;OAEG;IACH,KAAK,IAAI,IAAI;IAKb,OAAO,CAAC,QAAQ;CAWjB"}
|
package/dist/schema/registry.js
CHANGED
|
@@ -79,9 +79,12 @@ export class SchemaRegistry {
|
|
|
79
79
|
return undefined;
|
|
80
80
|
const def = {
|
|
81
81
|
name: subscriber.name,
|
|
82
|
-
description: subscriber.description,
|
|
83
82
|
idempotent: subscriber.idempotent ?? 'unknown',
|
|
84
83
|
importance: subscriber.importance ?? 'should-investigate',
|
|
84
|
+
...('description' in subscriber &&
|
|
85
|
+
subscriber.description !== undefined && {
|
|
86
|
+
description: subscriber.description,
|
|
87
|
+
}),
|
|
85
88
|
};
|
|
86
89
|
if (subscriber.targetQueue !== undefined) {
|
|
87
90
|
def.targetQueue = subscriber.targetQueue;
|
package/dist/types/envelope.d.ts
CHANGED
|
@@ -78,5 +78,17 @@ export interface CreateEnvelopeOptions<T> extends DocketCreateFields {
|
|
|
78
78
|
* Creates a new envelope with the provided options.
|
|
79
79
|
*/
|
|
80
80
|
export declare function createEnvelope<T>(options: CreateEnvelopeOptions<T>): Envelope<T>;
|
|
81
|
+
/**
|
|
82
|
+
* Helper to create a test envelope for a given event instance.
|
|
83
|
+
* Useful for unit testing subscriber callbacks directly.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* const event = new UserCreatedEvent({ userId: '123', email: 'test@example.com' });
|
|
88
|
+
* const envelope = createDummyEnvelope(event);
|
|
89
|
+
* await mySubscriber.callback(envelope, event);
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export declare function createDummyEnvelope<T>(data: T): Envelope<T>;
|
|
81
93
|
export {};
|
|
82
94
|
//# sourceMappingURL=envelope.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../../src/types/envelope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;GAGG;AACH,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,OAAO;IACnC,kCAAkC;IAClC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAEpB,qBAAqB;IACrB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IAEjB,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,MAAM;IAGrB,4BAA4B;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,0EAA0E;IAC1E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAE/C,6CAA6C;IAC7C,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAElC,oDAAoD;IACpD,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAEnC,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAIlC;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAEhC,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAI/B,sCAAsC;IACtC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAEhC,yCAAyC;IACzC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAE5C;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CACzD;AAED;;GAEG;AACH,KAAK,kBAAkB,GAAG,IAAI,CAC5B,MAAM,EACJ,UAAU,GACV,kBAAkB,GAClB,kBAAkB,GAClB,YAAY,GACZ,eAAe,CAClB,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,qBAAqB,CAAC,CAAC,CAAE,SAAQ,kBAAkB;IAClE,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAEjC,qBAAqB;IACrB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IAEjB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAExD;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAEjE,iDAAiD;IACjD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAC9B,OAAO,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAChC,QAAQ,CAAC,CAAC,CAAC,CAmCb"}
|
|
1
|
+
{"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../../src/types/envelope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;GAGG;AACH,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,OAAO;IACnC,kCAAkC;IAClC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAEpB,qBAAqB;IACrB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IAEjB,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,MAAM;IAGrB,4BAA4B;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B,0EAA0E;IAC1E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAE/C,6CAA6C;IAC7C,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAElC,oDAAoD;IACpD,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAEnC,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAIlC;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAEhC,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAI/B,sCAAsC;IACtC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAEhC,yCAAyC;IACzC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAE5C;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CACzD;AAED;;GAEG;AACH,KAAK,kBAAkB,GAAG,IAAI,CAC5B,MAAM,EACJ,UAAU,GACV,kBAAkB,GAClB,kBAAkB,GAClB,YAAY,GACZ,eAAe,CAClB,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,qBAAqB,CAAC,CAAC,CAAE,SAAQ,kBAAkB;IAClE,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAEjC,qBAAqB;IACrB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IAEjB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAExD;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAEjE,iDAAiD;IACjD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAC9B,OAAO,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAChC,QAAQ,CAAC,CAAC,CAAC,CAmCb;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAO3D"}
|
package/dist/types/envelope.js
CHANGED
|
@@ -34,3 +34,22 @@ export function createEnvelope(options) {
|
|
|
34
34
|
},
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Helper to create a test envelope for a given event instance.
|
|
39
|
+
* Useful for unit testing subscriber callbacks directly.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* const event = new UserCreatedEvent({ userId: '123', email: 'test@example.com' });
|
|
44
|
+
* const envelope = createDummyEnvelope(event);
|
|
45
|
+
* await mySubscriber.callback(envelope, event);
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function createDummyEnvelope(data) {
|
|
49
|
+
return createEnvelope({
|
|
50
|
+
data,
|
|
51
|
+
eventKey: 'dummy.event.key',
|
|
52
|
+
targetSubscriber: 'dummy-subscriber',
|
|
53
|
+
importance: 'can-ignore',
|
|
54
|
+
});
|
|
55
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export type { DeliveryMode, Idempotency, Importance, ValidationError, Validation
|
|
|
2
2
|
export { invalidResult, validResult } from './common.js';
|
|
3
3
|
export type { Dispatcher } from './dispatcher.js';
|
|
4
4
|
export type { CreateEnvelopeOptions, Docket, Envelope } from './envelope.js';
|
|
5
|
-
export { createEnvelope } from './envelope.js';
|
|
5
|
+
export { createDummyEnvelope, createEnvelope } from './envelope.js';
|
|
6
6
|
export type { Event, EventClass, EventData, EventKey, EventOptions, EventStatic, JsonPrimitive, JsonRecord, JsonValue, } from './event.js';
|
|
7
7
|
export { MatadorEvent } from './event.js';
|
|
8
8
|
export type { AnySubscriber, BaseSubscriberOptions, CallbackContext, CreateResumableSubscriberInput, CreateStandardSubscriberInput, CreateSubscriberInput, EnvelopeOf, ResumableCallback, ResumableCallbackContext, ResumableSubscriber, ResumableSubscriberOptions, StandardCallback, StandardSubscriber, StandardSubscriberOptions, Subscriber, SubscriberCallback, SubscriberDefinition, SubscriberOptions, SubscriberStub, } from './subscriber.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,YAAY,EACZ,WAAW,EACX,UAAU,EACV,eAAe,EACf,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEzD,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,YAAY,EAAE,qBAAqB,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,YAAY,EACZ,WAAW,EACX,UAAU,EACV,eAAe,EACf,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEzD,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,YAAY,EAAE,qBAAqB,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpE,YAAY,EACV,KAAK,EACL,UAAU,EACV,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,aAAa,EACb,UAAU,EACV,SAAS,GACV,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,YAAY,EACV,aAAa,EACb,qBAAqB,EACrB,eAAe,EACf,8BAA8B,EAC9B,6BAA6B,EAC7B,qBAAqB,EACrB,UAAU,EACV,iBAAiB,EACjB,wBAAwB,EACxB,mBAAmB,EACnB,0BAA0B,EAC1B,gBAAgB,EAChB,kBAAkB,EAClB,yBAAyB,EACzB,UAAU,EACV,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,EACpB,YAAY,EACZ,gBAAgB,GACjB,MAAM,iBAAiB,CAAC"}
|
package/dist/types/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { invalidResult, validResult } from './common.js';
|
|
2
|
-
export { createEnvelope } from './envelope.js';
|
|
2
|
+
export { createDummyEnvelope, createEnvelope } from './envelope.js';
|
|
3
3
|
export { MatadorEvent } from './event.js';
|
|
4
4
|
export { createSubscriber, createSubscriberStub, isResumableSubscriber, isStandardSubscriber, isSubscriber, isSubscriberStub, } from './subscriber.js';
|
|
@@ -25,8 +25,11 @@ export type EnvelopeOf<T extends MatadorEvent> = Envelope<T['data']>;
|
|
|
25
25
|
* Callback function executed when an event is received (standard subscribers).
|
|
26
26
|
* Receives the full envelope containing id, data, and docket, plus a context
|
|
27
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.
|
|
28
31
|
*/
|
|
29
|
-
export type StandardCallback<T = unknown> = (envelope: Envelope<T>, context: CallbackContext) => Promise<
|
|
32
|
+
export type StandardCallback<T = unknown> = (envelope: Envelope<T>, context: CallbackContext) => Promise<unknown> | unknown | void;
|
|
30
33
|
/**
|
|
31
34
|
* Context for resumable subscriber callbacks.
|
|
32
35
|
* Combines checkpoint operations (io, all) with matador access.
|
|
@@ -36,8 +39,11 @@ export type ResumableCallbackContext = SubscriberContext & CallbackContext;
|
|
|
36
39
|
* Callback function for resumable subscribers.
|
|
37
40
|
* Receives the envelope and a context with io() for checkpointed operations
|
|
38
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.
|
|
39
45
|
*/
|
|
40
|
-
export type ResumableCallback<T = unknown> = (envelope: Envelope<T>, context: ResumableCallbackContext) => Promise<
|
|
46
|
+
export type ResumableCallback<T = unknown> = (envelope: Envelope<T>, context: ResumableCallbackContext) => Promise<unknown> | unknown | void;
|
|
41
47
|
/**
|
|
42
48
|
* Callback function executed when an event is received.
|
|
43
49
|
* @deprecated Use StandardCallback or ResumableCallback instead.
|
|
@@ -102,7 +108,7 @@ export type Subscriber<T extends MatadorEvent> = StandardSubscriber<T> | Resumab
|
|
|
102
108
|
* is in a remote service. Declares the subscriber contract without providing
|
|
103
109
|
* the callback.
|
|
104
110
|
*/
|
|
105
|
-
export interface SubscriberStub extends StandardSubscriberOptions {
|
|
111
|
+
export interface SubscriberStub extends Omit<StandardSubscriberOptions, 'description'> {
|
|
106
112
|
/** Human-readable name for the subscriber */
|
|
107
113
|
readonly name: string;
|
|
108
114
|
/** Indicates this is a stub without implementation */
|
|
@@ -191,7 +197,6 @@ export declare function createSubscriber<T extends MatadorEvent>(input: CreateSu
|
|
|
191
197
|
*/
|
|
192
198
|
export interface CreateSubscriberStubInput {
|
|
193
199
|
readonly name: string;
|
|
194
|
-
readonly description: string;
|
|
195
200
|
readonly idempotent?: 'yes' | 'no' | 'unknown' | undefined;
|
|
196
201
|
readonly importance?: Importance | undefined;
|
|
197
202
|
readonly targetQueue?: string | undefined;
|
|
@@ -204,7 +209,6 @@ export interface CreateSubscriberStubInput {
|
|
|
204
209
|
* ```typescript
|
|
205
210
|
* const stub = createSubscriberStub({
|
|
206
211
|
* name: 'remote-analytics',
|
|
207
|
-
* description: 'Sends events to remote analytics service',
|
|
208
212
|
* targetQueue: 'analytics-worker',
|
|
209
213
|
* });
|
|
210
214
|
* ```
|
|
@@ -215,7 +219,7 @@ export declare function createSubscriberStub(input: CreateSubscriberStubInput):
|
|
|
215
219
|
*/
|
|
216
220
|
export interface SubscriberDefinition {
|
|
217
221
|
readonly name: string;
|
|
218
|
-
readonly description
|
|
222
|
+
readonly description?: string | undefined;
|
|
219
223
|
readonly idempotent: Idempotency;
|
|
220
224
|
readonly importance: Importance;
|
|
221
225
|
readonly targetQueue?: string | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscriber.d.ts","sourceRoot":"","sources":["../../src/types/subscriber.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,YAAY,IAAI,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAErE
|
|
1
|
+
{"version":3,"file":"subscriber.d.ts","sourceRoot":"","sources":["../../src/types/subscriber.ts"],"names":[],"mappings":"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"}
|
package/dist/types/subscriber.js
CHANGED
|
@@ -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
package/src/core/fanout.test.ts
CHANGED
package/src/errors/index.ts
CHANGED
|
@@ -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
|
-
*
|
|
80
|
-
*
|
|
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
|
-
*
|
|
83
|
-
* in
|
|
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
|
-
'
|
|
90
|
-
'
|
|
91
|
-
'
|
|
92
|
-
'
|
|
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
|
-
|
|
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
|
@@ -48,6 +48,7 @@ export type {
|
|
|
48
48
|
} from './types/index.js';
|
|
49
49
|
export {
|
|
50
50
|
MatadorEvent,
|
|
51
|
+
createDummyEnvelope,
|
|
51
52
|
createEnvelope,
|
|
52
53
|
createSubscriber,
|
|
53
54
|
createSubscriberStub,
|
|
@@ -171,6 +172,7 @@ export { ProcessingPipeline } from './pipeline/index.js';
|
|
|
171
172
|
export type { HasDescription } from './errors/index.js';
|
|
172
173
|
export {
|
|
173
174
|
// Retry control errors
|
|
175
|
+
assertEvent,
|
|
174
176
|
DontRetry,
|
|
175
177
|
DoRetry,
|
|
176
178
|
EventAssertionError,
|
|
@@ -542,6 +542,44 @@ describe('ProcessingPipeline', () => {
|
|
|
542
542
|
});
|
|
543
543
|
});
|
|
544
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
|
+
|
|
545
583
|
it('should call onWorkerError after failed callback', async () => {
|
|
546
584
|
const envelope = createTestEnvelope();
|
|
547
585
|
const subscriberDef = createSubscriberDefinition();
|
|
@@ -14,7 +14,15 @@ describe('StandardRetryPolicy', () => {
|
|
|
14
14
|
describe('shouldRetry', () => {
|
|
15
15
|
it('should dead-letter on EventAssertionError', () => {
|
|
16
16
|
const policy = new StandardRetryPolicy();
|
|
17
|
-
const
|
|
17
|
+
const envelope = createEnvelope({
|
|
18
|
+
eventKey: 'test.event',
|
|
19
|
+
targetSubscriber: 'test-subscriber',
|
|
20
|
+
data: { test: 'data' },
|
|
21
|
+
importance: 'should-investigate',
|
|
22
|
+
});
|
|
23
|
+
const context = createContext(
|
|
24
|
+
new EventAssertionError(envelope, 'Invalid event'),
|
|
25
|
+
);
|
|
18
26
|
|
|
19
27
|
const decision = policy.shouldRetry(context);
|
|
20
28
|
|