@sylphx/lens-server 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +14 -3
- package/dist/index.js +299 -100
- package/package.json +2 -2
- package/src/e2e/server.test.ts +81 -61
- package/src/handlers/framework.ts +4 -3
- package/src/handlers/http.ts +7 -4
- package/src/handlers/ws.ts +18 -10
- package/src/server/create.test.ts +701 -47
- package/src/server/create.ts +492 -105
- package/src/server/selection.test.ts +253 -0
- package/src/server/types.ts +15 -2
package/src/e2e/server.test.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { describe, expect, it } from "bun:test";
|
|
11
|
-
import { entity, lens, mutation, query, t } from "@sylphx/lens-core";
|
|
11
|
+
import { entity, firstValueFrom, lens, mutation, query, t } from "@sylphx/lens-core";
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
import { optimisticPlugin } from "../plugin/optimistic.js";
|
|
14
14
|
import { createApp } from "../server/create.js";
|
|
@@ -58,7 +58,7 @@ describe("E2E - Basic Operations", () => {
|
|
|
58
58
|
queries: { getUsers },
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
const result = await server.execute({ path: "getUsers" });
|
|
61
|
+
const result = await firstValueFrom(server.execute({ path: "getUsers" }));
|
|
62
62
|
|
|
63
63
|
expect(result.error).toBeUndefined();
|
|
64
64
|
expect(result.data).toEqual(mockUsers);
|
|
@@ -79,10 +79,12 @@ describe("E2E - Basic Operations", () => {
|
|
|
79
79
|
queries: { getUser },
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
-
const result = await
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
const result = await firstValueFrom(
|
|
83
|
+
server.execute({
|
|
84
|
+
path: "getUser",
|
|
85
|
+
input: { id: "user-1" },
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
86
88
|
|
|
87
89
|
expect(result.error).toBeUndefined();
|
|
88
90
|
expect(result.data).toEqual(mockUsers[0]);
|
|
@@ -104,10 +106,12 @@ describe("E2E - Basic Operations", () => {
|
|
|
104
106
|
mutations: { createUser },
|
|
105
107
|
});
|
|
106
108
|
|
|
107
|
-
const result = await
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
const result = await firstValueFrom(
|
|
110
|
+
server.execute({
|
|
111
|
+
path: "createUser",
|
|
112
|
+
input: { name: "Charlie", email: "charlie@example.com" },
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
111
115
|
|
|
112
116
|
expect(result.error).toBeUndefined();
|
|
113
117
|
expect(result.data).toEqual({
|
|
@@ -129,10 +133,12 @@ describe("E2E - Basic Operations", () => {
|
|
|
129
133
|
queries: { failingQuery },
|
|
130
134
|
});
|
|
131
135
|
|
|
132
|
-
const result = await
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
const result = await firstValueFrom(
|
|
137
|
+
server.execute({
|
|
138
|
+
path: "failingQuery",
|
|
139
|
+
input: { id: "123" },
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
136
142
|
|
|
137
143
|
expect(result.data).toBeUndefined();
|
|
138
144
|
expect(result.error).toBeInstanceOf(Error);
|
|
@@ -142,10 +148,12 @@ describe("E2E - Basic Operations", () => {
|
|
|
142
148
|
it("handles unknown operation", async () => {
|
|
143
149
|
const server = createApp({});
|
|
144
150
|
|
|
145
|
-
const result = await
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
151
|
+
const result = await firstValueFrom(
|
|
152
|
+
server.execute({
|
|
153
|
+
path: "unknownOperation",
|
|
154
|
+
input: {},
|
|
155
|
+
}),
|
|
156
|
+
);
|
|
149
157
|
|
|
150
158
|
expect(result.data).toBeUndefined();
|
|
151
159
|
expect(result.error?.message).toContain("not found");
|
|
@@ -172,10 +180,12 @@ describe("E2E - Context", () => {
|
|
|
172
180
|
context: () => ({ userId: "ctx-user-1", role: "admin" }),
|
|
173
181
|
});
|
|
174
182
|
|
|
175
|
-
await
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
183
|
+
await firstValueFrom(
|
|
184
|
+
server.execute({
|
|
185
|
+
path: "getUser",
|
|
186
|
+
input: { id: "user-1" },
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
179
189
|
|
|
180
190
|
expect(capturedContext).toMatchObject({
|
|
181
191
|
userId: "ctx-user-1",
|
|
@@ -201,10 +211,12 @@ describe("E2E - Context", () => {
|
|
|
201
211
|
},
|
|
202
212
|
});
|
|
203
213
|
|
|
204
|
-
await
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
214
|
+
await firstValueFrom(
|
|
215
|
+
server.execute({
|
|
216
|
+
path: "getUser",
|
|
217
|
+
input: { id: "user-1" },
|
|
218
|
+
}),
|
|
219
|
+
);
|
|
208
220
|
|
|
209
221
|
expect(capturedContext).toMatchObject({
|
|
210
222
|
userId: "async-user",
|
|
@@ -232,13 +244,15 @@ describe("E2E - Selection", () => {
|
|
|
232
244
|
queries: { getUser },
|
|
233
245
|
});
|
|
234
246
|
|
|
235
|
-
const result = await
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
247
|
+
const result = await firstValueFrom(
|
|
248
|
+
server.execute({
|
|
249
|
+
path: "getUser",
|
|
250
|
+
input: {
|
|
251
|
+
id: "user-1",
|
|
252
|
+
$select: { name: true },
|
|
253
|
+
},
|
|
254
|
+
}),
|
|
255
|
+
);
|
|
242
256
|
|
|
243
257
|
expect(result.error).toBeUndefined();
|
|
244
258
|
// Should include id (always) and selected fields
|
|
@@ -262,13 +276,15 @@ describe("E2E - Selection", () => {
|
|
|
262
276
|
queries: { getUser },
|
|
263
277
|
});
|
|
264
278
|
|
|
265
|
-
const result = await
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
279
|
+
const result = await firstValueFrom(
|
|
280
|
+
server.execute({
|
|
281
|
+
path: "getUser",
|
|
282
|
+
input: {
|
|
283
|
+
id: "user-1",
|
|
284
|
+
$select: { email: true },
|
|
285
|
+
},
|
|
286
|
+
}),
|
|
287
|
+
);
|
|
272
288
|
|
|
273
289
|
expect(result.data).toEqual({
|
|
274
290
|
id: "user-1",
|
|
@@ -318,20 +334,22 @@ describe("E2E - Entity Resolvers", () => {
|
|
|
318
334
|
});
|
|
319
335
|
|
|
320
336
|
// Test with $select for nested posts
|
|
321
|
-
const result = await
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
337
|
+
const result = await firstValueFrom(
|
|
338
|
+
server.execute({
|
|
339
|
+
path: "getUser",
|
|
340
|
+
input: {
|
|
341
|
+
id: "user-1",
|
|
342
|
+
$select: {
|
|
343
|
+
name: true,
|
|
344
|
+
posts: {
|
|
345
|
+
select: {
|
|
346
|
+
title: true,
|
|
347
|
+
},
|
|
330
348
|
},
|
|
331
349
|
},
|
|
332
350
|
},
|
|
333
|
-
},
|
|
334
|
-
|
|
351
|
+
}),
|
|
352
|
+
);
|
|
335
353
|
|
|
336
354
|
expect(result.error).toBeUndefined();
|
|
337
355
|
expect(result.data).toMatchObject({
|
|
@@ -380,19 +398,21 @@ describe("E2E - Entity Resolvers", () => {
|
|
|
380
398
|
});
|
|
381
399
|
|
|
382
400
|
// Execute query with nested selection for all users
|
|
383
|
-
const result = await
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
401
|
+
const result = await firstValueFrom(
|
|
402
|
+
server.execute({
|
|
403
|
+
path: "getUsers",
|
|
404
|
+
input: {
|
|
405
|
+
$select: {
|
|
406
|
+
name: true,
|
|
407
|
+
posts: {
|
|
408
|
+
select: {
|
|
409
|
+
title: true,
|
|
410
|
+
},
|
|
391
411
|
},
|
|
392
412
|
},
|
|
393
413
|
},
|
|
394
|
-
},
|
|
395
|
-
|
|
414
|
+
}),
|
|
415
|
+
);
|
|
396
416
|
|
|
397
417
|
expect(result.error).toBeUndefined();
|
|
398
418
|
// Resolvers are called - exact count depends on DataLoader batching behavior
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
* ```
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
|
+
import { firstValueFrom } from "@sylphx/lens-core";
|
|
40
41
|
import type { LensServer } from "../server/create.js";
|
|
41
42
|
|
|
42
43
|
// =============================================================================
|
|
@@ -74,7 +75,7 @@ export function createServerClientProxy(server: LensServer): unknown {
|
|
|
74
75
|
},
|
|
75
76
|
async apply(_, __, args) {
|
|
76
77
|
const input = args[0];
|
|
77
|
-
const result = await server.execute({ path, input });
|
|
78
|
+
const result = await firstValueFrom(server.execute({ path, input }));
|
|
78
79
|
|
|
79
80
|
if (result.error) {
|
|
80
81
|
throw result.error;
|
|
@@ -112,7 +113,7 @@ export async function handleWebQuery(
|
|
|
112
113
|
const inputParam = url.searchParams.get("input");
|
|
113
114
|
const input = inputParam ? JSON.parse(inputParam) : undefined;
|
|
114
115
|
|
|
115
|
-
const result = await server.execute({ path, input });
|
|
116
|
+
const result = await firstValueFrom(server.execute({ path, input }));
|
|
116
117
|
|
|
117
118
|
if (result.error) {
|
|
118
119
|
return Response.json({ error: result.error.message }, { status: 400 });
|
|
@@ -147,7 +148,7 @@ export async function handleWebMutation(
|
|
|
147
148
|
const body = (await request.json()) as { input?: unknown };
|
|
148
149
|
const input = body.input;
|
|
149
150
|
|
|
150
|
-
const result = await server.execute({ path, input });
|
|
151
|
+
const result = await firstValueFrom(server.execute({ path, input }));
|
|
151
152
|
|
|
152
153
|
if (result.error) {
|
|
153
154
|
return Response.json({ error: result.error.message }, { status: 400 });
|
package/src/handlers/http.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Works with Bun, Node (with adapter), Vercel, Cloudflare Workers.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { firstValueFrom } from "@sylphx/lens-core";
|
|
8
9
|
import type { LensServer } from "../server/create.js";
|
|
9
10
|
|
|
10
11
|
// =============================================================================
|
|
@@ -139,10 +140,12 @@ export function createHTTPHandler(
|
|
|
139
140
|
});
|
|
140
141
|
}
|
|
141
142
|
|
|
142
|
-
const result = await
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
const result = await firstValueFrom(
|
|
144
|
+
server.execute({
|
|
145
|
+
path: operationPath,
|
|
146
|
+
input: body.input,
|
|
147
|
+
}),
|
|
148
|
+
);
|
|
146
149
|
|
|
147
150
|
if (result.error) {
|
|
148
151
|
return new Response(JSON.stringify({ error: result.error.message }), {
|
package/src/handlers/ws.ts
CHANGED
|
@@ -22,7 +22,11 @@
|
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import
|
|
25
|
+
import {
|
|
26
|
+
firstValueFrom,
|
|
27
|
+
type ReconnectMessage,
|
|
28
|
+
type ReconnectSubscription,
|
|
29
|
+
} from "@sylphx/lens-core";
|
|
26
30
|
import type { LensServer, WebSocketLike } from "../server/create.js";
|
|
27
31
|
import type {
|
|
28
32
|
ClientConnection,
|
|
@@ -174,7 +178,7 @@ export function createWSHandler(server: LensServer, options: WSHandlerOptions =
|
|
|
174
178
|
// Execute query first to get data
|
|
175
179
|
let result: { data?: unknown; error?: Error };
|
|
176
180
|
try {
|
|
177
|
-
result = await server.execute({ path: operation, input });
|
|
181
|
+
result = await firstValueFrom(server.execute({ path: operation, input }));
|
|
178
182
|
|
|
179
183
|
if (result.error) {
|
|
180
184
|
conn.ws.send(
|
|
@@ -349,10 +353,12 @@ export function createWSHandler(server: LensServer, options: WSHandlerOptions =
|
|
|
349
353
|
// Handle query
|
|
350
354
|
async function handleQuery(conn: ClientConnection, message: QueryMessage): Promise<void> {
|
|
351
355
|
try {
|
|
352
|
-
const result = await
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
+
const result = await firstValueFrom(
|
|
357
|
+
server.execute({
|
|
358
|
+
path: message.operation,
|
|
359
|
+
input: message.input,
|
|
360
|
+
}),
|
|
361
|
+
);
|
|
356
362
|
|
|
357
363
|
if (result.error) {
|
|
358
364
|
conn.ws.send(
|
|
@@ -389,10 +395,12 @@ export function createWSHandler(server: LensServer, options: WSHandlerOptions =
|
|
|
389
395
|
// Handle mutation
|
|
390
396
|
async function handleMutation(conn: ClientConnection, message: MutationMessage): Promise<void> {
|
|
391
397
|
try {
|
|
392
|
-
const result = await
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
398
|
+
const result = await firstValueFrom(
|
|
399
|
+
server.execute({
|
|
400
|
+
path: message.operation,
|
|
401
|
+
input: message.input,
|
|
402
|
+
}),
|
|
403
|
+
);
|
|
396
404
|
|
|
397
405
|
if (result.error) {
|
|
398
406
|
conn.ws.send(
|