@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,226 @@
|
|
|
1
|
+
import type { HasDescription } from '../errors/index.js';
|
|
2
|
+
import type {
|
|
3
|
+
AnySubscriber,
|
|
4
|
+
EventClass,
|
|
5
|
+
MatadorEvent,
|
|
6
|
+
Subscriber,
|
|
7
|
+
SubscriberDefinition,
|
|
8
|
+
} from '../types/index.js';
|
|
9
|
+
import { isSubscriber } from '../types/index.js';
|
|
10
|
+
import type {
|
|
11
|
+
RegisterOptions,
|
|
12
|
+
SchemaEntry,
|
|
13
|
+
SchemaIssue,
|
|
14
|
+
SchemaValidationResult,
|
|
15
|
+
} from './types.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Error thrown when schema operations fail.
|
|
19
|
+
*/
|
|
20
|
+
export class SchemaError extends Error implements HasDescription {
|
|
21
|
+
readonly description =
|
|
22
|
+
'A schema operation failed, such as registering a duplicate event or ' +
|
|
23
|
+
'conflicting alias. This is a configuration error that should be fixed ' +
|
|
24
|
+
'during development. Review the event and subscriber registrations to ' +
|
|
25
|
+
'resolve the conflict.';
|
|
26
|
+
|
|
27
|
+
constructor(message: string) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = 'SchemaError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Registry for managing event-subscriber relationships.
|
|
35
|
+
*/
|
|
36
|
+
export class SchemaRegistry {
|
|
37
|
+
private readonly entries = new Map<string, SchemaEntry>();
|
|
38
|
+
private readonly aliases = new Map<string, string>();
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Registers an event class with its subscribers.
|
|
42
|
+
*/
|
|
43
|
+
register<T>(
|
|
44
|
+
eventClass: EventClass<T>,
|
|
45
|
+
subscribers: readonly AnySubscriber[],
|
|
46
|
+
options: RegisterOptions = {},
|
|
47
|
+
): void {
|
|
48
|
+
const key = eventClass.key;
|
|
49
|
+
|
|
50
|
+
if (this.entries.has(key) && !options.override) {
|
|
51
|
+
throw new SchemaError(`Event "${key}" is already registered`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.entries.set(key, {
|
|
55
|
+
eventClass,
|
|
56
|
+
subscribers,
|
|
57
|
+
} as SchemaEntry);
|
|
58
|
+
|
|
59
|
+
// Register aliases
|
|
60
|
+
if (eventClass.aliases) {
|
|
61
|
+
for (const alias of eventClass.aliases) {
|
|
62
|
+
if (
|
|
63
|
+
this.aliases.has(alias) &&
|
|
64
|
+
this.aliases.get(alias) !== key &&
|
|
65
|
+
!options.override
|
|
66
|
+
) {
|
|
67
|
+
throw new SchemaError(
|
|
68
|
+
`Alias "${alias}" is already registered for event "${this.aliases.get(alias)}"`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
this.aliases.set(alias, key);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Gets the event class for a given key.
|
|
78
|
+
*/
|
|
79
|
+
getEventClass(key: string): EventClass | undefined {
|
|
80
|
+
const entry = this.entries.get(key);
|
|
81
|
+
if (entry) return entry.eventClass;
|
|
82
|
+
|
|
83
|
+
// Check aliases
|
|
84
|
+
const aliasKey = this.aliases.get(key);
|
|
85
|
+
if (aliasKey) {
|
|
86
|
+
return this.entries.get(aliasKey)?.eventClass;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Gets all subscribers for an event key.
|
|
94
|
+
*/
|
|
95
|
+
getSubscribers(eventKey: string): readonly AnySubscriber[] {
|
|
96
|
+
const entry = this.getEntry(eventKey);
|
|
97
|
+
return entry?.subscribers ?? [];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Gets a specific subscriber by event key and subscriber name.
|
|
102
|
+
*/
|
|
103
|
+
getSubscriber(
|
|
104
|
+
eventKey: string,
|
|
105
|
+
subscriberName: string,
|
|
106
|
+
): AnySubscriber | undefined {
|
|
107
|
+
const subscribers = this.getSubscribers(eventKey);
|
|
108
|
+
return subscribers.find((s) => s.name === subscriberName);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Gets a subscriber definition for the pipeline.
|
|
113
|
+
*/
|
|
114
|
+
getSubscriberDefinition(
|
|
115
|
+
eventKey: string,
|
|
116
|
+
subscriberName: string,
|
|
117
|
+
): SubscriberDefinition | undefined {
|
|
118
|
+
const subscriber = this.getSubscriber(eventKey, subscriberName);
|
|
119
|
+
if (!subscriber) return undefined;
|
|
120
|
+
|
|
121
|
+
const def: SubscriberDefinition = {
|
|
122
|
+
name: subscriber.name,
|
|
123
|
+
description: subscriber.description,
|
|
124
|
+
idempotent: subscriber.idempotent ?? 'unknown',
|
|
125
|
+
importance: subscriber.importance ?? 'should-investigate',
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
if (subscriber.targetQueue !== undefined) {
|
|
129
|
+
(def as { targetQueue?: string }).targetQueue = subscriber.targetQueue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return def;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Gets a subscriber with callback for execution.
|
|
137
|
+
*/
|
|
138
|
+
getExecutableSubscriber(
|
|
139
|
+
eventKey: string,
|
|
140
|
+
subscriberName: string,
|
|
141
|
+
): Subscriber<MatadorEvent<unknown>> | undefined {
|
|
142
|
+
const subscriber = this.getSubscriber(eventKey, subscriberName);
|
|
143
|
+
if (!subscriber || !isSubscriber(subscriber)) return undefined;
|
|
144
|
+
return subscriber;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Gets the event class by alias.
|
|
149
|
+
*/
|
|
150
|
+
getEventByAlias(alias: string): EventClass | undefined {
|
|
151
|
+
const key = this.aliases.get(alias);
|
|
152
|
+
if (!key) return undefined;
|
|
153
|
+
return this.entries.get(key)?.eventClass;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Checks if an event key is registered.
|
|
158
|
+
*/
|
|
159
|
+
hasEvent(key: string): boolean {
|
|
160
|
+
return this.entries.has(key) || this.aliases.has(key);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Gets all registered event keys.
|
|
165
|
+
*/
|
|
166
|
+
getEventKeys(): readonly string[] {
|
|
167
|
+
return [...this.entries.keys()];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Validates the schema for issues.
|
|
172
|
+
*/
|
|
173
|
+
validate(): SchemaValidationResult {
|
|
174
|
+
const issues: SchemaIssue[] = [];
|
|
175
|
+
|
|
176
|
+
for (const [key, entry] of this.entries) {
|
|
177
|
+
// Check for duplicate subscriber names
|
|
178
|
+
const names = new Set<string>();
|
|
179
|
+
for (const subscriber of entry.subscribers) {
|
|
180
|
+
if (names.has(subscriber.name)) {
|
|
181
|
+
issues.push({
|
|
182
|
+
severity: 'error',
|
|
183
|
+
eventKey: key,
|
|
184
|
+
message: `Duplicate subscriber name: "${subscriber.name}"`,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
names.add(subscriber.name);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Check for subscribers without callbacks (stubs in consuming instance)
|
|
191
|
+
const stubs = entry.subscribers.filter((s) => !isSubscriber(s));
|
|
192
|
+
if (stubs.length === entry.subscribers.length && stubs.length > 0) {
|
|
193
|
+
issues.push({
|
|
194
|
+
severity: 'warning',
|
|
195
|
+
eventKey: key,
|
|
196
|
+
message: 'All subscribers are stubs, no local handlers',
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
valid: issues.filter((i) => i.severity === 'error').length === 0,
|
|
203
|
+
issues,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Clears all registered entries.
|
|
209
|
+
*/
|
|
210
|
+
clear(): void {
|
|
211
|
+
this.entries.clear();
|
|
212
|
+
this.aliases.clear();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private getEntry(eventKey: string): SchemaEntry | undefined {
|
|
216
|
+
const entry = this.entries.get(eventKey);
|
|
217
|
+
if (entry) return entry;
|
|
218
|
+
|
|
219
|
+
const aliasKey = this.aliases.get(eventKey);
|
|
220
|
+
if (aliasKey) {
|
|
221
|
+
return this.entries.get(aliasKey);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
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
|
+
description: 'Remote analytics stub',
|
|
229
|
+
targetQueue: 'analytics-worker',
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const baseSchema: MatadorSchema = {
|
|
233
|
+
[UserCreatedEvent.key]: [UserCreatedEvent, [sub1]],
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const schema = installPlugins(baseSchema, [{ subscriber: stubSub }]);
|
|
237
|
+
|
|
238
|
+
const entry = schema[UserCreatedEvent.key];
|
|
239
|
+
const [, subs] = entry as [unknown, readonly unknown[]];
|
|
240
|
+
expect(subs).toHaveLength(2);
|
|
241
|
+
expect(subs[1]).toBe(stubSub);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should return empty schema for empty input', () => {
|
|
245
|
+
const schema = installPlugins({}, [
|
|
246
|
+
{
|
|
247
|
+
subscriber: createSubscriber<MatadorEvent>({
|
|
248
|
+
name: 'analytics',
|
|
249
|
+
description: 'Analytics subscriber',
|
|
250
|
+
callback: async () => {},
|
|
251
|
+
}),
|
|
252
|
+
},
|
|
253
|
+
]);
|
|
254
|
+
|
|
255
|
+
expect(Object.keys(schema)).toHaveLength(0);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should not modify original schema', () => {
|
|
259
|
+
const sub1 = createSubscriber<UserCreatedEvent>({
|
|
260
|
+
name: 'user-handler',
|
|
261
|
+
description: 'Handles user events',
|
|
262
|
+
callback: async () => {},
|
|
263
|
+
});
|
|
264
|
+
const globalSub = createSubscriber<MatadorEvent>({
|
|
265
|
+
name: 'analytics',
|
|
266
|
+
description: 'Analytics subscriber',
|
|
267
|
+
callback: async () => {},
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const baseSchema: MatadorSchema = {
|
|
271
|
+
[UserCreatedEvent.key]: [UserCreatedEvent, [sub1]],
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
installPlugins(baseSchema, [{ subscriber: globalSub }]);
|
|
275
|
+
|
|
276
|
+
// Original schema should be unchanged
|
|
277
|
+
const entry = baseSchema[UserCreatedEvent.key];
|
|
278
|
+
const [, subs] = entry as [unknown, readonly unknown[]];
|
|
279
|
+
expect(subs).toHaveLength(1);
|
|
280
|
+
});
|
|
281
|
+
});
|