@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,336 @@
|
|
|
1
|
+
import type { Docket, Envelope } from '../types/index.js';
|
|
2
|
+
import { CodecDecodeError } from './codec.js';
|
|
3
|
+
import type { EncodedMessage, HeaderAwareCodec } from './header-aware-codec.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Header names used by Matador in RabbitMQ messages.
|
|
7
|
+
*/
|
|
8
|
+
const HEADERS = {
|
|
9
|
+
// Routing
|
|
10
|
+
EVENT_KEY: 'x-matador-event-key',
|
|
11
|
+
TARGET_SUBSCRIBER: 'x-matador-subscriber',
|
|
12
|
+
SCHEDULED_FOR: 'x-matador-scheduled-for',
|
|
13
|
+
ORIGINAL_QUEUE: 'x-matador-original-queue',
|
|
14
|
+
|
|
15
|
+
// Processing state
|
|
16
|
+
ATTEMPTS: 'x-matador-attempts',
|
|
17
|
+
CREATED_AT: 'x-matador-created-at',
|
|
18
|
+
FIRST_ERROR: 'x-matador-first-error',
|
|
19
|
+
LAST_ERROR: 'x-matador-last-error',
|
|
20
|
+
|
|
21
|
+
// Observability
|
|
22
|
+
IMPORTANCE: 'x-matador-importance',
|
|
23
|
+
CORRELATION_ID: 'x-matador-correlation-id',
|
|
24
|
+
|
|
25
|
+
// v1 compatibility headers
|
|
26
|
+
V1_EVENT_ID: 'x-event-id',
|
|
27
|
+
V1_CORRELATION_ID: 'x-correlation-id',
|
|
28
|
+
V1_USER_ID: 'x-user-id',
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Body structure for v2 messages.
|
|
33
|
+
* Only contains id, data, and optionally metadata (since it can be large).
|
|
34
|
+
*/
|
|
35
|
+
interface V2Body {
|
|
36
|
+
readonly id: string;
|
|
37
|
+
readonly data: unknown;
|
|
38
|
+
readonly metadata?: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* v1 message format (for backwards compatibility).
|
|
43
|
+
*/
|
|
44
|
+
interface V1Body {
|
|
45
|
+
key: string;
|
|
46
|
+
data: unknown;
|
|
47
|
+
metadata: unknown;
|
|
48
|
+
universal?: {
|
|
49
|
+
event_id?: string | null;
|
|
50
|
+
user_id?: string | null;
|
|
51
|
+
correlation_id?: string | null;
|
|
52
|
+
[key: string]: unknown;
|
|
53
|
+
};
|
|
54
|
+
before?: unknown;
|
|
55
|
+
options?: { delayMs?: number };
|
|
56
|
+
targetSubscriber: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* RabbitMQ-specific codec that stores routing/metadata in headers.
|
|
61
|
+
*
|
|
62
|
+
* Benefits:
|
|
63
|
+
* - No duplication between body and headers
|
|
64
|
+
* - RabbitMQ can route/filter based on headers
|
|
65
|
+
* - Smaller message body
|
|
66
|
+
* - v1 message format compatibility built-in
|
|
67
|
+
*/
|
|
68
|
+
export class RabbitMQCodec implements HeaderAwareCodec {
|
|
69
|
+
readonly contentType = 'application/json';
|
|
70
|
+
|
|
71
|
+
encode(envelope: Envelope): EncodedMessage {
|
|
72
|
+
const { docket } = envelope;
|
|
73
|
+
|
|
74
|
+
// Body only contains id, data, and metadata (since metadata can be large)
|
|
75
|
+
const body: V2Body = {
|
|
76
|
+
id: envelope.id,
|
|
77
|
+
data: envelope.data,
|
|
78
|
+
...(docket.metadata !== undefined && { metadata: docket.metadata }),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// All docket fields go in headers
|
|
82
|
+
const headers: Record<string, unknown> = {
|
|
83
|
+
// Routing
|
|
84
|
+
[HEADERS.EVENT_KEY]: docket.eventKey,
|
|
85
|
+
[HEADERS.TARGET_SUBSCRIBER]: docket.targetSubscriber,
|
|
86
|
+
|
|
87
|
+
// Processing state
|
|
88
|
+
[HEADERS.ATTEMPTS]: docket.attempts,
|
|
89
|
+
[HEADERS.CREATED_AT]: docket.createdAt,
|
|
90
|
+
|
|
91
|
+
// Observability
|
|
92
|
+
[HEADERS.IMPORTANCE]: docket.importance,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Optional fields
|
|
96
|
+
if (docket.scheduledFor !== undefined) {
|
|
97
|
+
headers[HEADERS.SCHEDULED_FOR] = docket.scheduledFor;
|
|
98
|
+
}
|
|
99
|
+
if (docket.originalQueue !== undefined) {
|
|
100
|
+
headers[HEADERS.ORIGINAL_QUEUE] = docket.originalQueue;
|
|
101
|
+
}
|
|
102
|
+
if (docket.firstError !== undefined) {
|
|
103
|
+
headers[HEADERS.FIRST_ERROR] = docket.firstError;
|
|
104
|
+
}
|
|
105
|
+
if (docket.lastError !== undefined) {
|
|
106
|
+
headers[HEADERS.LAST_ERROR] = docket.lastError;
|
|
107
|
+
}
|
|
108
|
+
if (docket.correlationId !== undefined) {
|
|
109
|
+
headers[HEADERS.CORRELATION_ID] = docket.correlationId;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
body: new TextEncoder().encode(JSON.stringify(body)),
|
|
114
|
+
headers,
|
|
115
|
+
contentType: this.contentType,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
decode(body: Uint8Array, headers: Record<string, unknown>): Envelope {
|
|
120
|
+
if (body.length === 0) {
|
|
121
|
+
throw new CodecDecodeError('Cannot decode empty buffer');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let json: string;
|
|
125
|
+
try {
|
|
126
|
+
json = new TextDecoder('utf-8').decode(body);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
throw new CodecDecodeError('Invalid UTF-8 encoding', error);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let parsed: unknown;
|
|
132
|
+
try {
|
|
133
|
+
parsed = JSON.parse(json);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
throw new CodecDecodeError('Invalid JSON', error);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Detect format: v1 has 'key' and 'targetSubscriber' at top level
|
|
139
|
+
if (this.isV1Body(parsed)) {
|
|
140
|
+
return this.decodeV1(parsed, headers);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// v2 format
|
|
144
|
+
if (!this.isV2Body(parsed)) {
|
|
145
|
+
throw new CodecDecodeError('Invalid message body structure');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return this.decodeV2(parsed, headers);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private isV1Body(value: unknown): value is V1Body {
|
|
152
|
+
if (typeof value !== 'object' || value === null) return false;
|
|
153
|
+
const obj = value as Record<string, unknown>;
|
|
154
|
+
return (
|
|
155
|
+
typeof obj['key'] === 'string' &&
|
|
156
|
+
typeof obj['targetSubscriber'] === 'string' &&
|
|
157
|
+
'data' in obj &&
|
|
158
|
+
!('payload' in obj)
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private isV2Body(value: unknown): value is V2Body {
|
|
163
|
+
if (typeof value !== 'object' || value === null) return false;
|
|
164
|
+
const obj = value as Record<string, unknown>;
|
|
165
|
+
return (
|
|
166
|
+
typeof obj['id'] === 'string' && 'data' in obj && !('key' in obj) // Distinguish from v1 which also has 'data'
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private decodeV2(body: V2Body, headers: Record<string, unknown>): Envelope {
|
|
171
|
+
// Required headers
|
|
172
|
+
const eventKey = this.requireStringHeader(headers, HEADERS.EVENT_KEY);
|
|
173
|
+
const targetSubscriber = this.requireStringHeader(
|
|
174
|
+
headers,
|
|
175
|
+
HEADERS.TARGET_SUBSCRIBER,
|
|
176
|
+
);
|
|
177
|
+
const attempts = this.requireNumberHeader(headers, HEADERS.ATTEMPTS);
|
|
178
|
+
const createdAt = this.requireStringHeader(headers, HEADERS.CREATED_AT);
|
|
179
|
+
const importance = this.requireStringHeader(headers, HEADERS.IMPORTANCE);
|
|
180
|
+
|
|
181
|
+
// Optional headers
|
|
182
|
+
const scheduledFor = this.optionalStringHeader(
|
|
183
|
+
headers,
|
|
184
|
+
HEADERS.SCHEDULED_FOR,
|
|
185
|
+
);
|
|
186
|
+
const originalQueue = this.optionalStringHeader(
|
|
187
|
+
headers,
|
|
188
|
+
HEADERS.ORIGINAL_QUEUE,
|
|
189
|
+
);
|
|
190
|
+
const firstError = this.optionalStringHeader(headers, HEADERS.FIRST_ERROR);
|
|
191
|
+
const lastError = this.optionalStringHeader(headers, HEADERS.LAST_ERROR);
|
|
192
|
+
const correlationId = this.optionalStringHeader(
|
|
193
|
+
headers,
|
|
194
|
+
HEADERS.CORRELATION_ID,
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const docket: Docket = {
|
|
198
|
+
// Routing
|
|
199
|
+
eventKey,
|
|
200
|
+
targetSubscriber,
|
|
201
|
+
...(scheduledFor !== undefined && { scheduledFor }),
|
|
202
|
+
...(originalQueue !== undefined && { originalQueue }),
|
|
203
|
+
// Processing state
|
|
204
|
+
attempts,
|
|
205
|
+
createdAt,
|
|
206
|
+
...(firstError !== undefined && { firstError }),
|
|
207
|
+
...(lastError !== undefined && { lastError }),
|
|
208
|
+
// Observability
|
|
209
|
+
importance: importance as Docket['importance'],
|
|
210
|
+
...(correlationId !== undefined && { correlationId }),
|
|
211
|
+
...(body.metadata !== undefined && { metadata: body.metadata }),
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
id: body.id,
|
|
216
|
+
data: body.data,
|
|
217
|
+
docket,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Decodes a v1 format message for backwards compatibility.
|
|
223
|
+
* v1 format: { key, data, metadata, universal, targetSubscriber, ... }
|
|
224
|
+
*/
|
|
225
|
+
private decodeV1(body: V1Body, headers: Record<string, unknown>): Envelope {
|
|
226
|
+
const { event_id, user_id, correlation_id, ...otherUniversal } =
|
|
227
|
+
body.universal ?? {};
|
|
228
|
+
|
|
229
|
+
// Merge metadata: event metadata + remaining universal fields
|
|
230
|
+
const mergedMetadata: Record<string, unknown> = {};
|
|
231
|
+
if (body.metadata && typeof body.metadata === 'object') {
|
|
232
|
+
Object.assign(mergedMetadata, body.metadata);
|
|
233
|
+
}
|
|
234
|
+
if (user_id !== undefined && user_id !== null) {
|
|
235
|
+
mergedMetadata['user_id'] = user_id;
|
|
236
|
+
}
|
|
237
|
+
Object.assign(mergedMetadata, otherUniversal);
|
|
238
|
+
|
|
239
|
+
// Try to get attempts from header (v1 also uses x-matador-attempts)
|
|
240
|
+
const attempts =
|
|
241
|
+
typeof headers[HEADERS.ATTEMPTS] === 'number'
|
|
242
|
+
? (headers[HEADERS.ATTEMPTS] as number)
|
|
243
|
+
: 1;
|
|
244
|
+
|
|
245
|
+
// Try to get importance from header
|
|
246
|
+
const importance =
|
|
247
|
+
typeof headers[HEADERS.IMPORTANCE] === 'string'
|
|
248
|
+
? (headers[HEADERS.IMPORTANCE] as string)
|
|
249
|
+
: 'should-investigate';
|
|
250
|
+
|
|
251
|
+
// Get correlation ID from either v1 or v2 header
|
|
252
|
+
const correlationId =
|
|
253
|
+
(headers[HEADERS.CORRELATION_ID] as string | undefined) ??
|
|
254
|
+
(headers[HEADERS.V1_CORRELATION_ID] as string | undefined) ??
|
|
255
|
+
correlation_id ??
|
|
256
|
+
undefined;
|
|
257
|
+
|
|
258
|
+
// Get event ID from v1 header or body
|
|
259
|
+
const eventId =
|
|
260
|
+
(headers[HEADERS.V1_EVENT_ID] as string | undefined) ??
|
|
261
|
+
event_id ??
|
|
262
|
+
crypto.randomUUID();
|
|
263
|
+
|
|
264
|
+
const now = new Date().toISOString();
|
|
265
|
+
|
|
266
|
+
// Calculate scheduledFor if delayMs was specified (though it's likely already passed)
|
|
267
|
+
let scheduledFor: string | undefined;
|
|
268
|
+
if (body.options?.delayMs) {
|
|
269
|
+
scheduledFor = new Date(Date.now() + body.options.delayMs).toISOString();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const docket: Docket = {
|
|
273
|
+
// Routing
|
|
274
|
+
eventKey: body.key,
|
|
275
|
+
targetSubscriber: body.targetSubscriber,
|
|
276
|
+
...(scheduledFor !== undefined && { scheduledFor }),
|
|
277
|
+
// Processing state
|
|
278
|
+
attempts,
|
|
279
|
+
createdAt: now,
|
|
280
|
+
// Observability
|
|
281
|
+
importance: importance as Docket['importance'],
|
|
282
|
+
...(correlationId !== undefined && { correlationId }),
|
|
283
|
+
...(Object.keys(mergedMetadata).length > 0 && {
|
|
284
|
+
metadata: mergedMetadata,
|
|
285
|
+
}),
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
id: eventId,
|
|
290
|
+
data: body.data,
|
|
291
|
+
docket,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private requireStringHeader(
|
|
296
|
+
headers: Record<string, unknown>,
|
|
297
|
+
name: string,
|
|
298
|
+
): string {
|
|
299
|
+
const value = headers[name];
|
|
300
|
+
if (value === undefined) {
|
|
301
|
+
throw new CodecDecodeError(`Missing required header: ${name}`);
|
|
302
|
+
}
|
|
303
|
+
if (typeof value !== 'string') {
|
|
304
|
+
throw new CodecDecodeError(
|
|
305
|
+
`Header ${name} must be string, got ${typeof value}`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
return value;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private requireNumberHeader(
|
|
312
|
+
headers: Record<string, unknown>,
|
|
313
|
+
name: string,
|
|
314
|
+
): number {
|
|
315
|
+
const value = headers[name];
|
|
316
|
+
if (value === undefined) {
|
|
317
|
+
throw new CodecDecodeError(`Missing required header: ${name}`);
|
|
318
|
+
}
|
|
319
|
+
if (typeof value !== 'number') {
|
|
320
|
+
throw new CodecDecodeError(
|
|
321
|
+
`Header ${name} must be number, got ${typeof value}`,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
return value;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private optionalStringHeader(
|
|
328
|
+
headers: Record<string, unknown>,
|
|
329
|
+
name: string,
|
|
330
|
+
): string | undefined {
|
|
331
|
+
const value = headers[name];
|
|
332
|
+
if (value === undefined) return undefined;
|
|
333
|
+
if (typeof value !== 'string') return undefined;
|
|
334
|
+
return value;
|
|
335
|
+
}
|
|
336
|
+
}
|