@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.
Files changed (256) hide show
  1. package/cli.ts +453 -0
  2. package/dist/checkpoint/context.d.ts +59 -0
  3. package/dist/checkpoint/context.d.ts.map +1 -0
  4. package/dist/checkpoint/context.js +140 -0
  5. package/dist/checkpoint/context.test.d.ts +2 -0
  6. package/dist/checkpoint/context.test.d.ts.map +1 -0
  7. package/dist/checkpoint/context.test.js +424 -0
  8. package/dist/checkpoint/index.d.ts +7 -0
  9. package/dist/checkpoint/index.d.ts.map +1 -0
  10. package/dist/checkpoint/index.js +6 -0
  11. package/dist/checkpoint/stores/memory.d.ts +29 -0
  12. package/dist/checkpoint/stores/memory.d.ts.map +1 -0
  13. package/dist/checkpoint/stores/memory.js +39 -0
  14. package/dist/checkpoint/stores/noop.d.ts +14 -0
  15. package/dist/checkpoint/stores/noop.d.ts.map +1 -0
  16. package/dist/checkpoint/stores/noop.js +18 -0
  17. package/dist/checkpoint/stores/stores.test.d.ts +2 -0
  18. package/dist/checkpoint/stores/stores.test.d.ts.map +1 -0
  19. package/dist/checkpoint/stores/stores.test.js +146 -0
  20. package/dist/checkpoint/types.d.ts +119 -0
  21. package/dist/checkpoint/types.d.ts.map +1 -0
  22. package/dist/checkpoint/types.js +1 -0
  23. package/dist/codec/codec.d.ts +29 -0
  24. package/dist/codec/codec.d.ts.map +1 -0
  25. package/dist/codec/codec.js +15 -0
  26. package/dist/codec/header-aware-codec.d.ts +36 -0
  27. package/dist/codec/header-aware-codec.d.ts.map +1 -0
  28. package/dist/codec/header-aware-codec.js +1 -0
  29. package/dist/codec/index.d.ts +6 -0
  30. package/dist/codec/index.d.ts.map +1 -0
  31. package/dist/codec/index.js +3 -0
  32. package/dist/codec/json-codec.d.ts +13 -0
  33. package/dist/codec/json-codec.d.ts.map +1 -0
  34. package/dist/codec/json-codec.js +64 -0
  35. package/dist/codec/rabbitmq-codec.d.ts +28 -0
  36. package/dist/codec/rabbitmq-codec.d.ts.map +1 -0
  37. package/dist/codec/rabbitmq-codec.js +242 -0
  38. package/dist/codec/rabbitmq-codec.test.d.ts +2 -0
  39. package/dist/codec/rabbitmq-codec.test.d.ts.map +1 -0
  40. package/dist/codec/rabbitmq-codec.test.js +433 -0
  41. package/dist/core/fanout.d.ts +59 -0
  42. package/dist/core/fanout.d.ts.map +1 -0
  43. package/dist/core/fanout.js +121 -0
  44. package/dist/core/fanout.test.d.ts +2 -0
  45. package/dist/core/fanout.test.d.ts.map +1 -0
  46. package/dist/core/fanout.test.js +1055 -0
  47. package/dist/core/index.d.ts +7 -0
  48. package/dist/core/index.d.ts.map +1 -0
  49. package/dist/core/index.js +3 -0
  50. package/dist/core/matador.d.ts +133 -0
  51. package/dist/core/matador.d.ts.map +1 -0
  52. package/dist/core/matador.js +222 -0
  53. package/dist/core/matador.test.d.ts +2 -0
  54. package/dist/core/matador.test.d.ts.map +1 -0
  55. package/dist/core/matador.test.js +446 -0
  56. package/dist/core/shutdown.d.ts +78 -0
  57. package/dist/core/shutdown.d.ts.map +1 -0
  58. package/dist/core/shutdown.js +111 -0
  59. package/dist/core/shutdown.test.d.ts +2 -0
  60. package/dist/core/shutdown.test.d.ts.map +1 -0
  61. package/dist/core/shutdown.test.js +524 -0
  62. package/dist/errors/checkpoint-errors.d.ts +30 -0
  63. package/dist/errors/checkpoint-errors.d.ts.map +1 -0
  64. package/dist/errors/checkpoint-errors.js +49 -0
  65. package/dist/errors/has-description.d.ts +18 -0
  66. package/dist/errors/has-description.d.ts.map +1 -0
  67. package/dist/errors/has-description.js +9 -0
  68. package/dist/errors/index.d.ts +6 -0
  69. package/dist/errors/index.d.ts.map +1 -0
  70. package/dist/errors/index.js +22 -0
  71. package/dist/errors/matador-errors.d.ts +183 -0
  72. package/dist/errors/matador-errors.d.ts.map +1 -0
  73. package/dist/errors/matador-errors.js +376 -0
  74. package/dist/errors/retry-errors.d.ts +67 -0
  75. package/dist/errors/retry-errors.d.ts.map +1 -0
  76. package/dist/errors/retry-errors.js +108 -0
  77. package/dist/hooks/index.d.ts +4 -0
  78. package/dist/hooks/index.d.ts.map +1 -0
  79. package/dist/hooks/index.js +2 -0
  80. package/dist/hooks/safe-hooks.d.ts +34 -0
  81. package/dist/hooks/safe-hooks.d.ts.map +1 -0
  82. package/dist/hooks/safe-hooks.js +135 -0
  83. package/dist/hooks/types.d.ts +189 -0
  84. package/dist/hooks/types.d.ts.map +1 -0
  85. package/dist/hooks/types.js +9 -0
  86. package/dist/index.cjs +3156 -0
  87. package/dist/index.cjs.map +1 -0
  88. package/dist/index.d.cts +23 -0
  89. package/dist/index.d.ts +23 -0
  90. package/dist/index.d.ts.map +1 -0
  91. package/dist/index.js +31 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/pipeline/index.d.ts +3 -0
  94. package/dist/pipeline/index.d.ts.map +1 -0
  95. package/dist/pipeline/index.js +1 -0
  96. package/dist/pipeline/pipeline.d.ts +60 -0
  97. package/dist/pipeline/pipeline.d.ts.map +1 -0
  98. package/dist/pipeline/pipeline.js +261 -0
  99. package/dist/pipeline/pipeline.test.d.ts +2 -0
  100. package/dist/pipeline/pipeline.test.d.ts.map +1 -0
  101. package/dist/pipeline/pipeline.test.js +1065 -0
  102. package/dist/retry/index.d.ts +4 -0
  103. package/dist/retry/index.d.ts.map +1 -0
  104. package/dist/retry/index.js +1 -0
  105. package/dist/retry/policy.d.ts +43 -0
  106. package/dist/retry/policy.d.ts.map +1 -0
  107. package/dist/retry/policy.js +1 -0
  108. package/dist/retry/standard-policy.d.ts +44 -0
  109. package/dist/retry/standard-policy.d.ts.map +1 -0
  110. package/dist/retry/standard-policy.js +102 -0
  111. package/dist/retry/standard-policy.test.d.ts +2 -0
  112. package/dist/retry/standard-policy.test.d.ts.map +1 -0
  113. package/dist/retry/standard-policy.test.js +190 -0
  114. package/dist/schema/index.d.ts +4 -0
  115. package/dist/schema/index.d.ts.map +1 -0
  116. package/dist/schema/index.js +2 -0
  117. package/dist/schema/registry.d.ts +63 -0
  118. package/dist/schema/registry.d.ts.map +1 -0
  119. package/dist/schema/registry.js +171 -0
  120. package/dist/schema/registry.test.d.ts +2 -0
  121. package/dist/schema/registry.test.d.ts.map +1 -0
  122. package/dist/schema/registry.test.js +278 -0
  123. package/dist/schema/types.d.ts +158 -0
  124. package/dist/schema/types.d.ts.map +1 -0
  125. package/dist/schema/types.js +74 -0
  126. package/dist/schema/types.test.d.ts +2 -0
  127. package/dist/schema/types.test.d.ts.map +1 -0
  128. package/dist/schema/types.test.js +243 -0
  129. package/dist/topology/builder.d.ts +66 -0
  130. package/dist/topology/builder.d.ts.map +1 -0
  131. package/dist/topology/builder.js +156 -0
  132. package/dist/topology/builder.test.d.ts +2 -0
  133. package/dist/topology/builder.test.d.ts.map +1 -0
  134. package/dist/topology/builder.test.js +222 -0
  135. package/dist/topology/index.d.ts +5 -0
  136. package/dist/topology/index.d.ts.map +1 -0
  137. package/dist/topology/index.js +2 -0
  138. package/dist/topology/types.d.ts +76 -0
  139. package/dist/topology/types.d.ts.map +1 -0
  140. package/dist/topology/types.js +18 -0
  141. package/dist/transport/capabilities.d.ts +66 -0
  142. package/dist/transport/capabilities.d.ts.map +1 -0
  143. package/dist/transport/capabilities.js +18 -0
  144. package/dist/transport/connection-manager.d.ts +95 -0
  145. package/dist/transport/connection-manager.d.ts.map +1 -0
  146. package/dist/transport/connection-manager.js +144 -0
  147. package/dist/transport/index.d.ts +11 -0
  148. package/dist/transport/index.d.ts.map +1 -0
  149. package/dist/transport/index.js +5 -0
  150. package/dist/transport/local/local-transport.d.ts +62 -0
  151. package/dist/transport/local/local-transport.d.ts.map +1 -0
  152. package/dist/transport/local/local-transport.js +241 -0
  153. package/dist/transport/local/local-transport.test.d.ts +2 -0
  154. package/dist/transport/local/local-transport.test.d.ts.map +1 -0
  155. package/dist/transport/local/local-transport.test.js +192 -0
  156. package/dist/transport/multi/multi-transport.d.ts +94 -0
  157. package/dist/transport/multi/multi-transport.d.ts.map +1 -0
  158. package/dist/transport/multi/multi-transport.js +184 -0
  159. package/dist/transport/multi/multi-transport.test.d.ts +2 -0
  160. package/dist/transport/multi/multi-transport.test.d.ts.map +1 -0
  161. package/dist/transport/multi/multi-transport.test.js +236 -0
  162. package/dist/transport/rabbitmq/rabbitmq-transport.d.ts +73 -0
  163. package/dist/transport/rabbitmq/rabbitmq-transport.d.ts.map +1 -0
  164. package/dist/transport/rabbitmq/rabbitmq-transport.js +518 -0
  165. package/dist/transport/transport.d.ts +152 -0
  166. package/dist/transport/transport.d.ts.map +1 -0
  167. package/dist/transport/transport.js +1 -0
  168. package/dist/types/common.d.ts +41 -0
  169. package/dist/types/common.d.ts.map +1 -0
  170. package/dist/types/common.js +12 -0
  171. package/dist/types/envelope.d.ts +82 -0
  172. package/dist/types/envelope.d.ts.map +1 -0
  173. package/dist/types/envelope.js +36 -0
  174. package/dist/types/event.d.ts +96 -0
  175. package/dist/types/event.d.ts.map +1 -0
  176. package/dist/types/event.js +26 -0
  177. package/dist/types/event.test.d.ts +2 -0
  178. package/dist/types/event.test.d.ts.map +1 -0
  179. package/dist/types/event.test.js +130 -0
  180. package/dist/types/index.d.ts +9 -0
  181. package/dist/types/index.d.ts.map +1 -0
  182. package/dist/types/index.js +4 -0
  183. package/dist/types/subscriber.d.ts +207 -0
  184. package/dist/types/subscriber.d.ts.map +1 -0
  185. package/dist/types/subscriber.js +99 -0
  186. package/examples/config.ts +126 -0
  187. package/examples/event.ts +26 -0
  188. package/examples/order-event.json +19 -0
  189. package/package.json +66 -0
  190. package/src/checkpoint/context.test.ts +510 -0
  191. package/src/checkpoint/context.ts +213 -0
  192. package/src/checkpoint/index.ts +30 -0
  193. package/src/checkpoint/stores/memory.ts +47 -0
  194. package/src/checkpoint/stores/noop.ts +22 -0
  195. package/src/checkpoint/stores/stores.test.ts +177 -0
  196. package/src/checkpoint/types.ts +147 -0
  197. package/src/codec/codec.ts +42 -0
  198. package/src/codec/header-aware-codec.ts +41 -0
  199. package/src/codec/index.ts +11 -0
  200. package/src/codec/json-codec.ts +69 -0
  201. package/src/codec/rabbitmq-codec.test.ts +516 -0
  202. package/src/codec/rabbitmq-codec.ts +336 -0
  203. package/src/core/fanout.test.ts +1351 -0
  204. package/src/core/fanout.ts +184 -0
  205. package/src/core/index.ts +12 -0
  206. package/src/core/matador.test.ts +575 -0
  207. package/src/core/matador.ts +344 -0
  208. package/src/core/shutdown.test.ts +853 -0
  209. package/src/core/shutdown.ts +165 -0
  210. package/src/errors/checkpoint-errors.ts +62 -0
  211. package/src/errors/has-description.ts +25 -0
  212. package/src/errors/index.ts +57 -0
  213. package/src/errors/matador-errors.ts +477 -0
  214. package/src/errors/retry-errors.ts +134 -0
  215. package/src/hooks/index.ts +15 -0
  216. package/src/hooks/safe-hooks.ts +223 -0
  217. package/src/hooks/types.ts +248 -0
  218. package/src/index.ts +231 -0
  219. package/src/pipeline/index.ts +2 -0
  220. package/src/pipeline/pipeline.test.ts +1317 -0
  221. package/src/pipeline/pipeline.ts +371 -0
  222. package/src/retry/index.ts +4 -0
  223. package/src/retry/policy.ts +46 -0
  224. package/src/retry/standard-policy.test.ts +282 -0
  225. package/src/retry/standard-policy.ts +156 -0
  226. package/src/schema/index.ts +16 -0
  227. package/src/schema/registry.test.ts +339 -0
  228. package/src/schema/registry.ts +226 -0
  229. package/src/schema/types.test.ts +281 -0
  230. package/src/schema/types.ts +217 -0
  231. package/src/topology/builder.test.ts +275 -0
  232. package/src/topology/builder.ts +199 -0
  233. package/src/topology/index.ts +15 -0
  234. package/src/topology/types.ts +109 -0
  235. package/src/transport/capabilities.ts +88 -0
  236. package/src/transport/connection-manager.ts +218 -0
  237. package/src/transport/index.ts +42 -0
  238. package/src/transport/local/local-transport.test.ts +262 -0
  239. package/src/transport/local/local-transport.ts +327 -0
  240. package/src/transport/multi/multi-transport.test.ts +320 -0
  241. package/src/transport/multi/multi-transport.ts +294 -0
  242. package/src/transport/rabbitmq/rabbitmq-transport.ts +753 -0
  243. package/src/transport/transport.ts +200 -0
  244. package/src/types/common.ts +53 -0
  245. package/src/types/envelope.ts +152 -0
  246. package/src/types/event.test.ts +157 -0
  247. package/src/types/event.ts +118 -0
  248. package/src/types/index.ts +52 -0
  249. package/src/types/subscriber.ts +310 -0
  250. package/test/e2e/multi-transport.e2e.test.ts +236 -0
  251. package/test/e2e/rabbitmq-transport.e2e.test.ts +327 -0
  252. package/test/e2e/transport-compliance.e2e.test.ts +505 -0
  253. package/test/integration/matador.integration.test.ts +634 -0
  254. package/tsconfig.json +29 -0
  255. package/tsconfig.tsbuildinfo +1 -0
  256. package/tsup.config.ts +13 -0
@@ -0,0 +1,278 @@
1
+ import { beforeEach, describe, expect, it } from 'bun:test';
2
+ import { MatadorEvent, createSubscriber } from '../types/index.js';
3
+ import { SchemaError, SchemaRegistry } from './registry.js';
4
+ class TestEvent extends MatadorEvent {
5
+ data;
6
+ static key = 'test.event';
7
+ static description = 'A test event';
8
+ constructor(data) {
9
+ super();
10
+ this.data = data;
11
+ }
12
+ }
13
+ class AliasedEvent extends MatadorEvent {
14
+ data;
15
+ static key = 'user.created.v2';
16
+ static description = 'User created event with aliases';
17
+ static aliases = ['user.created', 'user.created.v1'];
18
+ constructor(data) {
19
+ super();
20
+ this.data = data;
21
+ }
22
+ }
23
+ describe('SchemaRegistry', () => {
24
+ let registry;
25
+ beforeEach(() => {
26
+ registry = new SchemaRegistry();
27
+ });
28
+ describe('register', () => {
29
+ it('should register an event with subscribers', () => {
30
+ const subscriber = createSubscriber({
31
+ name: 'test-subscriber',
32
+ description: 'Test subscriber',
33
+ callback: async () => { },
34
+ });
35
+ registry.register(TestEvent, [subscriber]);
36
+ expect(registry.hasEvent('test.event')).toBe(true);
37
+ expect(registry.getSubscribers('test.event')).toHaveLength(1);
38
+ });
39
+ it('should throw when registering duplicate event without override', () => {
40
+ const subscriber = createSubscriber({
41
+ name: 'test-subscriber',
42
+ description: 'Test subscriber',
43
+ callback: async () => { },
44
+ });
45
+ registry.register(TestEvent, [subscriber]);
46
+ expect(() => registry.register(TestEvent, [subscriber])).toThrow(SchemaError);
47
+ });
48
+ it('should allow override when option is set', () => {
49
+ const subscriber1 = createSubscriber({
50
+ name: 'sub-1',
51
+ description: 'Subscriber 1',
52
+ callback: async () => { },
53
+ });
54
+ const subscriber2 = createSubscriber({
55
+ name: 'sub-2',
56
+ description: 'Subscriber 2',
57
+ callback: async () => { },
58
+ });
59
+ registry.register(TestEvent, [subscriber1]);
60
+ registry.register(TestEvent, [subscriber2], { override: true });
61
+ const subscribers = registry.getSubscribers('test.event');
62
+ expect(subscribers).toHaveLength(1);
63
+ expect(subscribers[0]?.name).toBe('sub-2');
64
+ });
65
+ it('should register aliases', () => {
66
+ const subscriber = createSubscriber({
67
+ name: 'test-subscriber',
68
+ description: 'Test subscriber',
69
+ callback: async () => { },
70
+ });
71
+ registry.register(AliasedEvent, [subscriber]);
72
+ expect(registry.hasEvent('user.created.v2')).toBe(true);
73
+ expect(registry.hasEvent('user.created')).toBe(true);
74
+ expect(registry.hasEvent('user.created.v1')).toBe(true);
75
+ });
76
+ it('should throw on duplicate aliases without override', () => {
77
+ class EventWithSameAlias extends MatadorEvent {
78
+ data;
79
+ static key = 'another.event';
80
+ static description = 'Another event';
81
+ static aliases = ['user.created']; // conflicts with AliasedEvent
82
+ constructor(data) {
83
+ super();
84
+ this.data = data;
85
+ }
86
+ }
87
+ const sub1 = createSubscriber({
88
+ name: 'sub-1',
89
+ description: 'Subscriber 1',
90
+ callback: async () => { },
91
+ });
92
+ const sub2 = createSubscriber({
93
+ name: 'sub-2',
94
+ description: 'Subscriber 2',
95
+ callback: async () => { },
96
+ });
97
+ registry.register(AliasedEvent, [sub1]);
98
+ expect(() => registry.register(EventWithSameAlias, [sub2])).toThrow('Alias "user.created" is already registered');
99
+ });
100
+ });
101
+ describe('getEventClass', () => {
102
+ it('should return event class by key', () => {
103
+ const subscriber = createSubscriber({
104
+ name: 'test-subscriber',
105
+ description: 'Test subscriber',
106
+ callback: async () => { },
107
+ });
108
+ registry.register(TestEvent, [subscriber]);
109
+ const eventClass = registry.getEventClass('test.event');
110
+ expect(eventClass).toBeDefined();
111
+ expect(eventClass?.key).toBe('test.event');
112
+ });
113
+ it('should return event class by alias', () => {
114
+ const subscriber = createSubscriber({
115
+ name: 'test-subscriber',
116
+ description: 'Test subscriber',
117
+ callback: async () => { },
118
+ });
119
+ registry.register(AliasedEvent, [subscriber]);
120
+ const eventClass = registry.getEventClass('user.created');
121
+ expect(eventClass).toBeDefined();
122
+ expect(eventClass?.key).toBe('user.created.v2');
123
+ });
124
+ it('should return undefined for unknown key', () => {
125
+ expect(registry.getEventClass('unknown.event')).toBeUndefined();
126
+ });
127
+ });
128
+ describe('getSubscribers', () => {
129
+ it('should return all subscribers for an event', () => {
130
+ const sub1 = createSubscriber({
131
+ name: 'sub-1',
132
+ description: 'Subscriber 1',
133
+ callback: async () => { },
134
+ });
135
+ const sub2 = createSubscriber({
136
+ name: 'sub-2',
137
+ description: 'Subscriber 2',
138
+ callback: async () => { },
139
+ });
140
+ registry.register(TestEvent, [sub1, sub2]);
141
+ const subscribers = registry.getSubscribers('test.event');
142
+ expect(subscribers).toHaveLength(2);
143
+ expect(subscribers[0]?.name).toBe('sub-1');
144
+ expect(subscribers[1]?.name).toBe('sub-2');
145
+ });
146
+ it('should return empty array for unknown event', () => {
147
+ expect(registry.getSubscribers('unknown.event')).toEqual([]);
148
+ });
149
+ });
150
+ describe('getSubscriber', () => {
151
+ it('should return specific subscriber by name', () => {
152
+ const sub1 = createSubscriber({
153
+ name: 'sub-1',
154
+ description: 'Subscriber 1',
155
+ callback: async () => { },
156
+ });
157
+ const sub2 = createSubscriber({
158
+ name: 'sub-2',
159
+ description: 'Subscriber 2',
160
+ callback: async () => { },
161
+ });
162
+ registry.register(TestEvent, [sub1, sub2]);
163
+ const subscriber = registry.getSubscriber('test.event', 'sub-2');
164
+ expect(subscriber?.name).toBe('sub-2');
165
+ });
166
+ it('should return undefined for unknown subscriber', () => {
167
+ const sub = createSubscriber({
168
+ name: 'sub-1',
169
+ description: 'Subscriber 1',
170
+ callback: async () => { },
171
+ });
172
+ registry.register(TestEvent, [sub]);
173
+ expect(registry.getSubscriber('test.event', 'unknown')).toBeUndefined();
174
+ });
175
+ });
176
+ describe('getSubscriberDefinition', () => {
177
+ it('should return subscriber definition for pipeline', () => {
178
+ const sub = createSubscriber({
179
+ name: 'sub-1',
180
+ description: 'Subscriber 1',
181
+ callback: async () => { },
182
+ idempotent: 'yes',
183
+ importance: 'must-investigate',
184
+ targetQueue: 'custom-queue',
185
+ });
186
+ registry.register(TestEvent, [sub]);
187
+ const definition = registry.getSubscriberDefinition('test.event', 'sub-1');
188
+ expect(definition).toEqual({
189
+ name: 'sub-1',
190
+ description: 'Subscriber 1',
191
+ idempotent: 'yes',
192
+ importance: 'must-investigate',
193
+ targetQueue: 'custom-queue',
194
+ });
195
+ });
196
+ it('should return undefined for unknown subscriber', () => {
197
+ expect(registry.getSubscriberDefinition('unknown', 'unknown')).toBeUndefined();
198
+ });
199
+ });
200
+ describe('getExecutableSubscriber', () => {
201
+ it('should return subscriber with callback', () => {
202
+ const callback = async () => { };
203
+ const sub = createSubscriber({
204
+ name: 'sub-1',
205
+ description: 'Subscriber 1',
206
+ callback,
207
+ });
208
+ registry.register(TestEvent, [sub]);
209
+ const executable = registry.getExecutableSubscriber('test.event', 'sub-1');
210
+ expect(executable).toBeDefined();
211
+ expect(executable?.callback).toBe(callback);
212
+ });
213
+ });
214
+ describe('getEventKeys', () => {
215
+ it('should return all registered event keys', () => {
216
+ const sub1 = createSubscriber({
217
+ name: 'sub-1',
218
+ description: 'Subscriber 1',
219
+ callback: async () => { },
220
+ });
221
+ const sub2 = createSubscriber({
222
+ name: 'sub-2',
223
+ description: 'Subscriber 2',
224
+ callback: async () => { },
225
+ });
226
+ registry.register(TestEvent, [sub1]);
227
+ registry.register(AliasedEvent, [sub2]);
228
+ const keys = registry.getEventKeys();
229
+ expect(keys).toContain('test.event');
230
+ expect(keys).toContain('user.created.v2');
231
+ expect(keys).not.toContain('user.created'); // aliases not included
232
+ });
233
+ });
234
+ describe('validate', () => {
235
+ it('should pass validation for valid schema', () => {
236
+ const sub = createSubscriber({
237
+ name: 'sub-1',
238
+ description: 'Subscriber 1',
239
+ callback: async () => { },
240
+ });
241
+ registry.register(TestEvent, [sub]);
242
+ const result = registry.validate();
243
+ expect(result.valid).toBe(true);
244
+ expect(result.issues).toHaveLength(0);
245
+ });
246
+ it('should detect duplicate subscriber names', () => {
247
+ const sub1 = createSubscriber({
248
+ name: 'duplicate-name',
249
+ description: 'Duplicate subscriber',
250
+ callback: async () => { },
251
+ });
252
+ const sub2 = createSubscriber({
253
+ name: 'duplicate-name',
254
+ description: 'Duplicate subscriber',
255
+ callback: async () => { },
256
+ });
257
+ registry.register(TestEvent, [sub1, sub2]);
258
+ const result = registry.validate();
259
+ expect(result.valid).toBe(false);
260
+ expect(result.issues).toHaveLength(1);
261
+ expect(result.issues[0]?.severity).toBe('error');
262
+ expect(result.issues[0]?.message).toContain('Duplicate subscriber name');
263
+ });
264
+ });
265
+ describe('clear', () => {
266
+ it('should remove all entries', () => {
267
+ const sub = createSubscriber({
268
+ name: 'sub-1',
269
+ description: 'Subscriber 1',
270
+ callback: async () => { },
271
+ });
272
+ registry.register(TestEvent, [sub]);
273
+ registry.clear();
274
+ expect(registry.hasEvent('test.event')).toBe(false);
275
+ expect(registry.getEventKeys()).toHaveLength(0);
276
+ });
277
+ });
278
+ });
@@ -0,0 +1,158 @@
1
+ import type { AnySubscriber, EventClass, MatadorEvent, Subscriber, SubscriberStub } from '../types/index.js';
2
+ /**
3
+ * Type-safe schema entry for a single event type (object format).
4
+ * The type parameter enforces that subscribers match the event type.
5
+ */
6
+ export interface SchemaEntry<T extends MatadorEvent = MatadorEvent> {
7
+ /** The event class */
8
+ readonly eventClass: EventClass<T['data']>;
9
+ /** Subscribers for this event */
10
+ readonly subscribers: readonly (Subscriber<T> | SubscriberStub)[];
11
+ }
12
+ /**
13
+ * Type-safe schema entry as a tuple: [EventClass, Subscribers[]]
14
+ * The type parameter enforces that subscribers match the event type.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const entry: SchemaEntryTuple<UserCreatedEvent> = [
19
+ * UserCreatedEvent,
20
+ * [createSubscriber<UserCreatedEvent>('handler', async (env) => {})]
21
+ * ];
22
+ * ```
23
+ */
24
+ export type SchemaEntryTuple<T extends MatadorEvent = MatadorEvent> = readonly [
25
+ eventClass: EventClass<T['data']>,
26
+ subscribers: readonly (Subscriber<T> | SubscriberStub)[]
27
+ ];
28
+ /**
29
+ * Creates a type-safe schema entry tuple.
30
+ * Ensures that subscribers are compatible with the event class at compile time.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * const schema = {
35
+ * [UserCreatedEvent.key]: bind(UserCreatedEvent, [
36
+ * createSubscriber<UserCreatedEvent>('send-email', async (env) => {
37
+ * console.log(env.data.email); // Type-safe access
38
+ * }),
39
+ * ]),
40
+ * } satisfies MatadorSchema;
41
+ * ```
42
+ */
43
+ export declare function bind<T extends MatadorEvent>(eventClass: EventClass<T['data']>, subscribers: readonly (Subscriber<T> | SubscriberStub)[]): SchemaEntryTuple<T>;
44
+ /**
45
+ * Minimal type for event class in schema storage.
46
+ * Only requires the static `key` property for routing.
47
+ * Constructor constraint is omitted to allow heterogeneous event types.
48
+ */
49
+ export type AnyEventClass = {
50
+ readonly key: string;
51
+ new (data: any): MatadorEvent;
52
+ };
53
+ /**
54
+ * Runtime schema entry stored in MatadorSchema (object format).
55
+ * Uses AnySubscriber and AnyEventClass to allow heterogeneous event types.
56
+ * Type safety is enforced at definition time via `schemaEntry()` helper.
57
+ */
58
+ export interface RuntimeSchemaEntry {
59
+ readonly eventClass: AnyEventClass;
60
+ readonly subscribers: readonly AnySubscriber[];
61
+ }
62
+ /**
63
+ * Runtime schema tuple stored in MatadorSchema.
64
+ * Uses AnySubscriber and AnyEventClass to allow heterogeneous event types.
65
+ * Type safety is enforced at definition time via `schemaEntry()` helper.
66
+ */
67
+ export type RuntimeSchemaEntryTuple = readonly [
68
+ eventClass: AnyEventClass,
69
+ subscribers: readonly AnySubscriber[]
70
+ ];
71
+ /**
72
+ * Matador schema mapping event keys to their definitions.
73
+ * Supports both object format (SchemaEntry) and tuple format (SchemaEntryTuple).
74
+ *
75
+ * For type-safe schema definitions, use the `bind` helper function
76
+ * which ensures subscribers are compatible with their event class.
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * // Type-safe with bind helper:
81
+ * const schema = {
82
+ * [UserCreatedEvent.key]: bind(UserCreatedEvent, [emailSubscriber]),
83
+ * } satisfies MatadorSchema;
84
+ *
85
+ * // Or inline (less type-safe):
86
+ * const schema: MatadorSchema = {
87
+ * [UserCreatedEvent.key]: [UserCreatedEvent, [emailSubscriber]],
88
+ * };
89
+ * ```
90
+ */
91
+ export type MatadorSchema = {
92
+ readonly [eventKey: string]: RuntimeSchemaEntry | RuntimeSchemaEntryTuple;
93
+ };
94
+ /**
95
+ * Type guard to check if a schema entry is in tuple format.
96
+ */
97
+ export declare function isSchemaEntryTuple(entry: RuntimeSchemaEntry | RuntimeSchemaEntryTuple): entry is RuntimeSchemaEntryTuple;
98
+ /**
99
+ * Options for schema registration.
100
+ */
101
+ export interface RegisterOptions {
102
+ /** Override existing registration if present */
103
+ readonly override?: boolean;
104
+ }
105
+ /**
106
+ * Result of schema validation.
107
+ */
108
+ export interface SchemaValidationResult {
109
+ readonly valid: boolean;
110
+ readonly issues: readonly SchemaIssue[];
111
+ }
112
+ /**
113
+ * Individual schema issue.
114
+ */
115
+ export interface SchemaIssue {
116
+ readonly severity: 'error' | 'warning';
117
+ readonly eventKey: string;
118
+ readonly message: string;
119
+ }
120
+ /**
121
+ * Plugin definition for adding subscribers to multiple events.
122
+ */
123
+ export interface SchemaPlugin {
124
+ /** The subscriber to add to events */
125
+ readonly subscriber: AnySubscriber;
126
+ /** Event keys to exclude from this plugin */
127
+ readonly exclusions?: readonly string[];
128
+ }
129
+ /**
130
+ * Installs plugins (global subscribers) into a schema.
131
+ * Each plugin subscriber is added to all events except those in its exclusions list.
132
+ *
133
+ * @param schema - The base schema to extend
134
+ * @param plugins - Array of plugins to install
135
+ * @returns A new schema with plugin subscribers added
136
+ *
137
+ * @example
138
+ * ```typescript
139
+ * const baseSchema: MatadorSchema = {
140
+ * [UserCreatedEvent.key]: [UserCreatedEvent, [sendWelcomeEmail]],
141
+ * [OrderPlacedEvent.key]: [OrderPlacedEvent, [processOrder]],
142
+ * [ChatMessageSent.key]: [ChatMessageSent, [notifyRecipient]],
143
+ * };
144
+ *
145
+ * const schema = installPlugins(baseSchema, [
146
+ * {
147
+ * subscriber: logToBigQuery,
148
+ * exclusions: [ChatMessageSent.key], // Don't log chat messages
149
+ * },
150
+ * {
151
+ * subscriber: trackAnalytics,
152
+ * // No exclusions - added to all events
153
+ * },
154
+ * ]);
155
+ * ```
156
+ */
157
+ export declare function installPlugins(schema: MatadorSchema, plugins: readonly SchemaPlugin[]): MatadorSchema;
158
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/schema/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EACV,YAAY,EACZ,UAAU,EACV,cAAc,EACf,MAAM,mBAAmB,CAAC;AAE3B;;;GAGG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY;IAChE,sBAAsB;IACtB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAE3C,iCAAiC;IACjC,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,EAAE,CAAC;CACnE;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,IAAI,SAAS;IAC7E,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACjC,WAAW,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,EAAE;CACzD,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS,YAAY,EACzC,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,EACjC,WAAW,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,EAAE,GACvD,gBAAgB,CAAC,CAAC,CAAC,CAErB;AAED;;;;GAIG;AAEH,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,KAAK,IAAI,EAAE,GAAG,GAAG,YAAY,CAAC;CAC/B,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC;IACnC,QAAQ,CAAC,WAAW,EAAE,SAAS,aAAa,EAAE,CAAC;CAChD;AAED;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,SAAS;IAC7C,UAAU,EAAE,aAAa;IACzB,WAAW,EAAE,SAAS,aAAa,EAAE;CACtC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,kBAAkB,GAAG,uBAAuB,CAAC;CAC3E,CAAC;AAEF;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,kBAAkB,GAAG,uBAAuB,GAClD,KAAK,IAAI,uBAAuB,CAElC;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,gDAAgD;IAChD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,sCAAsC;IACtC,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC;IAEnC,6CAA6C;IAC7C,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,SAAS,YAAY,EAAE,GAC/B,aAAa,CA0Bf"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Creates a type-safe schema entry tuple.
3
+ * Ensures that subscribers are compatible with the event class at compile time.
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * const schema = {
8
+ * [UserCreatedEvent.key]: bind(UserCreatedEvent, [
9
+ * createSubscriber<UserCreatedEvent>('send-email', async (env) => {
10
+ * console.log(env.data.email); // Type-safe access
11
+ * }),
12
+ * ]),
13
+ * } satisfies MatadorSchema;
14
+ * ```
15
+ */
16
+ export function bind(eventClass, subscribers) {
17
+ return [eventClass, subscribers];
18
+ }
19
+ /**
20
+ * Type guard to check if a schema entry is in tuple format.
21
+ */
22
+ export function isSchemaEntryTuple(entry) {
23
+ return Array.isArray(entry);
24
+ }
25
+ /**
26
+ * Installs plugins (global subscribers) into a schema.
27
+ * Each plugin subscriber is added to all events except those in its exclusions list.
28
+ *
29
+ * @param schema - The base schema to extend
30
+ * @param plugins - Array of plugins to install
31
+ * @returns A new schema with plugin subscribers added
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * const baseSchema: MatadorSchema = {
36
+ * [UserCreatedEvent.key]: [UserCreatedEvent, [sendWelcomeEmail]],
37
+ * [OrderPlacedEvent.key]: [OrderPlacedEvent, [processOrder]],
38
+ * [ChatMessageSent.key]: [ChatMessageSent, [notifyRecipient]],
39
+ * };
40
+ *
41
+ * const schema = installPlugins(baseSchema, [
42
+ * {
43
+ * subscriber: logToBigQuery,
44
+ * exclusions: [ChatMessageSent.key], // Don't log chat messages
45
+ * },
46
+ * {
47
+ * subscriber: trackAnalytics,
48
+ * // No exclusions - added to all events
49
+ * },
50
+ * ]);
51
+ * ```
52
+ */
53
+ export function installPlugins(schema, plugins) {
54
+ const result = {};
55
+ for (const [eventKey, entry] of Object.entries(schema)) {
56
+ // Get existing event class and subscribers
57
+ const [eventClass, existingSubscribers] = isSchemaEntryTuple(entry)
58
+ ? [entry[0], entry[1]]
59
+ : [entry.eventClass, entry.subscribers];
60
+ // Collect subscribers to add from plugins
61
+ const pluginSubscribers = [];
62
+ for (const plugin of plugins) {
63
+ if (!plugin.exclusions?.includes(eventKey)) {
64
+ pluginSubscribers.push(plugin.subscriber);
65
+ }
66
+ }
67
+ // Create new entry with combined subscribers
68
+ result[eventKey] = [
69
+ eventClass,
70
+ [...existingSubscribers, ...pluginSubscribers],
71
+ ];
72
+ }
73
+ return result;
74
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.test.d.ts","sourceRoot":"","sources":["../../src/schema/types.test.ts"],"names":[],"mappings":""}