@zdavison/matador 2.0.8 → 2.0.10
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/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +6 -2
- package/examples/config.ts +0 -126
- package/examples/event.ts +0 -26
- package/examples/order-event.json +0 -19
- package/src/checkpoint/context.test.ts +0 -510
- package/src/checkpoint/context.ts +0 -213
- package/src/checkpoint/index.ts +0 -30
- package/src/checkpoint/stores/memory.ts +0 -47
- package/src/checkpoint/stores/noop.ts +0 -22
- package/src/checkpoint/stores/stores.test.ts +0 -177
- package/src/checkpoint/types.ts +0 -147
- package/src/codec/codec.ts +0 -42
- package/src/codec/header-aware-codec.ts +0 -41
- package/src/codec/index.ts +0 -11
- package/src/codec/json-codec.ts +0 -69
- package/src/codec/rabbitmq-codec.test.ts +0 -516
- package/src/codec/rabbitmq-codec.ts +0 -336
- package/src/core/fanout.test.ts +0 -1350
- package/src/core/fanout.ts +0 -184
- package/src/core/index.ts +0 -12
- package/src/core/matador.test.ts +0 -575
- package/src/core/matador.ts +0 -357
- package/src/core/shutdown.test.ts +0 -853
- package/src/core/shutdown.ts +0 -165
- package/src/errors/checkpoint-errors.ts +0 -62
- package/src/errors/has-description.ts +0 -25
- package/src/errors/index.ts +0 -58
- package/src/errors/matador-errors.ts +0 -477
- package/src/errors/retry-errors.test.ts +0 -175
- package/src/errors/retry-errors.ts +0 -188
- package/src/hooks/index.ts +0 -14
- package/src/hooks/safe-hooks.ts +0 -200
- package/src/hooks/types.ts +0 -226
- package/src/index.ts +0 -241
- package/src/pipeline/index.ts +0 -2
- package/src/pipeline/pipeline.test.ts +0 -1377
- package/src/pipeline/pipeline.ts +0 -393
- package/src/retry/index.ts +0 -4
- package/src/retry/policy.ts +0 -46
- package/src/retry/standard-policy.test.ts +0 -290
- package/src/retry/standard-policy.ts +0 -156
- package/src/schema/index.ts +0 -16
- package/src/schema/registry.test.ts +0 -339
- package/src/schema/registry.ts +0 -229
- package/src/schema/types.test.ts +0 -280
- package/src/schema/types.ts +0 -217
- package/src/topology/builder.test.ts +0 -451
- package/src/topology/builder.ts +0 -238
- package/src/topology/index.ts +0 -19
- package/src/topology/types.ts +0 -183
- package/src/transport/capabilities.ts +0 -88
- package/src/transport/connection-manager.ts +0 -218
- package/src/transport/index.ts +0 -42
- package/src/transport/local/local-transport.test.ts +0 -262
- package/src/transport/local/local-transport.ts +0 -330
- package/src/transport/multi/multi-transport.test.ts +0 -320
- package/src/transport/multi/multi-transport.ts +0 -294
- package/src/transport/rabbitmq/rabbitmq-transport.test.ts +0 -120
- package/src/transport/rabbitmq/rabbitmq-transport.ts +0 -782
- package/src/transport/transport.ts +0 -200
- package/src/types/common.ts +0 -53
- package/src/types/dispatcher.ts +0 -18
- package/src/types/envelope.ts +0 -244
- package/src/types/event.test.ts +0 -157
- package/src/types/event.ts +0 -112
- package/src/types/index.ts +0 -62
- package/src/types/subscriber.ts +0 -333
- package/test/e2e/multi-transport.e2e.test.ts +0 -237
- package/test/e2e/rabbitmq-transport.e2e.test.ts +0 -618
- package/test/e2e/transport-compliance.e2e.test.ts +0 -506
- package/test/integration/matador.integration.test.ts +0 -634
- package/tsconfig.json +0 -29
- package/tsconfig.tsbuildinfo +0 -1
- package/tsup.config.ts +0 -13
package/src/schema/types.test.ts
DELETED
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'bun:test';
|
|
2
|
-
import {
|
|
3
|
-
MatadorEvent,
|
|
4
|
-
createSubscriber,
|
|
5
|
-
createSubscriberStub,
|
|
6
|
-
} from '../types/index.js';
|
|
7
|
-
import { bind, installPlugins, isSchemaEntryTuple } from './types.js';
|
|
8
|
-
import type { MatadorSchema } from './types.js';
|
|
9
|
-
|
|
10
|
-
class UserCreatedEvent extends MatadorEvent {
|
|
11
|
-
static readonly key = 'user.created';
|
|
12
|
-
static readonly description = 'User created event';
|
|
13
|
-
|
|
14
|
-
constructor(public data: { userId: string }) {
|
|
15
|
-
super();
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
class OrderPlacedEvent extends MatadorEvent {
|
|
20
|
-
static readonly key = 'order.placed';
|
|
21
|
-
static readonly description = 'Order placed event';
|
|
22
|
-
|
|
23
|
-
constructor(public data: { orderId: string }) {
|
|
24
|
-
super();
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
class ChatMessageSentEvent extends MatadorEvent {
|
|
29
|
-
static readonly key = 'chat.message.sent';
|
|
30
|
-
static readonly description = 'Chat message sent event';
|
|
31
|
-
|
|
32
|
-
constructor(public data: { messageId: string }) {
|
|
33
|
-
super();
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
describe('bind', () => {
|
|
38
|
-
it('should create a schema entry tuple', () => {
|
|
39
|
-
const subscriber = createSubscriber<UserCreatedEvent>({
|
|
40
|
-
name: 'test-sub',
|
|
41
|
-
description: 'Test subscriber',
|
|
42
|
-
callback: async () => {},
|
|
43
|
-
});
|
|
44
|
-
const entry = bind(UserCreatedEvent, [subscriber]);
|
|
45
|
-
|
|
46
|
-
expect(isSchemaEntryTuple(entry)).toBe(true);
|
|
47
|
-
expect(entry[0]).toBe(UserCreatedEvent);
|
|
48
|
-
expect(entry[1]).toHaveLength(1);
|
|
49
|
-
expect(entry[1][0]).toBe(subscriber);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe('isSchemaEntryTuple', () => {
|
|
54
|
-
it('should return true for tuple format', () => {
|
|
55
|
-
const entry = [UserCreatedEvent, []] as const;
|
|
56
|
-
expect(isSchemaEntryTuple(entry)).toBe(true);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should return false for object format', () => {
|
|
60
|
-
const entry = { eventClass: UserCreatedEvent, subscribers: [] };
|
|
61
|
-
expect(isSchemaEntryTuple(entry)).toBe(false);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
describe('installPlugins', () => {
|
|
66
|
-
it('should add plugin subscriber to all events', () => {
|
|
67
|
-
const sub1 = createSubscriber<UserCreatedEvent>({
|
|
68
|
-
name: 'user-handler',
|
|
69
|
-
description: 'Handles user events',
|
|
70
|
-
callback: async () => {},
|
|
71
|
-
});
|
|
72
|
-
const sub2 = createSubscriber<OrderPlacedEvent>({
|
|
73
|
-
name: 'order-handler',
|
|
74
|
-
description: 'Handles order events',
|
|
75
|
-
callback: async () => {},
|
|
76
|
-
});
|
|
77
|
-
const globalSub = createSubscriber<MatadorEvent>({
|
|
78
|
-
name: 'analytics',
|
|
79
|
-
description: 'Analytics subscriber',
|
|
80
|
-
callback: async () => {},
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
const baseSchema: MatadorSchema = {
|
|
84
|
-
[UserCreatedEvent.key]: [UserCreatedEvent, [sub1]],
|
|
85
|
-
[OrderPlacedEvent.key]: [OrderPlacedEvent, [sub2]],
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const schema = installPlugins(baseSchema, [{ subscriber: globalSub }]);
|
|
89
|
-
|
|
90
|
-
// Check user.created has both subscribers
|
|
91
|
-
const userEntry = schema[UserCreatedEvent.key];
|
|
92
|
-
expect(isSchemaEntryTuple(userEntry!)).toBe(true);
|
|
93
|
-
const [, userSubs] = userEntry as [unknown, readonly unknown[]];
|
|
94
|
-
expect(userSubs).toHaveLength(2);
|
|
95
|
-
expect(userSubs[0]).toBe(sub1);
|
|
96
|
-
expect(userSubs[1]).toBe(globalSub);
|
|
97
|
-
|
|
98
|
-
// Check order.placed has both subscribers
|
|
99
|
-
const orderEntry = schema[OrderPlacedEvent.key];
|
|
100
|
-
const [, orderSubs] = orderEntry as [unknown, readonly unknown[]];
|
|
101
|
-
expect(orderSubs).toHaveLength(2);
|
|
102
|
-
expect(orderSubs[0]).toBe(sub2);
|
|
103
|
-
expect(orderSubs[1]).toBe(globalSub);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('should respect exclusions', () => {
|
|
107
|
-
const sub1 = createSubscriber<UserCreatedEvent>({
|
|
108
|
-
name: 'user-handler',
|
|
109
|
-
description: 'Handles user events',
|
|
110
|
-
callback: async () => {},
|
|
111
|
-
});
|
|
112
|
-
const sub2 = createSubscriber<OrderPlacedEvent>({
|
|
113
|
-
name: 'order-handler',
|
|
114
|
-
description: 'Handles order events',
|
|
115
|
-
callback: async () => {},
|
|
116
|
-
});
|
|
117
|
-
const sub3 = createSubscriber<ChatMessageSentEvent>({
|
|
118
|
-
name: 'chat-handler',
|
|
119
|
-
description: 'Handles chat events',
|
|
120
|
-
callback: async () => {},
|
|
121
|
-
});
|
|
122
|
-
const globalSub = createSubscriber<MatadorEvent>({
|
|
123
|
-
name: 'analytics',
|
|
124
|
-
description: 'Analytics subscriber',
|
|
125
|
-
callback: async () => {},
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
const baseSchema: MatadorSchema = {
|
|
129
|
-
[UserCreatedEvent.key]: [UserCreatedEvent, [sub1]],
|
|
130
|
-
[OrderPlacedEvent.key]: [OrderPlacedEvent, [sub2]],
|
|
131
|
-
[ChatMessageSentEvent.key]: [ChatMessageSentEvent, [sub3]],
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const schema = installPlugins(baseSchema, [
|
|
135
|
-
{
|
|
136
|
-
subscriber: globalSub,
|
|
137
|
-
exclusions: [ChatMessageSentEvent.key],
|
|
138
|
-
},
|
|
139
|
-
]);
|
|
140
|
-
|
|
141
|
-
// user.created should have analytics
|
|
142
|
-
const userEntry = schema[UserCreatedEvent.key];
|
|
143
|
-
const [, userSubs] = userEntry as [unknown, readonly unknown[]];
|
|
144
|
-
expect(userSubs).toHaveLength(2);
|
|
145
|
-
|
|
146
|
-
// order.placed should have analytics
|
|
147
|
-
const orderEntry = schema[OrderPlacedEvent.key];
|
|
148
|
-
const [, orderSubs] = orderEntry as [unknown, readonly unknown[]];
|
|
149
|
-
expect(orderSubs).toHaveLength(2);
|
|
150
|
-
|
|
151
|
-
// chat.message.sent should NOT have analytics (excluded)
|
|
152
|
-
const chatEntry = schema[ChatMessageSentEvent.key];
|
|
153
|
-
const [, chatSubs] = chatEntry as [unknown, readonly unknown[]];
|
|
154
|
-
expect(chatSubs).toHaveLength(1);
|
|
155
|
-
expect(chatSubs[0]).toBe(sub3);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('should handle multiple plugins', () => {
|
|
159
|
-
const sub1 = createSubscriber<UserCreatedEvent>({
|
|
160
|
-
name: 'user-handler',
|
|
161
|
-
description: 'Handles user events',
|
|
162
|
-
callback: async () => {},
|
|
163
|
-
});
|
|
164
|
-
const analyticsSub = createSubscriber<MatadorEvent>({
|
|
165
|
-
name: 'analytics',
|
|
166
|
-
description: 'Analytics subscriber',
|
|
167
|
-
callback: async () => {},
|
|
168
|
-
});
|
|
169
|
-
const loggingSub = createSubscriber<MatadorEvent>({
|
|
170
|
-
name: 'logging',
|
|
171
|
-
description: 'Logging subscriber',
|
|
172
|
-
callback: async () => {},
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
const baseSchema: MatadorSchema = {
|
|
176
|
-
[UserCreatedEvent.key]: [UserCreatedEvent, [sub1]],
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
const schema = installPlugins(baseSchema, [
|
|
180
|
-
{ subscriber: analyticsSub },
|
|
181
|
-
{ subscriber: loggingSub },
|
|
182
|
-
]);
|
|
183
|
-
|
|
184
|
-
const entry = schema[UserCreatedEvent.key];
|
|
185
|
-
const [, subs] = entry as [unknown, readonly unknown[]];
|
|
186
|
-
expect(subs).toHaveLength(3);
|
|
187
|
-
expect(subs[0]).toBe(sub1);
|
|
188
|
-
expect(subs[1]).toBe(analyticsSub);
|
|
189
|
-
expect(subs[2]).toBe(loggingSub);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('should handle object format schema entries', () => {
|
|
193
|
-
const sub1 = createSubscriber<UserCreatedEvent>({
|
|
194
|
-
name: 'user-handler',
|
|
195
|
-
description: 'Handles user events',
|
|
196
|
-
callback: async () => {},
|
|
197
|
-
});
|
|
198
|
-
const globalSub = createSubscriber<MatadorEvent>({
|
|
199
|
-
name: 'analytics',
|
|
200
|
-
description: 'Analytics subscriber',
|
|
201
|
-
callback: async () => {},
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
const baseSchema: MatadorSchema = {
|
|
205
|
-
[UserCreatedEvent.key]: {
|
|
206
|
-
eventClass: UserCreatedEvent,
|
|
207
|
-
subscribers: [sub1],
|
|
208
|
-
},
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
const schema = installPlugins(baseSchema, [{ subscriber: globalSub }]);
|
|
212
|
-
|
|
213
|
-
const entry = schema[UserCreatedEvent.key];
|
|
214
|
-
expect(isSchemaEntryTuple(entry!)).toBe(true);
|
|
215
|
-
const [eventClass, subs] = entry as [unknown, readonly unknown[]];
|
|
216
|
-
expect(eventClass).toBe(UserCreatedEvent);
|
|
217
|
-
expect(subs).toHaveLength(2);
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
it('should work with subscriber stubs', () => {
|
|
221
|
-
const sub1 = createSubscriber<UserCreatedEvent>({
|
|
222
|
-
name: 'user-handler',
|
|
223
|
-
description: 'Handles user events',
|
|
224
|
-
callback: async () => {},
|
|
225
|
-
});
|
|
226
|
-
const stubSub = createSubscriberStub({
|
|
227
|
-
name: 'remote-analytics',
|
|
228
|
-
targetQueue: 'analytics-worker',
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
const baseSchema: MatadorSchema = {
|
|
232
|
-
[UserCreatedEvent.key]: [UserCreatedEvent, [sub1]],
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
const schema = installPlugins(baseSchema, [{ subscriber: stubSub }]);
|
|
236
|
-
|
|
237
|
-
const entry = schema[UserCreatedEvent.key];
|
|
238
|
-
const [, subs] = entry as [unknown, readonly unknown[]];
|
|
239
|
-
expect(subs).toHaveLength(2);
|
|
240
|
-
expect(subs[1]).toBe(stubSub);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it('should return empty schema for empty input', () => {
|
|
244
|
-
const schema = installPlugins({}, [
|
|
245
|
-
{
|
|
246
|
-
subscriber: createSubscriber<MatadorEvent>({
|
|
247
|
-
name: 'analytics',
|
|
248
|
-
description: 'Analytics subscriber',
|
|
249
|
-
callback: async () => {},
|
|
250
|
-
}),
|
|
251
|
-
},
|
|
252
|
-
]);
|
|
253
|
-
|
|
254
|
-
expect(Object.keys(schema)).toHaveLength(0);
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
it('should not modify original schema', () => {
|
|
258
|
-
const sub1 = createSubscriber<UserCreatedEvent>({
|
|
259
|
-
name: 'user-handler',
|
|
260
|
-
description: 'Handles user events',
|
|
261
|
-
callback: async () => {},
|
|
262
|
-
});
|
|
263
|
-
const globalSub = createSubscriber<MatadorEvent>({
|
|
264
|
-
name: 'analytics',
|
|
265
|
-
description: 'Analytics subscriber',
|
|
266
|
-
callback: async () => {},
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
const baseSchema: MatadorSchema = {
|
|
270
|
-
[UserCreatedEvent.key]: [UserCreatedEvent, [sub1]],
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
installPlugins(baseSchema, [{ subscriber: globalSub }]);
|
|
274
|
-
|
|
275
|
-
// Original schema should be unchanged
|
|
276
|
-
const entry = baseSchema[UserCreatedEvent.key];
|
|
277
|
-
const [, subs] = entry as [unknown, readonly unknown[]];
|
|
278
|
-
expect(subs).toHaveLength(1);
|
|
279
|
-
});
|
|
280
|
-
});
|
package/src/schema/types.ts
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AnySubscriber,
|
|
3
|
-
EventClass,
|
|
4
|
-
MatadorEvent,
|
|
5
|
-
Subscriber,
|
|
6
|
-
SubscriberStub,
|
|
7
|
-
} from '../types/index.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Type-safe schema entry for a single event type (object format).
|
|
11
|
-
* The type parameter enforces that subscribers match the event type.
|
|
12
|
-
*/
|
|
13
|
-
export interface SchemaEntry<T extends MatadorEvent = MatadorEvent> {
|
|
14
|
-
/** The event class */
|
|
15
|
-
readonly eventClass: EventClass<T['data']>;
|
|
16
|
-
|
|
17
|
-
/** Subscribers for this event */
|
|
18
|
-
readonly subscribers: readonly (Subscriber<T> | SubscriberStub)[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Type-safe schema entry as a tuple: [EventClass, Subscribers[]]
|
|
23
|
-
* The type parameter enforces that subscribers match the event type.
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* ```typescript
|
|
27
|
-
* const entry: SchemaEntryTuple<UserCreatedEvent> = [
|
|
28
|
-
* UserCreatedEvent,
|
|
29
|
-
* [createSubscriber<UserCreatedEvent>('handler', async (env) => {})]
|
|
30
|
-
* ];
|
|
31
|
-
* ```
|
|
32
|
-
*/
|
|
33
|
-
export type SchemaEntryTuple<T extends MatadorEvent = MatadorEvent> = readonly [
|
|
34
|
-
eventClass: EventClass<T['data']>,
|
|
35
|
-
subscribers: readonly (Subscriber<T> | SubscriberStub)[],
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Creates a type-safe schema entry tuple.
|
|
40
|
-
* Ensures that subscribers are compatible with the event class at compile time.
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
43
|
-
* ```typescript
|
|
44
|
-
* const schema = {
|
|
45
|
-
* [UserCreatedEvent.key]: bind(UserCreatedEvent, [
|
|
46
|
-
* createSubscriber<UserCreatedEvent>('send-email', async (env) => {
|
|
47
|
-
* console.log(env.data.email); // Type-safe access
|
|
48
|
-
* }),
|
|
49
|
-
* ]),
|
|
50
|
-
* } satisfies MatadorSchema;
|
|
51
|
-
* ```
|
|
52
|
-
*/
|
|
53
|
-
export function bind<T extends MatadorEvent>(
|
|
54
|
-
eventClass: EventClass<T['data']>,
|
|
55
|
-
subscribers: readonly (Subscriber<T> | SubscriberStub)[],
|
|
56
|
-
): SchemaEntryTuple<T> {
|
|
57
|
-
return [eventClass, subscribers] as const;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Minimal type for event class in schema storage.
|
|
62
|
-
* Only requires the static `key` property for routing.
|
|
63
|
-
* Constructor constraint is omitted to allow heterogeneous event types.
|
|
64
|
-
*/
|
|
65
|
-
// biome-ignore lint/suspicious/noExplicitAny: Constructor accepts any data type for variance compatibility
|
|
66
|
-
export type AnyEventClass = {
|
|
67
|
-
readonly key: string;
|
|
68
|
-
new (data: any): MatadorEvent;
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Runtime schema entry stored in MatadorSchema (object format).
|
|
73
|
-
* Uses AnySubscriber and AnyEventClass to allow heterogeneous event types.
|
|
74
|
-
* Type safety is enforced at definition time via `schemaEntry()` helper.
|
|
75
|
-
*/
|
|
76
|
-
export interface RuntimeSchemaEntry {
|
|
77
|
-
readonly eventClass: AnyEventClass;
|
|
78
|
-
readonly subscribers: readonly AnySubscriber[];
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Runtime schema tuple stored in MatadorSchema.
|
|
83
|
-
* Uses AnySubscriber and AnyEventClass to allow heterogeneous event types.
|
|
84
|
-
* Type safety is enforced at definition time via `schemaEntry()` helper.
|
|
85
|
-
*/
|
|
86
|
-
export type RuntimeSchemaEntryTuple = readonly [
|
|
87
|
-
eventClass: AnyEventClass,
|
|
88
|
-
subscribers: readonly AnySubscriber[],
|
|
89
|
-
];
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Matador schema mapping event keys to their definitions.
|
|
93
|
-
* Supports both object format (SchemaEntry) and tuple format (SchemaEntryTuple).
|
|
94
|
-
*
|
|
95
|
-
* For type-safe schema definitions, use the `bind` helper function
|
|
96
|
-
* which ensures subscribers are compatible with their event class.
|
|
97
|
-
*
|
|
98
|
-
* @example
|
|
99
|
-
* ```typescript
|
|
100
|
-
* // Type-safe with bind helper:
|
|
101
|
-
* const schema = {
|
|
102
|
-
* [UserCreatedEvent.key]: bind(UserCreatedEvent, [emailSubscriber]),
|
|
103
|
-
* } satisfies MatadorSchema;
|
|
104
|
-
*
|
|
105
|
-
* // Or inline (less type-safe):
|
|
106
|
-
* const schema: MatadorSchema = {
|
|
107
|
-
* [UserCreatedEvent.key]: [UserCreatedEvent, [emailSubscriber]],
|
|
108
|
-
* };
|
|
109
|
-
* ```
|
|
110
|
-
*/
|
|
111
|
-
export type MatadorSchema = {
|
|
112
|
-
readonly [eventKey: string]: RuntimeSchemaEntry | RuntimeSchemaEntryTuple;
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Type guard to check if a schema entry is in tuple format.
|
|
117
|
-
*/
|
|
118
|
-
export function isSchemaEntryTuple(
|
|
119
|
-
entry: RuntimeSchemaEntry | RuntimeSchemaEntryTuple,
|
|
120
|
-
): entry is RuntimeSchemaEntryTuple {
|
|
121
|
-
return Array.isArray(entry);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Options for schema registration.
|
|
126
|
-
*/
|
|
127
|
-
export interface RegisterOptions {
|
|
128
|
-
/** Override existing registration if present */
|
|
129
|
-
readonly override?: boolean;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Result of schema validation.
|
|
134
|
-
*/
|
|
135
|
-
export interface SchemaValidationResult {
|
|
136
|
-
readonly valid: boolean;
|
|
137
|
-
readonly issues: readonly SchemaIssue[];
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Individual schema issue.
|
|
142
|
-
*/
|
|
143
|
-
export interface SchemaIssue {
|
|
144
|
-
readonly severity: 'error' | 'warning';
|
|
145
|
-
readonly eventKey: string;
|
|
146
|
-
readonly message: string;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Plugin definition for adding subscribers to multiple events.
|
|
151
|
-
*/
|
|
152
|
-
export interface SchemaPlugin {
|
|
153
|
-
/** The subscriber to add to events */
|
|
154
|
-
readonly subscriber: AnySubscriber;
|
|
155
|
-
|
|
156
|
-
/** Event keys to exclude from this plugin */
|
|
157
|
-
readonly exclusions?: readonly string[];
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Installs plugins (global subscribers) into a schema.
|
|
162
|
-
* Each plugin subscriber is added to all events except those in its exclusions list.
|
|
163
|
-
*
|
|
164
|
-
* @param schema - The base schema to extend
|
|
165
|
-
* @param plugins - Array of plugins to install
|
|
166
|
-
* @returns A new schema with plugin subscribers added
|
|
167
|
-
*
|
|
168
|
-
* @example
|
|
169
|
-
* ```typescript
|
|
170
|
-
* const baseSchema: MatadorSchema = {
|
|
171
|
-
* [UserCreatedEvent.key]: [UserCreatedEvent, [sendWelcomeEmail]],
|
|
172
|
-
* [OrderPlacedEvent.key]: [OrderPlacedEvent, [processOrder]],
|
|
173
|
-
* [ChatMessageSent.key]: [ChatMessageSent, [notifyRecipient]],
|
|
174
|
-
* };
|
|
175
|
-
*
|
|
176
|
-
* const schema = installPlugins(baseSchema, [
|
|
177
|
-
* {
|
|
178
|
-
* subscriber: logToBigQuery,
|
|
179
|
-
* exclusions: [ChatMessageSent.key], // Don't log chat messages
|
|
180
|
-
* },
|
|
181
|
-
* {
|
|
182
|
-
* subscriber: trackAnalytics,
|
|
183
|
-
* // No exclusions - added to all events
|
|
184
|
-
* },
|
|
185
|
-
* ]);
|
|
186
|
-
* ```
|
|
187
|
-
*/
|
|
188
|
-
export function installPlugins(
|
|
189
|
-
schema: MatadorSchema,
|
|
190
|
-
plugins: readonly SchemaPlugin[],
|
|
191
|
-
): MatadorSchema {
|
|
192
|
-
const result: Record<string, RuntimeSchemaEntry | RuntimeSchemaEntryTuple> =
|
|
193
|
-
{};
|
|
194
|
-
|
|
195
|
-
for (const [eventKey, entry] of Object.entries(schema)) {
|
|
196
|
-
// Get existing event class and subscribers
|
|
197
|
-
const [eventClass, existingSubscribers] = isSchemaEntryTuple(entry)
|
|
198
|
-
? [entry[0], entry[1]]
|
|
199
|
-
: [entry.eventClass, entry.subscribers];
|
|
200
|
-
|
|
201
|
-
// Collect subscribers to add from plugins
|
|
202
|
-
const pluginSubscribers: AnySubscriber[] = [];
|
|
203
|
-
for (const plugin of plugins) {
|
|
204
|
-
if (!plugin.exclusions?.includes(eventKey)) {
|
|
205
|
-
pluginSubscribers.push(plugin.subscriber);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Create new entry with combined subscribers
|
|
210
|
-
result[eventKey] = [
|
|
211
|
-
eventClass,
|
|
212
|
-
[...existingSubscribers, ...pluginSubscribers],
|
|
213
|
-
];
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return result;
|
|
217
|
-
}
|