@deessejs/server 0.0.0 → 1.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 (159) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/dist/api/factory.d.ts +8 -8
  4. package/dist/api/factory.d.ts.map +1 -1
  5. package/dist/api/factory.js +181 -109
  6. package/dist/api/factory.js.map +1 -1
  7. package/dist/api/index.d.ts +2 -2
  8. package/dist/api/index.d.ts.map +1 -1
  9. package/dist/api/index.js +1 -1
  10. package/dist/api/index.js.map +1 -1
  11. package/dist/api/types.d.ts +31 -16
  12. package/dist/api/types.d.ts.map +1 -1
  13. package/dist/context/builder.d.ts +5 -4
  14. package/dist/context/builder.d.ts.map +1 -1
  15. package/dist/context/builder.js +8 -3
  16. package/dist/context/builder.js.map +1 -1
  17. package/dist/context/types.d.ts +10 -3
  18. package/dist/context/types.d.ts.map +1 -1
  19. package/dist/errors/server-error.d.ts +9 -3
  20. package/dist/errors/server-error.d.ts.map +1 -1
  21. package/dist/errors/server-error.js +16 -4
  22. package/dist/errors/server-error.js.map +1 -1
  23. package/dist/errors/types.d.ts +1 -0
  24. package/dist/errors/types.d.ts.map +1 -1
  25. package/dist/events/dsl.d.ts +55 -0
  26. package/dist/events/dsl.d.ts.map +1 -0
  27. package/dist/events/dsl.js +42 -0
  28. package/dist/events/dsl.js.map +1 -0
  29. package/dist/events/emitter.d.ts +27 -6
  30. package/dist/events/emitter.d.ts.map +1 -1
  31. package/dist/events/emitter.js +193 -23
  32. package/dist/events/emitter.js.map +1 -1
  33. package/dist/events/index.d.ts +4 -1
  34. package/dist/events/index.d.ts.map +1 -1
  35. package/dist/events/index.js +3 -1
  36. package/dist/events/index.js.map +1 -1
  37. package/dist/events/queue.d.ts +19 -0
  38. package/dist/events/queue.d.ts.map +1 -0
  39. package/dist/events/queue.js +54 -0
  40. package/dist/events/queue.js.map +1 -0
  41. package/dist/events/types.d.ts +1 -1
  42. package/dist/events/types.d.ts.map +1 -1
  43. package/dist/hooks/executor.d.ts +3 -3
  44. package/dist/hooks/executor.d.ts.map +1 -1
  45. package/dist/hooks/types.d.ts +4 -4
  46. package/dist/hooks/types.d.ts.map +1 -1
  47. package/dist/index.d.ts +6 -3
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +5 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/internal-mutation/builder.d.ts +13 -10
  52. package/dist/internal-mutation/builder.d.ts.map +1 -1
  53. package/dist/internal-mutation/builder.js +57 -1
  54. package/dist/internal-mutation/builder.js.map +1 -1
  55. package/dist/internal-mutation/types.d.ts +5 -4
  56. package/dist/internal-mutation/types.d.ts.map +1 -1
  57. package/dist/internal-query/builder.d.ts +13 -10
  58. package/dist/internal-query/builder.d.ts.map +1 -1
  59. package/dist/internal-query/builder.js +56 -1
  60. package/dist/internal-query/builder.js.map +1 -1
  61. package/dist/internal-query/types.d.ts +4 -3
  62. package/dist/internal-query/types.d.ts.map +1 -1
  63. package/dist/middleware/builder.d.ts +1 -1
  64. package/dist/middleware/builder.d.ts.map +1 -1
  65. package/dist/middleware/helpers.d.ts +28 -0
  66. package/dist/middleware/helpers.d.ts.map +1 -0
  67. package/dist/middleware/helpers.js +31 -0
  68. package/dist/middleware/helpers.js.map +1 -0
  69. package/dist/middleware/types.d.ts.map +1 -1
  70. package/dist/mutation/builder.d.ts +13 -10
  71. package/dist/mutation/builder.d.ts.map +1 -1
  72. package/dist/mutation/builder.js +55 -1
  73. package/dist/mutation/builder.js.map +1 -1
  74. package/dist/mutation/types.d.ts +5 -4
  75. package/dist/mutation/types.d.ts.map +1 -1
  76. package/dist/query/builder.d.ts +18 -18
  77. package/dist/query/builder.d.ts.map +1 -1
  78. package/dist/query/builder.js +83 -32
  79. package/dist/query/builder.js.map +1 -1
  80. package/dist/query/types.d.ts +21 -18
  81. package/dist/query/types.d.ts.map +1 -1
  82. package/dist/router/builder.d.ts +4 -12
  83. package/dist/router/builder.d.ts.map +1 -1
  84. package/dist/router/builder.js +10 -32
  85. package/dist/router/builder.js.map +1 -1
  86. package/dist/router/types.d.ts +78 -2
  87. package/dist/router/types.d.ts.map +1 -1
  88. package/dist/type-test.d.ts +2 -0
  89. package/dist/type-test.d.ts.map +1 -0
  90. package/dist/type-test.js +2 -0
  91. package/dist/type-test.js.map +1 -0
  92. package/dist/types.d.ts +28 -7
  93. package/dist/types.d.ts.map +1 -1
  94. package/eslint.config.js +124 -1
  95. package/package.json +42 -38
  96. package/src/api/factory.ts +243 -160
  97. package/src/api/index.ts +2 -2
  98. package/src/api/types.ts +45 -18
  99. package/src/context/builder.ts +16 -10
  100. package/src/context/types.ts +11 -4
  101. package/src/errors/server-error.ts +20 -9
  102. package/src/errors/types.ts +1 -0
  103. package/src/events/dsl.ts +65 -0
  104. package/src/events/emitter.ts +224 -35
  105. package/src/events/index.ts +4 -1
  106. package/src/events/queue.ts +70 -0
  107. package/src/events/types.ts +1 -3
  108. package/src/hooks/executor.ts +4 -4
  109. package/src/hooks/types.ts +6 -11
  110. package/src/index.ts +11 -4
  111. package/src/internal-mutation/builder.ts +110 -36
  112. package/src/internal-mutation/types.ts +6 -5
  113. package/src/internal-query/builder.ts +102 -34
  114. package/src/internal-query/types.ts +5 -4
  115. package/src/middleware/builder.ts +1 -1
  116. package/src/middleware/helpers.ts +87 -0
  117. package/src/middleware/types.ts +1 -3
  118. package/src/mutation/builder.ts +106 -33
  119. package/src/mutation/types.ts +6 -5
  120. package/src/procedure/types.ts +1 -1
  121. package/src/query/builder.ts +151 -101
  122. package/src/query/types.ts +29 -19
  123. package/src/router/builder.ts +21 -46
  124. package/src/router/types.ts +120 -3
  125. package/src/types.ts +41 -38
  126. package/tests/index.test.ts +389 -2
  127. package/tests/type-safety.test.ts +496 -0
  128. package/dist/api.d.ts +0 -31
  129. package/dist/api.d.ts.map +0 -1
  130. package/dist/api.js +0 -155
  131. package/dist/api.js.map +0 -1
  132. package/dist/context.d.ts +0 -16
  133. package/dist/context.d.ts.map +0 -1
  134. package/dist/context.js +0 -25
  135. package/dist/context.js.map +0 -1
  136. package/dist/errors.d.ts +0 -30
  137. package/dist/errors.d.ts.map +0 -1
  138. package/dist/errors.js +0 -79
  139. package/dist/errors.js.map +0 -1
  140. package/dist/events.d.ts +0 -14
  141. package/dist/events.d.ts.map +0 -1
  142. package/dist/events.js +0 -42
  143. package/dist/events.js.map +0 -1
  144. package/dist/hooks.d.ts +0 -11
  145. package/dist/hooks.d.ts.map +0 -1
  146. package/dist/hooks.js +0 -26
  147. package/dist/hooks.js.map +0 -1
  148. package/dist/procedures.d.ts +0 -23
  149. package/dist/procedures.d.ts.map +0 -1
  150. package/dist/procedures.js +0 -4
  151. package/dist/procedures.js.map +0 -1
  152. package/dist/query-builder.d.ts +0 -37
  153. package/dist/query-builder.d.ts.map +0 -1
  154. package/dist/query-builder.js +0 -103
  155. package/dist/query-builder.js.map +0 -1
  156. package/dist/router.d.ts +0 -32
  157. package/dist/router.d.ts.map +0 -1
  158. package/dist/router.js +0 -98
  159. package/dist/router.js.map +0 -1
package/src/types.ts CHANGED
@@ -1,13 +1,8 @@
1
- import type { ZodType } from "zod";
2
- import type { Result } from "@deessejs/fp";
1
+ import { type ZodType } from "zod";
2
+ import { type Result } from "@deessejs/fp";
3
3
 
4
- // Re-export Result type from @deessejs/fp
5
4
  export type { Result } from "@deessejs/fp";
6
5
 
7
- // ============================================
8
- // Procedure Types
9
- // ============================================
10
-
11
6
  export type ProcedureType = "query" | "mutation" | "internalQuery" | "internalMutation";
12
7
 
13
8
  export interface BaseProcedure<Ctx, Args, Output> {
@@ -33,10 +28,6 @@ export interface InternalMutation<Ctx, Args, Output> extends BaseProcedure<Ctx,
33
28
  readonly type: "internalMutation";
34
29
  }
35
30
 
36
- // ============================================
37
- // Hooks
38
- // ============================================
39
-
40
31
  export type BeforeInvokeHook<Ctx, Args> = (ctx: Ctx, args: Args) => void | Promise<void>;
41
32
 
42
33
  export type AfterInvokeHook<Ctx, Args, Output> = (
@@ -49,35 +40,31 @@ export type OnSuccessHook<Ctx, Args, Output> = (ctx: Ctx, args: Args, data: Outp
49
40
 
50
41
  export type OnErrorHook<Ctx, Args, Error> = (ctx: Ctx, args: Args, error: Error) => void | Promise<void>;
51
42
 
52
- // ============================================
53
- // Middleware
54
- // ============================================
55
-
56
43
  export interface Middleware<Ctx, Args = unknown> {
57
44
  readonly name: string;
58
45
  readonly args?: Args;
59
46
  readonly handler: (
60
- ctx: Ctx & { args: Args; meta: Record<string, unknown> },
61
- next: () => Promise<Result<unknown>>
47
+ ctx: Ctx,
48
+ opts: {
49
+ next: (overrides?: { ctx?: Partial<Ctx> }) => Promise<Result<unknown>>;
50
+ args: Args;
51
+ meta: Record<string, unknown>;
52
+ }
62
53
  ) => Promise<Result<unknown>>;
63
54
  }
64
55
 
65
- // ============================================
66
- // Plugin
67
- // ============================================
68
-
69
56
  export interface Plugin<Ctx> {
70
57
  readonly name: string;
71
58
  readonly extend: (ctx: Ctx) => Partial<Ctx>;
72
59
  }
73
60
 
74
- // ============================================
75
- // Router
76
- // ============================================
77
-
78
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
- export type Router<Ctx = any, Routes = Record<string, any>> = Routes & {
80
- [key: string]: Router<Ctx> | Procedure<Ctx, any, any>;
61
+ export type Router<Ctx = unknown, Routes extends Record<string, unknown> = Record<string, unknown>> = {
62
+ /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
63
+ [K in keyof Routes & string]: Routes[K] extends Procedure<Ctx, infer _Args, infer _Output>
64
+ ? Routes[K]
65
+ : Routes[K] extends Record<string, unknown>
66
+ ? Router<Ctx, Routes[K]>
67
+ : never;
81
68
  };
82
69
 
83
70
  export type Procedure<Ctx, Args, Output> =
@@ -86,10 +73,6 @@ export type Procedure<Ctx, Args, Output> =
86
73
  | InternalQuery<Ctx, Args, Output>
87
74
  | InternalMutation<Ctx, Args, Output>;
88
75
 
89
- // ============================================
90
- // Events
91
- // ============================================
92
-
93
76
  export interface EventRegistry {
94
77
  [eventName: string]: {
95
78
  data?: unknown;
@@ -97,14 +80,27 @@ export interface EventRegistry {
97
80
  };
98
81
  }
99
82
 
100
- export interface EventPayload {
83
+ export interface EventPayload<T = unknown> {
101
84
  name: string;
102
- data: unknown;
85
+ data: T;
86
+ timestamp: string;
87
+ namespace: string;
88
+ source?: string;
103
89
  }
104
90
 
105
- // ============================================
106
- // Context Types
107
- // ============================================
91
+ export interface SendOptions {
92
+ namespace?: string;
93
+ broadcast?: boolean;
94
+ delay?: number;
95
+ }
96
+
97
+ export interface PendingEvent {
98
+ name: string;
99
+ data: unknown;
100
+ timestamp: string;
101
+ namespace: string;
102
+ options?: SendOptions;
103
+ }
108
104
 
109
105
  export interface ContextWithSend<Ctx, Events extends EventRegistry> {
110
106
  ctx: Ctx;
@@ -112,4 +108,11 @@ export interface ContextWithSend<Ctx, Events extends EventRegistry> {
112
108
  event: EventName,
113
109
  data: Events[EventName]["data"]
114
110
  ) => void;
115
- }
111
+ }
112
+
113
+ export type HandlerContext<Ctx, Events extends EventRegistry> = Ctx & {
114
+ send: <EventName extends keyof Events>(
115
+ event: EventName,
116
+ data: Events[EventName]["data"]
117
+ ) => void;
118
+ };
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { defineContext, ok, err } from "../src/index";
2
+ import { defineContext, defineEvents, ok, err } from "../src/index";
3
3
  import { z } from "zod";
4
4
 
5
5
  describe("defineContext", () => {
@@ -74,7 +74,7 @@ describe("defineContext", () => {
74
74
  });
75
75
 
76
76
  describe("createAPI", () => {
77
- it("should execute a query", async () => {
77
+ it("should execute a query using execute method", async () => {
78
78
  const { t, createAPI } = defineContext({
79
79
  context: { db: { find: () => ({ id: 1, name: "test" }) } },
80
80
  });
@@ -94,6 +94,7 @@ describe("createAPI", () => {
94
94
  }),
95
95
  });
96
96
 
97
+ // Old syntax still works
97
98
  const result = await api.execute("users.get", { id: 1 });
98
99
 
99
100
  expect(result.ok).toBe(true);
@@ -102,6 +103,35 @@ describe("createAPI", () => {
102
103
  }
103
104
  });
104
105
 
106
+ it("should execute a query using direct proxy access", async () => {
107
+ const { t, createAPI } = defineContext({
108
+ context: { db: { find: () => ({ id: 1, name: "test" }) } },
109
+ });
110
+
111
+ const getUser = t.query({
112
+ args: z.object({ id: z.number() }),
113
+ handler: async (ctx, args) => {
114
+ return ok({ id: args.id, name: "test" });
115
+ },
116
+ });
117
+
118
+ const api = createAPI({
119
+ router: t.router({
120
+ users: {
121
+ get: getUser,
122
+ },
123
+ }),
124
+ });
125
+
126
+ // New direct syntax: api.users.get({})
127
+ const result = await api.users.get({ id: 1 });
128
+
129
+ expect(result.ok).toBe(true);
130
+ if (result.ok) {
131
+ expect(result.value).toEqual({ id: 1, name: "test" });
132
+ }
133
+ });
134
+
105
135
  it("should return error for unknown route", async () => {
106
136
  const { t, createAPI } = defineContext({
107
137
  context: { name: "test" },
@@ -115,4 +145,361 @@ describe("createAPI", () => {
115
145
 
116
146
  expect(result.ok).toBe(false);
117
147
  });
148
+
149
+ it("should return error for unknown route via direct access", async () => {
150
+ const { t, createAPI } = defineContext({
151
+ context: { name: "test" },
152
+ });
153
+
154
+ const api = createAPI({
155
+ router: t.router({}),
156
+ });
157
+
158
+ // Accessing a non-existent route should return undefined function
159
+ const unknownRoute = (api as any).unknown?.route;
160
+ expect(unknownRoute).toBeUndefined();
161
+ });
162
+ });
163
+
164
+ describe("ctx.send", () => {
165
+ it("should emit events after successful handler execution", async () => {
166
+ const { t, createAPI } = defineContext({
167
+ context: { db: { create: (data: any) => ({ id: 1, ...data }) } },
168
+ events: {
169
+ "user.created": { data: {} as { id: number; name: string } },
170
+ } as any,
171
+ });
172
+
173
+ const createUser = t.mutation({
174
+ args: z.object({ name: z.string() }),
175
+ handler: async (ctx, args) => {
176
+ const user = ctx.db.create(args);
177
+ ctx.send("user.created", { id: user.id, name: user.name });
178
+ return ok(user);
179
+ },
180
+ });
181
+
182
+ const api = createAPI({
183
+ router: t.router({
184
+ users: { create: createUser },
185
+ }),
186
+ });
187
+
188
+ const executor = api;
189
+ const result = await executor.execute("users.create", { name: "John" });
190
+
191
+ expect(result.ok).toBe(true);
192
+ const events = executor.getEvents();
193
+ expect(events).toHaveLength(1);
194
+ expect(events[0].name).toBe("user.created");
195
+ expect(events[0].data).toEqual({ id: 1, name: "John" });
196
+ expect(events[0].namespace).toBe("default");
197
+ expect(events[0].timestamp).toBeDefined();
198
+ });
199
+
200
+ it("should discard events when handler fails", async () => {
201
+ const { t, createAPI } = defineContext({
202
+ context: { db: { create: (data: any) => ({ id: 1, ...data }) } },
203
+ events: {
204
+ "user.created": { data: {} as { id: number; name: string } },
205
+ } as any,
206
+ });
207
+
208
+ const createUser = t.mutation({
209
+ args: z.object({ name: z.string() }),
210
+ handler: async (ctx, args) => {
211
+ ctx.send("user.created", { id: 1, name: args.name });
212
+ return err("USER_CREATION_FAILED" as any, "Failed to create user");
213
+ },
214
+ });
215
+
216
+ const api = createAPI({
217
+ router: t.router({
218
+ users: { create: createUser },
219
+ }),
220
+ });
221
+
222
+ const executor = api;
223
+ const result = await executor.execute("users.create", { name: "John" });
224
+
225
+ expect(result.ok).toBe(false);
226
+ const events = executor.getEvents();
227
+ expect(events).toHaveLength(0);
228
+ });
229
+
230
+ it("should discard events when handler throws", async () => {
231
+ const { t, createAPI } = defineContext({
232
+ context: { db: { create: (data: any) => ({ id: 1, ...data }) } },
233
+ events: {
234
+ "user.created": { data: {} as { id: number; name: string } },
235
+ } as any,
236
+ });
237
+
238
+ const createUser = t.mutation({
239
+ args: z.object({ name: z.string() }),
240
+ handler: async (ctx, args) => {
241
+ ctx.send("user.created", { id: 1, name: args.name });
242
+ throw new Error("Database error");
243
+ },
244
+ });
245
+
246
+ const api = createAPI({
247
+ router: t.router({
248
+ users: { create: createUser },
249
+ }),
250
+ });
251
+
252
+ const executor = api;
253
+ const result = await executor.execute("users.create", { name: "John" });
254
+
255
+ expect(result.ok).toBe(false);
256
+ const events = executor.getEvents();
257
+ expect(events).toHaveLength(0);
258
+ });
259
+
260
+ it("should emit multiple events", async () => {
261
+ const { t, createAPI } = defineContext({
262
+ context: { db: { create: (data: any) => ({ id: 1, ...data }) } },
263
+ events: {
264
+ "user.created": { data: {} as { id: number } },
265
+ "email.sent": { data: {} as { to: string } },
266
+ } as any,
267
+ });
268
+
269
+ const createUser = t.mutation({
270
+ args: z.object({ name: z.string(), email: z.string() }),
271
+ handler: async (ctx, args) => {
272
+ const user = ctx.db.create(args);
273
+ ctx.send("user.created", { id: user.id });
274
+ ctx.send("email.sent", { to: args.email });
275
+ return ok(user);
276
+ },
277
+ });
278
+
279
+ const api = createAPI({
280
+ router: t.router({
281
+ users: { create: createUser },
282
+ }),
283
+ });
284
+
285
+ const executor = api;
286
+ await executor.execute("users.create", { name: "John", email: "john@example.com" });
287
+
288
+ const events = executor.getEvents();
289
+ expect(events).toHaveLength(2);
290
+ expect(events[0].name).toBe("user.created");
291
+ expect(events[1].name).toBe("email.sent");
292
+ });
293
+
294
+ it("should support namespace option", async () => {
295
+ const { t, createAPI } = defineContext({
296
+ context: { db: { create: (data: any) => ({ id: 1, ...data }) } },
297
+ events: {
298
+ "order.created": { data: {} as { id: number } },
299
+ } as any,
300
+ });
301
+
302
+ const createOrder = t.mutation({
303
+ args: z.object({ item: z.string() }),
304
+ handler: async (ctx, args) => {
305
+ const order = ctx.db.create(args);
306
+ ctx.send("order.created", { id: order.id }, { namespace: "ecommerce" });
307
+ return ok(order);
308
+ },
309
+ });
310
+
311
+ const api = createAPI({
312
+ router: t.router({
313
+ orders: { create: createOrder },
314
+ }),
315
+ });
316
+
317
+ const executor = api;
318
+ await executor.execute("orders.create", { item: "Widget" });
319
+
320
+ const events = executor.getEvents();
321
+ expect(events).toHaveLength(1);
322
+ expect(events[0].namespace).toBe("ecommerce");
323
+ });
324
+ });
325
+
326
+ describe("t.on", () => {
327
+ it("should register global event listener", async () => {
328
+ const { t, createAPI } = defineContext({
329
+ context: { db: { create: (data: any) => ({ id: 1, ...data }) } },
330
+ events: {
331
+ "user.created": { data: {} as { id: number } },
332
+ } as any,
333
+ });
334
+
335
+ const receivedEvents: any[] = [];
336
+ const unsubscribe = t.on("user.created" as any, (_ctx, event) => {
337
+ receivedEvents.push(event);
338
+ });
339
+
340
+ const createUser = t.mutation({
341
+ args: z.object({ name: z.string() }),
342
+ handler: async (ctx, args) => {
343
+ const user = ctx.db.create(args);
344
+ ctx.send("user.created", { id: user.id });
345
+ return ok(user);
346
+ },
347
+ });
348
+
349
+ const api = createAPI({
350
+ router: t.router({
351
+ users: { create: createUser },
352
+ }),
353
+ });
354
+
355
+ await api.execute("users.create", { name: "John" });
356
+
357
+ expect(receivedEvents).toHaveLength(1);
358
+ expect(receivedEvents[0].name).toBe("user.created");
359
+ expect(receivedEvents[0].data).toEqual({ id: 1 });
360
+
361
+ unsubscribe();
362
+ });
363
+
364
+ it("should return unsubscribe function", async () => {
365
+ const { t, createAPI } = defineContext({
366
+ context: { db: { create: (data: any) => ({ id: 1, ...data }) } },
367
+ events: {
368
+ "user.created": { data: {} as { id: number } },
369
+ } as any,
370
+ });
371
+
372
+ const receivedEvents: any[] = [];
373
+ const unsubscribe = t.on("user.created" as any, (_ctx, event) => {
374
+ receivedEvents.push(event);
375
+ });
376
+
377
+ const createUser = t.mutation({
378
+ args: z.object({ name: z.string() }),
379
+ handler: async (ctx, args) => {
380
+ const user = ctx.db.create(args);
381
+ ctx.send("user.created", { id: user.id });
382
+ return ok(user);
383
+ },
384
+ });
385
+
386
+ const api = createAPI({
387
+ router: t.router({
388
+ users: { create: createUser },
389
+ }),
390
+ });
391
+
392
+ await api.execute("users.create", { name: "John" });
393
+ expect(receivedEvents).toHaveLength(1);
394
+
395
+ // Unsubscribe
396
+ unsubscribe();
397
+
398
+ await api.execute("users.create", { name: "Jane" });
399
+ expect(receivedEvents).toHaveLength(1); // Still 1, not 2
400
+ });
401
+
402
+ it("should support wildcard pattern user.*", async () => {
403
+ const { t, createAPI } = defineContext({
404
+ context: { db: { create: (data: any) => ({ id: 1, ...data }) } },
405
+ events: {
406
+ "user.created": { data: {} as { id: number } },
407
+ "user.updated": { data: {} as { id: number } },
408
+ "user.deleted": { data: {} as { id: number } },
409
+ "post.created": { data: {} as { id: number } },
410
+ } as any,
411
+ });
412
+
413
+ const receivedEvents: any[] = [];
414
+ t.on("user.*" as any, (_ctx, event) => {
415
+ receivedEvents.push(event);
416
+ });
417
+
418
+ const createUser = t.mutation({
419
+ args: z.object({ name: z.string() }),
420
+ handler: async (ctx, args) => {
421
+ const user = ctx.db.create(args);
422
+ ctx.send("user.created", { id: user.id });
423
+ return ok(user);
424
+ },
425
+ });
426
+
427
+ const updateUser = t.mutation({
428
+ args: z.object({ id: z.number(), name: z.string() }),
429
+ handler: async (ctx, args) => {
430
+ ctx.send("user.updated", { id: args.id });
431
+ return ok({ id: args.id, name: args.name });
432
+ },
433
+ });
434
+
435
+ const createPost = t.mutation({
436
+ args: z.object({ title: z.string() }),
437
+ handler: async (ctx, args) => {
438
+ const post = ctx.db.create(args);
439
+ ctx.send("post.created", { id: post.id });
440
+ return ok(post);
441
+ },
442
+ });
443
+
444
+ const api = createAPI({
445
+ router: t.router({
446
+ users: { create: createUser, update: updateUser },
447
+ posts: { create: createPost },
448
+ }),
449
+ });
450
+
451
+ await api.execute("users.create", { name: "John" });
452
+ await api.execute("users.update", { id: 1, name: "Jane" });
453
+ await api.execute("posts.create", { title: "Hello" });
454
+
455
+ // Should only receive user.* events, not post.*
456
+ expect(receivedEvents).toHaveLength(2);
457
+ expect(receivedEvents[0].name).toBe("user.created");
458
+ expect(receivedEvents[1].name).toBe("user.updated");
459
+ });
460
+
461
+ it("should support wildcard pattern *", async () => {
462
+ const { t, createAPI } = defineContext({
463
+ context: { db: { create: (data: any) => ({ id: 1, ...data }) } },
464
+ events: {
465
+ "user.created": { data: {} as { id: number } },
466
+ "post.created": { data: {} as { id: number } },
467
+ } as any,
468
+ });
469
+
470
+ const receivedEvents: any[] = [];
471
+ t.on("*" as any, (_ctx, event) => {
472
+ receivedEvents.push(event);
473
+ });
474
+
475
+ const createUser = t.mutation({
476
+ args: z.object({ name: z.string() }),
477
+ handler: async (ctx, args) => {
478
+ const user = ctx.db.create(args);
479
+ ctx.send("user.created", { id: user.id });
480
+ return ok(user);
481
+ },
482
+ });
483
+
484
+ const createPost = t.mutation({
485
+ args: z.object({ title: z.string() }),
486
+ handler: async (ctx, args) => {
487
+ const post = ctx.db.create(args);
488
+ ctx.send("post.created", { id: post.id });
489
+ return ok(post);
490
+ },
491
+ });
492
+
493
+ const api = createAPI({
494
+ router: t.router({
495
+ users: { create: createUser },
496
+ posts: { create: createPost },
497
+ }),
498
+ });
499
+
500
+ await api.execute("users.create", { name: "John" });
501
+ await api.execute("posts.create", { title: "Hello" });
502
+
503
+ expect(receivedEvents).toHaveLength(2);
504
+ });
118
505
  });