@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.
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/dist/api/factory.d.ts +8 -8
- package/dist/api/factory.d.ts.map +1 -1
- package/dist/api/factory.js +181 -109
- package/dist/api/factory.js.map +1 -1
- package/dist/api/index.d.ts +2 -2
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +1 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/types.d.ts +31 -16
- package/dist/api/types.d.ts.map +1 -1
- package/dist/context/builder.d.ts +5 -4
- package/dist/context/builder.d.ts.map +1 -1
- package/dist/context/builder.js +8 -3
- package/dist/context/builder.js.map +1 -1
- package/dist/context/types.d.ts +10 -3
- package/dist/context/types.d.ts.map +1 -1
- package/dist/errors/server-error.d.ts +9 -3
- package/dist/errors/server-error.d.ts.map +1 -1
- package/dist/errors/server-error.js +16 -4
- package/dist/errors/server-error.js.map +1 -1
- package/dist/errors/types.d.ts +1 -0
- package/dist/errors/types.d.ts.map +1 -1
- package/dist/events/dsl.d.ts +55 -0
- package/dist/events/dsl.d.ts.map +1 -0
- package/dist/events/dsl.js +42 -0
- package/dist/events/dsl.js.map +1 -0
- package/dist/events/emitter.d.ts +27 -6
- package/dist/events/emitter.d.ts.map +1 -1
- package/dist/events/emitter.js +193 -23
- package/dist/events/emitter.js.map +1 -1
- package/dist/events/index.d.ts +4 -1
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +3 -1
- package/dist/events/index.js.map +1 -1
- package/dist/events/queue.d.ts +19 -0
- package/dist/events/queue.d.ts.map +1 -0
- package/dist/events/queue.js +54 -0
- package/dist/events/queue.js.map +1 -0
- package/dist/events/types.d.ts +1 -1
- package/dist/events/types.d.ts.map +1 -1
- package/dist/hooks/executor.d.ts +3 -3
- package/dist/hooks/executor.d.ts.map +1 -1
- package/dist/hooks/types.d.ts +4 -4
- package/dist/hooks/types.d.ts.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/internal-mutation/builder.d.ts +13 -10
- package/dist/internal-mutation/builder.d.ts.map +1 -1
- package/dist/internal-mutation/builder.js +57 -1
- package/dist/internal-mutation/builder.js.map +1 -1
- package/dist/internal-mutation/types.d.ts +5 -4
- package/dist/internal-mutation/types.d.ts.map +1 -1
- package/dist/internal-query/builder.d.ts +13 -10
- package/dist/internal-query/builder.d.ts.map +1 -1
- package/dist/internal-query/builder.js +56 -1
- package/dist/internal-query/builder.js.map +1 -1
- package/dist/internal-query/types.d.ts +4 -3
- package/dist/internal-query/types.d.ts.map +1 -1
- package/dist/middleware/builder.d.ts +1 -1
- package/dist/middleware/builder.d.ts.map +1 -1
- package/dist/middleware/helpers.d.ts +28 -0
- package/dist/middleware/helpers.d.ts.map +1 -0
- package/dist/middleware/helpers.js +31 -0
- package/dist/middleware/helpers.js.map +1 -0
- package/dist/middleware/types.d.ts.map +1 -1
- package/dist/mutation/builder.d.ts +13 -10
- package/dist/mutation/builder.d.ts.map +1 -1
- package/dist/mutation/builder.js +55 -1
- package/dist/mutation/builder.js.map +1 -1
- package/dist/mutation/types.d.ts +5 -4
- package/dist/mutation/types.d.ts.map +1 -1
- package/dist/query/builder.d.ts +18 -18
- package/dist/query/builder.d.ts.map +1 -1
- package/dist/query/builder.js +83 -32
- package/dist/query/builder.js.map +1 -1
- package/dist/query/types.d.ts +21 -18
- package/dist/query/types.d.ts.map +1 -1
- package/dist/router/builder.d.ts +4 -12
- package/dist/router/builder.d.ts.map +1 -1
- package/dist/router/builder.js +10 -32
- package/dist/router/builder.js.map +1 -1
- package/dist/router/types.d.ts +78 -2
- package/dist/router/types.d.ts.map +1 -1
- package/dist/type-test.d.ts +2 -0
- package/dist/type-test.d.ts.map +1 -0
- package/dist/type-test.js +2 -0
- package/dist/type-test.js.map +1 -0
- package/dist/types.d.ts +28 -7
- package/dist/types.d.ts.map +1 -1
- package/eslint.config.js +124 -1
- package/package.json +42 -38
- package/src/api/factory.ts +243 -160
- package/src/api/index.ts +2 -2
- package/src/api/types.ts +45 -18
- package/src/context/builder.ts +16 -10
- package/src/context/types.ts +11 -4
- package/src/errors/server-error.ts +20 -9
- package/src/errors/types.ts +1 -0
- package/src/events/dsl.ts +65 -0
- package/src/events/emitter.ts +224 -35
- package/src/events/index.ts +4 -1
- package/src/events/queue.ts +70 -0
- package/src/events/types.ts +1 -3
- package/src/hooks/executor.ts +4 -4
- package/src/hooks/types.ts +6 -11
- package/src/index.ts +11 -4
- package/src/internal-mutation/builder.ts +110 -36
- package/src/internal-mutation/types.ts +6 -5
- package/src/internal-query/builder.ts +102 -34
- package/src/internal-query/types.ts +5 -4
- package/src/middleware/builder.ts +1 -1
- package/src/middleware/helpers.ts +87 -0
- package/src/middleware/types.ts +1 -3
- package/src/mutation/builder.ts +106 -33
- package/src/mutation/types.ts +6 -5
- package/src/procedure/types.ts +1 -1
- package/src/query/builder.ts +151 -101
- package/src/query/types.ts +29 -19
- package/src/router/builder.ts +21 -46
- package/src/router/types.ts +120 -3
- package/src/types.ts +41 -38
- package/tests/index.test.ts +389 -2
- package/tests/type-safety.test.ts +496 -0
- package/dist/api.d.ts +0 -31
- package/dist/api.d.ts.map +0 -1
- package/dist/api.js +0 -155
- package/dist/api.js.map +0 -1
- package/dist/context.d.ts +0 -16
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -25
- package/dist/context.js.map +0 -1
- package/dist/errors.d.ts +0 -30
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -79
- package/dist/errors.js.map +0 -1
- package/dist/events.d.ts +0 -14
- package/dist/events.d.ts.map +0 -1
- package/dist/events.js +0 -42
- package/dist/events.js.map +0 -1
- package/dist/hooks.d.ts +0 -11
- package/dist/hooks.d.ts.map +0 -1
- package/dist/hooks.js +0 -26
- package/dist/hooks.js.map +0 -1
- package/dist/procedures.d.ts +0 -23
- package/dist/procedures.d.ts.map +0 -1
- package/dist/procedures.js +0 -4
- package/dist/procedures.js.map +0 -1
- package/dist/query-builder.d.ts +0 -37
- package/dist/query-builder.d.ts.map +0 -1
- package/dist/query-builder.js +0 -103
- package/dist/query-builder.js.map +0 -1
- package/dist/router.d.ts +0 -32
- package/dist/router.d.ts.map +0 -1
- package/dist/router.js +0 -98
- package/dist/router.js.map +0 -1
package/src/types.ts
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type
|
|
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
|
|
61
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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:
|
|
85
|
+
data: T;
|
|
86
|
+
timestamp: string;
|
|
87
|
+
namespace: string;
|
|
88
|
+
source?: string;
|
|
103
89
|
}
|
|
104
90
|
|
|
105
|
-
|
|
106
|
-
|
|
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
|
+
};
|
package/tests/index.test.ts
CHANGED
|
@@ -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
|
});
|