@zdavison/matador 2.0.1
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/cli.ts +453 -0
- package/dist/checkpoint/context.d.ts +59 -0
- package/dist/checkpoint/context.d.ts.map +1 -0
- package/dist/checkpoint/context.js +140 -0
- package/dist/checkpoint/context.test.d.ts +2 -0
- package/dist/checkpoint/context.test.d.ts.map +1 -0
- package/dist/checkpoint/context.test.js +424 -0
- package/dist/checkpoint/index.d.ts +7 -0
- package/dist/checkpoint/index.d.ts.map +1 -0
- package/dist/checkpoint/index.js +6 -0
- package/dist/checkpoint/stores/memory.d.ts +29 -0
- package/dist/checkpoint/stores/memory.d.ts.map +1 -0
- package/dist/checkpoint/stores/memory.js +39 -0
- package/dist/checkpoint/stores/noop.d.ts +14 -0
- package/dist/checkpoint/stores/noop.d.ts.map +1 -0
- package/dist/checkpoint/stores/noop.js +18 -0
- package/dist/checkpoint/stores/stores.test.d.ts +2 -0
- package/dist/checkpoint/stores/stores.test.d.ts.map +1 -0
- package/dist/checkpoint/stores/stores.test.js +146 -0
- package/dist/checkpoint/types.d.ts +119 -0
- package/dist/checkpoint/types.d.ts.map +1 -0
- package/dist/checkpoint/types.js +1 -0
- package/dist/codec/codec.d.ts +29 -0
- package/dist/codec/codec.d.ts.map +1 -0
- package/dist/codec/codec.js +15 -0
- package/dist/codec/header-aware-codec.d.ts +36 -0
- package/dist/codec/header-aware-codec.d.ts.map +1 -0
- package/dist/codec/header-aware-codec.js +1 -0
- package/dist/codec/index.d.ts +6 -0
- package/dist/codec/index.d.ts.map +1 -0
- package/dist/codec/index.js +3 -0
- package/dist/codec/json-codec.d.ts +13 -0
- package/dist/codec/json-codec.d.ts.map +1 -0
- package/dist/codec/json-codec.js +64 -0
- package/dist/codec/rabbitmq-codec.d.ts +28 -0
- package/dist/codec/rabbitmq-codec.d.ts.map +1 -0
- package/dist/codec/rabbitmq-codec.js +242 -0
- package/dist/codec/rabbitmq-codec.test.d.ts +2 -0
- package/dist/codec/rabbitmq-codec.test.d.ts.map +1 -0
- package/dist/codec/rabbitmq-codec.test.js +433 -0
- package/dist/core/fanout.d.ts +59 -0
- package/dist/core/fanout.d.ts.map +1 -0
- package/dist/core/fanout.js +121 -0
- package/dist/core/fanout.test.d.ts +2 -0
- package/dist/core/fanout.test.d.ts.map +1 -0
- package/dist/core/fanout.test.js +1055 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +3 -0
- package/dist/core/matador.d.ts +133 -0
- package/dist/core/matador.d.ts.map +1 -0
- package/dist/core/matador.js +222 -0
- package/dist/core/matador.test.d.ts +2 -0
- package/dist/core/matador.test.d.ts.map +1 -0
- package/dist/core/matador.test.js +446 -0
- package/dist/core/shutdown.d.ts +78 -0
- package/dist/core/shutdown.d.ts.map +1 -0
- package/dist/core/shutdown.js +111 -0
- package/dist/core/shutdown.test.d.ts +2 -0
- package/dist/core/shutdown.test.d.ts.map +1 -0
- package/dist/core/shutdown.test.js +524 -0
- package/dist/errors/checkpoint-errors.d.ts +30 -0
- package/dist/errors/checkpoint-errors.d.ts.map +1 -0
- package/dist/errors/checkpoint-errors.js +49 -0
- package/dist/errors/has-description.d.ts +18 -0
- package/dist/errors/has-description.d.ts.map +1 -0
- package/dist/errors/has-description.js +9 -0
- package/dist/errors/index.d.ts +6 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +22 -0
- package/dist/errors/matador-errors.d.ts +183 -0
- package/dist/errors/matador-errors.d.ts.map +1 -0
- package/dist/errors/matador-errors.js +376 -0
- package/dist/errors/retry-errors.d.ts +67 -0
- package/dist/errors/retry-errors.d.ts.map +1 -0
- package/dist/errors/retry-errors.js +108 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/safe-hooks.d.ts +34 -0
- package/dist/hooks/safe-hooks.d.ts.map +1 -0
- package/dist/hooks/safe-hooks.js +135 -0
- package/dist/hooks/types.d.ts +189 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +9 -0
- package/dist/index.cjs +3156 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +23 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/pipeline/index.d.ts +3 -0
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/pipeline/index.js +1 -0
- package/dist/pipeline/pipeline.d.ts +60 -0
- package/dist/pipeline/pipeline.d.ts.map +1 -0
- package/dist/pipeline/pipeline.js +261 -0
- package/dist/pipeline/pipeline.test.d.ts +2 -0
- package/dist/pipeline/pipeline.test.d.ts.map +1 -0
- package/dist/pipeline/pipeline.test.js +1065 -0
- package/dist/retry/index.d.ts +4 -0
- package/dist/retry/index.d.ts.map +1 -0
- package/dist/retry/index.js +1 -0
- package/dist/retry/policy.d.ts +43 -0
- package/dist/retry/policy.d.ts.map +1 -0
- package/dist/retry/policy.js +1 -0
- package/dist/retry/standard-policy.d.ts +44 -0
- package/dist/retry/standard-policy.d.ts.map +1 -0
- package/dist/retry/standard-policy.js +102 -0
- package/dist/retry/standard-policy.test.d.ts +2 -0
- package/dist/retry/standard-policy.test.d.ts.map +1 -0
- package/dist/retry/standard-policy.test.js +190 -0
- package/dist/schema/index.d.ts +4 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +2 -0
- package/dist/schema/registry.d.ts +63 -0
- package/dist/schema/registry.d.ts.map +1 -0
- package/dist/schema/registry.js +171 -0
- package/dist/schema/registry.test.d.ts +2 -0
- package/dist/schema/registry.test.d.ts.map +1 -0
- package/dist/schema/registry.test.js +278 -0
- package/dist/schema/types.d.ts +158 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +74 -0
- package/dist/schema/types.test.d.ts +2 -0
- package/dist/schema/types.test.d.ts.map +1 -0
- package/dist/schema/types.test.js +243 -0
- package/dist/topology/builder.d.ts +66 -0
- package/dist/topology/builder.d.ts.map +1 -0
- package/dist/topology/builder.js +156 -0
- package/dist/topology/builder.test.d.ts +2 -0
- package/dist/topology/builder.test.d.ts.map +1 -0
- package/dist/topology/builder.test.js +222 -0
- package/dist/topology/index.d.ts +5 -0
- package/dist/topology/index.d.ts.map +1 -0
- package/dist/topology/index.js +2 -0
- package/dist/topology/types.d.ts +76 -0
- package/dist/topology/types.d.ts.map +1 -0
- package/dist/topology/types.js +18 -0
- package/dist/transport/capabilities.d.ts +66 -0
- package/dist/transport/capabilities.d.ts.map +1 -0
- package/dist/transport/capabilities.js +18 -0
- package/dist/transport/connection-manager.d.ts +95 -0
- package/dist/transport/connection-manager.d.ts.map +1 -0
- package/dist/transport/connection-manager.js +144 -0
- package/dist/transport/index.d.ts +11 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +5 -0
- package/dist/transport/local/local-transport.d.ts +62 -0
- package/dist/transport/local/local-transport.d.ts.map +1 -0
- package/dist/transport/local/local-transport.js +241 -0
- package/dist/transport/local/local-transport.test.d.ts +2 -0
- package/dist/transport/local/local-transport.test.d.ts.map +1 -0
- package/dist/transport/local/local-transport.test.js +192 -0
- package/dist/transport/multi/multi-transport.d.ts +94 -0
- package/dist/transport/multi/multi-transport.d.ts.map +1 -0
- package/dist/transport/multi/multi-transport.js +184 -0
- package/dist/transport/multi/multi-transport.test.d.ts +2 -0
- package/dist/transport/multi/multi-transport.test.d.ts.map +1 -0
- package/dist/transport/multi/multi-transport.test.js +236 -0
- package/dist/transport/rabbitmq/rabbitmq-transport.d.ts +73 -0
- package/dist/transport/rabbitmq/rabbitmq-transport.d.ts.map +1 -0
- package/dist/transport/rabbitmq/rabbitmq-transport.js +518 -0
- package/dist/transport/transport.d.ts +152 -0
- package/dist/transport/transport.d.ts.map +1 -0
- package/dist/transport/transport.js +1 -0
- package/dist/types/common.d.ts +41 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +12 -0
- package/dist/types/envelope.d.ts +82 -0
- package/dist/types/envelope.d.ts.map +1 -0
- package/dist/types/envelope.js +36 -0
- package/dist/types/event.d.ts +96 -0
- package/dist/types/event.d.ts.map +1 -0
- package/dist/types/event.js +26 -0
- package/dist/types/event.test.d.ts +2 -0
- package/dist/types/event.test.d.ts.map +1 -0
- package/dist/types/event.test.js +130 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +4 -0
- package/dist/types/subscriber.d.ts +207 -0
- package/dist/types/subscriber.d.ts.map +1 -0
- package/dist/types/subscriber.js +99 -0
- package/examples/config.ts +126 -0
- package/examples/event.ts +26 -0
- package/examples/order-event.json +19 -0
- package/package.json +66 -0
- package/src/checkpoint/context.test.ts +510 -0
- package/src/checkpoint/context.ts +213 -0
- package/src/checkpoint/index.ts +30 -0
- package/src/checkpoint/stores/memory.ts +47 -0
- package/src/checkpoint/stores/noop.ts +22 -0
- package/src/checkpoint/stores/stores.test.ts +177 -0
- package/src/checkpoint/types.ts +147 -0
- package/src/codec/codec.ts +42 -0
- package/src/codec/header-aware-codec.ts +41 -0
- package/src/codec/index.ts +11 -0
- package/src/codec/json-codec.ts +69 -0
- package/src/codec/rabbitmq-codec.test.ts +516 -0
- package/src/codec/rabbitmq-codec.ts +336 -0
- package/src/core/fanout.test.ts +1351 -0
- package/src/core/fanout.ts +184 -0
- package/src/core/index.ts +12 -0
- package/src/core/matador.test.ts +575 -0
- package/src/core/matador.ts +344 -0
- package/src/core/shutdown.test.ts +853 -0
- package/src/core/shutdown.ts +165 -0
- package/src/errors/checkpoint-errors.ts +62 -0
- package/src/errors/has-description.ts +25 -0
- package/src/errors/index.ts +57 -0
- package/src/errors/matador-errors.ts +477 -0
- package/src/errors/retry-errors.ts +134 -0
- package/src/hooks/index.ts +15 -0
- package/src/hooks/safe-hooks.ts +223 -0
- package/src/hooks/types.ts +248 -0
- package/src/index.ts +231 -0
- package/src/pipeline/index.ts +2 -0
- package/src/pipeline/pipeline.test.ts +1317 -0
- package/src/pipeline/pipeline.ts +371 -0
- package/src/retry/index.ts +4 -0
- package/src/retry/policy.ts +46 -0
- package/src/retry/standard-policy.test.ts +282 -0
- package/src/retry/standard-policy.ts +156 -0
- package/src/schema/index.ts +16 -0
- package/src/schema/registry.test.ts +339 -0
- package/src/schema/registry.ts +226 -0
- package/src/schema/types.test.ts +281 -0
- package/src/schema/types.ts +217 -0
- package/src/topology/builder.test.ts +275 -0
- package/src/topology/builder.ts +199 -0
- package/src/topology/index.ts +15 -0
- package/src/topology/types.ts +109 -0
- package/src/transport/capabilities.ts +88 -0
- package/src/transport/connection-manager.ts +218 -0
- package/src/transport/index.ts +42 -0
- package/src/transport/local/local-transport.test.ts +262 -0
- package/src/transport/local/local-transport.ts +327 -0
- package/src/transport/multi/multi-transport.test.ts +320 -0
- package/src/transport/multi/multi-transport.ts +294 -0
- package/src/transport/rabbitmq/rabbitmq-transport.ts +753 -0
- package/src/transport/transport.ts +200 -0
- package/src/types/common.ts +53 -0
- package/src/types/envelope.ts +152 -0
- package/src/types/event.test.ts +157 -0
- package/src/types/event.ts +118 -0
- package/src/types/index.ts +52 -0
- package/src/types/subscriber.ts +310 -0
- package/test/e2e/multi-transport.e2e.test.ts +236 -0
- package/test/e2e/rabbitmq-transport.e2e.test.ts +327 -0
- package/test/e2e/transport-compliance.e2e.test.ts +505 -0
- package/test/integration/matador.integration.test.ts +634 -0
- package/tsconfig.json +29 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsup.config.ts +13 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pipeline/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ProcessingPipeline } from './pipeline.js';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { CheckpointStore } from '../checkpoint/index.js';
|
|
2
|
+
import type { Codec } from '../codec/index.js';
|
|
3
|
+
import type { SafeHooks } from '../hooks/index.js';
|
|
4
|
+
import type { RetryDecision, RetryPolicy } from '../retry/index.js';
|
|
5
|
+
import type { SchemaRegistry } from '../schema/index.js';
|
|
6
|
+
import type { MessageReceipt, Transport } from '../transport/index.js';
|
|
7
|
+
import type { Envelope, SubscriberDefinition } from '../types/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* Configuration for the processing pipeline.
|
|
10
|
+
*/
|
|
11
|
+
export interface PipelineConfig {
|
|
12
|
+
readonly transport: Transport;
|
|
13
|
+
readonly schema: SchemaRegistry;
|
|
14
|
+
readonly codec: Codec;
|
|
15
|
+
readonly retryPolicy: RetryPolicy;
|
|
16
|
+
readonly hooks: SafeHooks;
|
|
17
|
+
/** Optional checkpoint store for resumable subscribers */
|
|
18
|
+
readonly checkpointStore?: CheckpointStore | undefined;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Result of pipeline processing.
|
|
22
|
+
*/
|
|
23
|
+
export interface ProcessResult {
|
|
24
|
+
readonly success: boolean;
|
|
25
|
+
readonly envelope?: Envelope | undefined;
|
|
26
|
+
readonly subscriber?: SubscriberDefinition | undefined;
|
|
27
|
+
readonly error?: Error | undefined;
|
|
28
|
+
readonly decision?: RetryDecision | undefined;
|
|
29
|
+
readonly durationMs: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Processing pipeline for incoming messages.
|
|
33
|
+
*
|
|
34
|
+
* Handles the complete message lifecycle:
|
|
35
|
+
* 1. Decode envelope from raw bytes
|
|
36
|
+
* 2. Lookup subscriber from schema
|
|
37
|
+
* 3. Execute subscriber callback with hooks
|
|
38
|
+
* 4. Handle success/failure with retry policy
|
|
39
|
+
*/
|
|
40
|
+
export declare class ProcessingPipeline {
|
|
41
|
+
private readonly transport;
|
|
42
|
+
private readonly schema;
|
|
43
|
+
private readonly codec;
|
|
44
|
+
private readonly retryPolicy;
|
|
45
|
+
private readonly hooks;
|
|
46
|
+
private readonly checkpointStore;
|
|
47
|
+
constructor(config: PipelineConfig);
|
|
48
|
+
/**
|
|
49
|
+
* Processes a raw message from the transport.
|
|
50
|
+
*/
|
|
51
|
+
process(rawMessage: Uint8Array, receipt: MessageReceipt): Promise<ProcessResult>;
|
|
52
|
+
private handleRetryDecision;
|
|
53
|
+
private sendToDeadLetter;
|
|
54
|
+
/**
|
|
55
|
+
* Gets retry decision for unhandled messages (subscriber not found).
|
|
56
|
+
* Uses same retry policy logic but sends to 'unhandled' DLQ after max attempts.
|
|
57
|
+
*/
|
|
58
|
+
private getUnhandledRetryDecision;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/pipeline/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE1E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAM/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,KAAK,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAGxE;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;CACxD;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,UAAU,CAAC,EAAE,oBAAoB,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAY;IAClC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;gBAEtC,MAAM,EAAE,cAAc;IASlC;;OAEG;IACG,OAAO,CACX,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC;YAgNX,mBAAmB;YAqCnB,gBAAgB;IAkB9B;;;OAGG;IACH,OAAO,CAAC,yBAAyB;CA+BlC"}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { NoOpCheckpointStore, ResumableContext } from '../checkpoint/index.js';
|
|
2
|
+
import { CodecDecodeError } from '../codec/index.js';
|
|
3
|
+
import { SubscriberIsStubError, SubscriberNotRegisteredError, } from '../errors/index.js';
|
|
4
|
+
import { isResumableSubscriber } from '../types/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Processing pipeline for incoming messages.
|
|
7
|
+
*
|
|
8
|
+
* Handles the complete message lifecycle:
|
|
9
|
+
* 1. Decode envelope from raw bytes
|
|
10
|
+
* 2. Lookup subscriber from schema
|
|
11
|
+
* 3. Execute subscriber callback with hooks
|
|
12
|
+
* 4. Handle success/failure with retry policy
|
|
13
|
+
*/
|
|
14
|
+
export class ProcessingPipeline {
|
|
15
|
+
transport;
|
|
16
|
+
schema;
|
|
17
|
+
codec;
|
|
18
|
+
retryPolicy;
|
|
19
|
+
hooks;
|
|
20
|
+
checkpointStore;
|
|
21
|
+
constructor(config) {
|
|
22
|
+
this.transport = config.transport;
|
|
23
|
+
this.schema = config.schema;
|
|
24
|
+
this.codec = config.codec;
|
|
25
|
+
this.retryPolicy = config.retryPolicy;
|
|
26
|
+
this.hooks = config.hooks;
|
|
27
|
+
this.checkpointStore = config.checkpointStore ?? new NoOpCheckpointStore();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Processes a raw message from the transport.
|
|
31
|
+
*/
|
|
32
|
+
async process(rawMessage, receipt) {
|
|
33
|
+
const startTime = performance.now();
|
|
34
|
+
// 1. Decode envelope
|
|
35
|
+
let envelope;
|
|
36
|
+
try {
|
|
37
|
+
envelope = this.codec.decode(rawMessage);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
const decodeError = error instanceof CodecDecodeError
|
|
41
|
+
? error
|
|
42
|
+
: new CodecDecodeError('Unknown decode error', error);
|
|
43
|
+
await this.transport.complete(receipt);
|
|
44
|
+
await this.hooks.onDecodeError({
|
|
45
|
+
error: decodeError,
|
|
46
|
+
rawMessage,
|
|
47
|
+
sourceQueue: receipt.sourceQueue,
|
|
48
|
+
transport: receipt.sourceTransport,
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
error: decodeError,
|
|
53
|
+
durationMs: performance.now() - startTime,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// 2. Lookup subscriber from schema
|
|
57
|
+
const subscriberDef = this.schema.getSubscriberDefinition(envelope.docket.eventKey, envelope.docket.targetSubscriber);
|
|
58
|
+
if (!subscriberDef) {
|
|
59
|
+
const error = new SubscriberNotRegisteredError(envelope.docket.targetSubscriber, envelope.docket.eventKey);
|
|
60
|
+
// Retry unhandled messages (likely deployment timing issue)
|
|
61
|
+
const decision = this.getUnhandledRetryDecision(envelope, receipt, error);
|
|
62
|
+
await this.handleRetryDecision(receipt, envelope, decision);
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
envelope,
|
|
66
|
+
error,
|
|
67
|
+
decision,
|
|
68
|
+
durationMs: performance.now() - startTime,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// Get executable subscriber
|
|
72
|
+
const subscriber = this.schema.getExecutableSubscriber(envelope.docket.eventKey, envelope.docket.targetSubscriber);
|
|
73
|
+
if (!subscriber) {
|
|
74
|
+
// Subscriber is a stub (remote implementation)
|
|
75
|
+
const error = new SubscriberIsStubError(envelope.docket.targetSubscriber);
|
|
76
|
+
// Retry stub messages (likely deployment timing issue)
|
|
77
|
+
const decision = this.getUnhandledRetryDecision(envelope, receipt, error);
|
|
78
|
+
await this.handleRetryDecision(receipt, envelope, decision);
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
envelope,
|
|
82
|
+
subscriber: subscriberDef,
|
|
83
|
+
error,
|
|
84
|
+
decision,
|
|
85
|
+
durationMs: performance.now() - startTime,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// 3. Execute subscriber callback with hooks
|
|
89
|
+
let result;
|
|
90
|
+
let error;
|
|
91
|
+
let context;
|
|
92
|
+
// For resumable subscribers, load existing checkpoint and create context
|
|
93
|
+
const isResumable = isResumableSubscriber(subscriber);
|
|
94
|
+
let existingCheckpoint;
|
|
95
|
+
if (isResumable) {
|
|
96
|
+
existingCheckpoint = await this.checkpointStore.get(envelope.id);
|
|
97
|
+
if (existingCheckpoint) {
|
|
98
|
+
await this.hooks.onCheckpointLoaded?.({
|
|
99
|
+
envelope,
|
|
100
|
+
subscriber: subscriberDef,
|
|
101
|
+
checkpoint: existingCheckpoint,
|
|
102
|
+
cachedSteps: Object.keys(existingCheckpoint.completedSteps).length,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
context = new ResumableContext({
|
|
106
|
+
store: this.checkpointStore,
|
|
107
|
+
envelope,
|
|
108
|
+
subscriber: subscriberDef,
|
|
109
|
+
existingCheckpoint,
|
|
110
|
+
hooks: {
|
|
111
|
+
onCheckpointHit: (ctx) => this.hooks.onCheckpointHit?.(ctx),
|
|
112
|
+
onCheckpointMiss: (ctx) => this.hooks.onCheckpointMiss?.(ctx),
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
await this.hooks.onWorkerWrap(envelope, subscriberDef, async () => {
|
|
117
|
+
await this.hooks.onWorkerBeforeProcess(envelope, subscriberDef);
|
|
118
|
+
try {
|
|
119
|
+
if (isResumable && context) {
|
|
120
|
+
// Resumable subscriber: pass context as second argument
|
|
121
|
+
// Cast needed because TypeScript can't narrow the union type based on isResumable
|
|
122
|
+
const resumableCallback = subscriber.callback;
|
|
123
|
+
result = await resumableCallback(envelope, context);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
// Standard subscriber: just pass envelope
|
|
127
|
+
const standardCallback = subscriber.callback;
|
|
128
|
+
result = await standardCallback(envelope);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (e) {
|
|
132
|
+
error = e instanceof Error ? e : new Error(String(e));
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
const durationMs = performance.now() - startTime;
|
|
136
|
+
// 4. Handle success
|
|
137
|
+
if (!error) {
|
|
138
|
+
// Clear checkpoint on success for resumable subscribers
|
|
139
|
+
if (context) {
|
|
140
|
+
await context.clear();
|
|
141
|
+
await this.hooks.onCheckpointCleared?.({
|
|
142
|
+
envelope,
|
|
143
|
+
subscriber: subscriberDef,
|
|
144
|
+
reason: 'success',
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
await this.transport.complete(receipt);
|
|
148
|
+
await this.hooks.onWorkerSuccess({
|
|
149
|
+
envelope,
|
|
150
|
+
subscriber: subscriberDef,
|
|
151
|
+
result,
|
|
152
|
+
durationMs,
|
|
153
|
+
transport: receipt.sourceTransport,
|
|
154
|
+
});
|
|
155
|
+
return {
|
|
156
|
+
success: true,
|
|
157
|
+
envelope,
|
|
158
|
+
subscriber: subscriberDef,
|
|
159
|
+
durationMs,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
// 5. Handle failure - consult retry policy
|
|
163
|
+
const decision = this.retryPolicy.shouldRetry({
|
|
164
|
+
envelope,
|
|
165
|
+
error,
|
|
166
|
+
subscriber: subscriberDef,
|
|
167
|
+
receipt,
|
|
168
|
+
});
|
|
169
|
+
// Update envelope with error info
|
|
170
|
+
envelope.docket.lastError = error.message;
|
|
171
|
+
envelope.docket.firstError ??= error.message;
|
|
172
|
+
// Clear checkpoint on dead-letter (terminal state)
|
|
173
|
+
if (decision.action === 'dead-letter' && context) {
|
|
174
|
+
await context.clear();
|
|
175
|
+
await this.hooks.onCheckpointCleared?.({
|
|
176
|
+
envelope,
|
|
177
|
+
subscriber: subscriberDef,
|
|
178
|
+
reason: 'dead-letter',
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
await this.handleRetryDecision(receipt, envelope, decision);
|
|
182
|
+
await this.hooks.onWorkerError({
|
|
183
|
+
envelope,
|
|
184
|
+
subscriber: subscriberDef,
|
|
185
|
+
error,
|
|
186
|
+
durationMs,
|
|
187
|
+
decision,
|
|
188
|
+
transport: receipt.sourceTransport,
|
|
189
|
+
});
|
|
190
|
+
return {
|
|
191
|
+
success: false,
|
|
192
|
+
envelope,
|
|
193
|
+
subscriber: subscriberDef,
|
|
194
|
+
error,
|
|
195
|
+
decision,
|
|
196
|
+
durationMs,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
async handleRetryDecision(receipt, envelope, decision) {
|
|
200
|
+
switch (decision.action) {
|
|
201
|
+
case 'retry': {
|
|
202
|
+
// Increment attempts and schedule retry
|
|
203
|
+
envelope.docket.attempts++;
|
|
204
|
+
envelope.docket.scheduledFor = new Date(Date.now() + decision.delay).toISOString();
|
|
205
|
+
await this.transport.send(receipt.sourceQueue, envelope, {
|
|
206
|
+
delay: decision.delay,
|
|
207
|
+
});
|
|
208
|
+
await this.transport.complete(receipt);
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
case 'dead-letter': {
|
|
212
|
+
await this.sendToDeadLetter(receipt, envelope, decision.queue, decision.reason);
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
case 'discard': {
|
|
216
|
+
await this.transport.complete(receipt);
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async sendToDeadLetter(receipt, envelope, dlqName, reason) {
|
|
222
|
+
envelope.docket.originalQueue ??= receipt.sourceQueue;
|
|
223
|
+
if (this.transport.sendToDeadLetter) {
|
|
224
|
+
await this.transport.sendToDeadLetter(receipt, dlqName, envelope, reason);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
// Manual: send to DLQ then complete original
|
|
228
|
+
const fullDlqName = `${receipt.sourceQueue}.${dlqName}`;
|
|
229
|
+
await this.transport.send(fullDlqName, envelope);
|
|
230
|
+
await this.transport.complete(receipt);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Gets retry decision for unhandled messages (subscriber not found).
|
|
235
|
+
* Uses same retry policy logic but sends to 'unhandled' DLQ after max attempts.
|
|
236
|
+
*/
|
|
237
|
+
getUnhandledRetryDecision(envelope, receipt, error) {
|
|
238
|
+
// Create a synthetic subscriber definition for retry policy
|
|
239
|
+
const syntheticSubscriber = {
|
|
240
|
+
name: envelope.docket.targetSubscriber,
|
|
241
|
+
description: 'Synthetic subscriber for unhandled message retry',
|
|
242
|
+
idempotent: 'yes', // Safe to retry unhandled messages
|
|
243
|
+
importance: envelope.docket.importance ?? 'should-investigate',
|
|
244
|
+
};
|
|
245
|
+
const decision = this.retryPolicy.shouldRetry({
|
|
246
|
+
envelope,
|
|
247
|
+
error,
|
|
248
|
+
subscriber: syntheticSubscriber,
|
|
249
|
+
receipt,
|
|
250
|
+
});
|
|
251
|
+
// Override dead-letter queue to 'unhandled' instead of 'undeliverable'
|
|
252
|
+
if (decision.action === 'dead-letter') {
|
|
253
|
+
return {
|
|
254
|
+
action: 'dead-letter',
|
|
255
|
+
queue: 'unhandled',
|
|
256
|
+
reason: decision.reason,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
return decision;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.test.d.ts","sourceRoot":"","sources":["../../src/pipeline/pipeline.test.ts"],"names":[],"mappings":""}
|