@sylphx/lens-server 1.3.2 → 1.5.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/README.md +35 -0
- package/dist/index.d.ts +23 -109
- package/dist/index.js +58 -38
- package/package.json +37 -36
- package/src/e2e/server.test.ts +56 -45
- package/src/index.ts +26 -29
- package/src/server/create.test.ts +997 -20
- package/src/server/create.ts +82 -85
- package/src/sse/handler.ts +1 -1
- package/src/state/graph-state-manager.test.ts +566 -10
- package/src/state/graph-state-manager.ts +38 -13
- package/src/state/index.ts +3 -3
package/src/e2e/server.test.ts
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { describe, expect, it } from "bun:test";
|
|
14
|
-
import {
|
|
14
|
+
import { applyUpdate, entity, lens, mutation, query, t, type Update } from "@sylphx/lens-core";
|
|
15
15
|
import { z } from "zod";
|
|
16
|
-
import { type WebSocketLike
|
|
16
|
+
import { createServer, type WebSocketLike } from "../server/create";
|
|
17
17
|
|
|
18
18
|
// =============================================================================
|
|
19
19
|
// Test Fixtures
|
|
@@ -40,7 +40,7 @@ const mockUsers = [
|
|
|
40
40
|
{ id: "user-2", name: "Bob", email: "bob@example.com", status: "offline" },
|
|
41
41
|
];
|
|
42
42
|
|
|
43
|
-
const
|
|
43
|
+
const _mockPosts = [
|
|
44
44
|
{ id: "post-1", title: "Hello", content: "World", authorId: "user-1" },
|
|
45
45
|
{ id: "post-2", title: "Test", content: "Post", authorId: "user-1" },
|
|
46
46
|
];
|
|
@@ -65,10 +65,7 @@ function createMockClient(server: ReturnType<typeof createServer>) {
|
|
|
65
65
|
lastData: unknown;
|
|
66
66
|
}
|
|
67
67
|
>();
|
|
68
|
-
const pending = new Map<
|
|
69
|
-
string,
|
|
70
|
-
{ resolve: (data: unknown) => void; reject: (error: Error) => void }
|
|
71
|
-
>();
|
|
68
|
+
const pending = new Map<string, { resolve: (data: unknown) => void; reject: (error: Error) => void }>();
|
|
72
69
|
|
|
73
70
|
let messageIdCounter = 0;
|
|
74
71
|
const nextId = () => `msg_${++messageIdCounter}`;
|
|
@@ -237,7 +234,11 @@ describe("E2E - Basic Operations", () => {
|
|
|
237
234
|
const getUser = query()
|
|
238
235
|
.input(z.object({ id: z.string() }))
|
|
239
236
|
.returns(User)
|
|
240
|
-
.resolve(({ input }) =>
|
|
237
|
+
.resolve(({ input }) => {
|
|
238
|
+
const user = mockUsers.find((u) => u.id === input.id);
|
|
239
|
+
if (!user) throw new Error("Not found");
|
|
240
|
+
return user;
|
|
241
|
+
});
|
|
241
242
|
|
|
242
243
|
const server = createServer({
|
|
243
244
|
entities: { User },
|
|
@@ -283,7 +284,11 @@ describe("E2E - Subscriptions", () => {
|
|
|
283
284
|
const getUser = query()
|
|
284
285
|
.input(z.object({ id: z.string() }))
|
|
285
286
|
.returns(User)
|
|
286
|
-
.resolve(({ input }) =>
|
|
287
|
+
.resolve(({ input }) => {
|
|
288
|
+
const user = mockUsers.find((u) => u.id === input.id);
|
|
289
|
+
if (!user) throw new Error("Not found");
|
|
290
|
+
return user;
|
|
291
|
+
});
|
|
287
292
|
|
|
288
293
|
const server = createServer({
|
|
289
294
|
entities: { User },
|
|
@@ -314,7 +319,9 @@ describe("E2E - Subscriptions", () => {
|
|
|
314
319
|
.returns(User)
|
|
315
320
|
.resolve(({ input, emit }) => {
|
|
316
321
|
emitFn = emit;
|
|
317
|
-
|
|
322
|
+
const user = mockUsers.find((u) => u.id === input.id);
|
|
323
|
+
if (!user) throw new Error("Not found");
|
|
324
|
+
return user;
|
|
318
325
|
});
|
|
319
326
|
|
|
320
327
|
const server = createServer({
|
|
@@ -357,7 +364,9 @@ describe("E2E - Subscriptions", () => {
|
|
|
357
364
|
.returns(User)
|
|
358
365
|
.resolve(({ input, emit }) => {
|
|
359
366
|
emitFn = emit;
|
|
360
|
-
|
|
367
|
+
const user = mockUsers.find((u) => u.id === input.id);
|
|
368
|
+
if (!user) throw new Error("Not found");
|
|
369
|
+
return user;
|
|
361
370
|
});
|
|
362
371
|
|
|
363
372
|
const server = createServer({
|
|
@@ -407,9 +416,7 @@ describe("E2E - Server API", () => {
|
|
|
407
416
|
const searchUsers = query()
|
|
408
417
|
.input(z.object({ query: z.string() }))
|
|
409
418
|
.returns([User])
|
|
410
|
-
.resolve(({ input }) =>
|
|
411
|
-
mockUsers.filter((u) => u.name.toLowerCase().includes(input.query.toLowerCase())),
|
|
412
|
-
);
|
|
419
|
+
.resolve(({ input }) => mockUsers.filter((u) => u.name.toLowerCase().includes(input.query.toLowerCase())));
|
|
413
420
|
|
|
414
421
|
const server = createServer({
|
|
415
422
|
entities: { User },
|
|
@@ -461,7 +468,9 @@ describe("E2E - Cleanup", () => {
|
|
|
461
468
|
onCleanup(() => {
|
|
462
469
|
cleanedUp = true;
|
|
463
470
|
});
|
|
464
|
-
|
|
471
|
+
const user = mockUsers.find((u) => u.id === input.id);
|
|
472
|
+
if (!user) throw new Error("Not found");
|
|
473
|
+
return user;
|
|
465
474
|
});
|
|
466
475
|
|
|
467
476
|
const server = createServer({
|
|
@@ -499,7 +508,9 @@ describe("E2E - GraphStateManager", () => {
|
|
|
499
508
|
.returns(User)
|
|
500
509
|
.resolve(({ input, emit }) => {
|
|
501
510
|
emitFn = emit;
|
|
502
|
-
|
|
511
|
+
const user = mockUsers.find((u) => u.id === input.id);
|
|
512
|
+
if (!user) throw new Error("Not found");
|
|
513
|
+
return user;
|
|
503
514
|
});
|
|
504
515
|
|
|
505
516
|
const updateUser = mutation()
|
|
@@ -566,22 +577,25 @@ describe("E2E - Entity Resolvers", () => {
|
|
|
566
577
|
const getUser = query()
|
|
567
578
|
.input(z.object({ id: z.string() }))
|
|
568
579
|
.returns(User)
|
|
569
|
-
.resolve(({ input }) =>
|
|
580
|
+
.resolve(({ input }) => {
|
|
581
|
+
const user = users.find((u) => u.id === input.id);
|
|
582
|
+
if (!user) throw new Error("Not found");
|
|
583
|
+
return user;
|
|
584
|
+
});
|
|
570
585
|
|
|
571
|
-
// Create entity resolvers
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
};
|
|
586
|
+
// Create entity resolvers using lens() factory
|
|
587
|
+
const { resolver } = lens();
|
|
588
|
+
const userResolver = resolver(User, (f) => ({
|
|
589
|
+
id: f.expose("id"),
|
|
590
|
+
name: f.expose("name"),
|
|
591
|
+
email: f.expose("email"),
|
|
592
|
+
posts: f.many(Post).resolve(({ parent }) => posts.filter((p) => p.authorId === parent.id)),
|
|
593
|
+
}));
|
|
580
594
|
|
|
581
595
|
const server = createServer({
|
|
582
596
|
entities: { User, Post },
|
|
583
597
|
queries: { getUser },
|
|
584
|
-
resolvers:
|
|
598
|
+
resolvers: [userResolver],
|
|
585
599
|
});
|
|
586
600
|
|
|
587
601
|
// Test with $select for nested posts
|
|
@@ -625,26 +639,22 @@ describe("E2E - Entity Resolvers", () => {
|
|
|
625
639
|
.returns([User])
|
|
626
640
|
.resolve(() => users);
|
|
627
641
|
|
|
628
|
-
// Create
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
}
|
|
640
|
-
return undefined;
|
|
641
|
-
},
|
|
642
|
-
};
|
|
642
|
+
// Create entity resolvers using lens() factory
|
|
643
|
+
const { resolver } = lens();
|
|
644
|
+
const userResolver = resolver(User, (f) => ({
|
|
645
|
+
id: f.expose("id"),
|
|
646
|
+
name: f.expose("name"),
|
|
647
|
+
posts: f.many(Post).resolve(({ parent }) => {
|
|
648
|
+
// Simple resolve - batching is not part of the new pattern
|
|
649
|
+
batchCallCount++;
|
|
650
|
+
return posts.filter((p) => p.authorId === parent.id);
|
|
651
|
+
}),
|
|
652
|
+
}));
|
|
643
653
|
|
|
644
654
|
const server = createServer({
|
|
645
655
|
entities: { User, Post },
|
|
646
656
|
queries: { getUsers },
|
|
647
|
-
resolvers:
|
|
657
|
+
resolvers: [userResolver],
|
|
648
658
|
});
|
|
649
659
|
|
|
650
660
|
// Execute query with nested selection for all users
|
|
@@ -659,8 +669,9 @@ describe("E2E - Entity Resolvers", () => {
|
|
|
659
669
|
},
|
|
660
670
|
});
|
|
661
671
|
|
|
662
|
-
//
|
|
663
|
-
|
|
672
|
+
// With the new pattern, each user's posts resolver is called individually
|
|
673
|
+
// So batch call count will be equal to number of users
|
|
674
|
+
expect(batchCallCount).toBe(2);
|
|
664
675
|
expect(result).toHaveLength(2);
|
|
665
676
|
});
|
|
666
677
|
});
|
package/src/index.ts
CHANGED
|
@@ -10,20 +10,18 @@
|
|
|
10
10
|
// =============================================================================
|
|
11
11
|
|
|
12
12
|
export {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
type InferRouterContext,
|
|
14
|
+
type MutationDef,
|
|
15
15
|
mutation,
|
|
16
|
-
router,
|
|
17
16
|
// Types
|
|
18
|
-
type QueryBuilder,
|
|
19
|
-
type MutationBuilder,
|
|
20
17
|
type QueryDef,
|
|
21
|
-
|
|
18
|
+
// Operations
|
|
19
|
+
query,
|
|
20
|
+
type ResolverContext,
|
|
21
|
+
type ResolverFn,
|
|
22
22
|
type RouterDef,
|
|
23
23
|
type RouterRoutes,
|
|
24
|
-
|
|
25
|
-
type ResolverContext,
|
|
26
|
-
type InferRouterContext,
|
|
24
|
+
router,
|
|
27
25
|
} from "@sylphx/lens-core";
|
|
28
26
|
|
|
29
27
|
// =============================================================================
|
|
@@ -33,26 +31,25 @@ export {
|
|
|
33
31
|
export {
|
|
34
32
|
// Factory
|
|
35
33
|
createServer,
|
|
34
|
+
type EntitiesMap,
|
|
35
|
+
// Type inference utilities (tRPC-style)
|
|
36
|
+
type InferApi,
|
|
37
|
+
type InferInput,
|
|
38
|
+
type InferOutput,
|
|
39
|
+
// In-process transport types
|
|
40
|
+
type LensOperation,
|
|
41
|
+
type LensResult,
|
|
36
42
|
// Types
|
|
37
43
|
type LensServer,
|
|
38
44
|
type LensServerConfig as ServerConfig,
|
|
39
|
-
type EntitiesMap,
|
|
40
|
-
type RelationsArray,
|
|
41
|
-
type QueriesMap,
|
|
42
45
|
type MutationsMap,
|
|
43
|
-
type
|
|
46
|
+
type OperationMeta,
|
|
47
|
+
type OperationsMap,
|
|
48
|
+
type QueriesMap,
|
|
44
49
|
type SelectionObject,
|
|
45
50
|
// Metadata types (for transport handshake)
|
|
46
51
|
type ServerMetadata,
|
|
47
|
-
type
|
|
48
|
-
type OperationsMap,
|
|
49
|
-
// In-process transport types
|
|
50
|
-
type LensOperation,
|
|
51
|
-
type LensResult,
|
|
52
|
-
// Type inference utilities (tRPC-style)
|
|
53
|
-
type InferApi,
|
|
54
|
-
type InferInput,
|
|
55
|
-
type InferOutput,
|
|
52
|
+
type WebSocketLike,
|
|
56
53
|
} from "./server/create";
|
|
57
54
|
|
|
58
55
|
// =============================================================================
|
|
@@ -60,17 +57,17 @@ export {
|
|
|
60
57
|
// =============================================================================
|
|
61
58
|
|
|
62
59
|
export {
|
|
63
|
-
// Class
|
|
64
|
-
GraphStateManager,
|
|
65
60
|
// Factory
|
|
66
61
|
createGraphStateManager,
|
|
67
62
|
// Types
|
|
68
63
|
type EntityKey,
|
|
64
|
+
// Class
|
|
65
|
+
GraphStateManager,
|
|
66
|
+
type GraphStateManagerConfig,
|
|
69
67
|
type StateClient,
|
|
70
|
-
type StateUpdateMessage,
|
|
71
68
|
type StateFullMessage,
|
|
69
|
+
type StateUpdateMessage,
|
|
72
70
|
type Subscription,
|
|
73
|
-
type GraphStateManagerConfig,
|
|
74
71
|
} from "./state";
|
|
75
72
|
|
|
76
73
|
// =============================================================================
|
|
@@ -78,11 +75,11 @@ export {
|
|
|
78
75
|
// =============================================================================
|
|
79
76
|
|
|
80
77
|
export {
|
|
81
|
-
// Class
|
|
82
|
-
SSEHandler,
|
|
83
78
|
// Factory
|
|
84
79
|
createSSEHandler,
|
|
80
|
+
type SSEClientInfo,
|
|
81
|
+
// Class
|
|
82
|
+
SSEHandler,
|
|
85
83
|
// Types
|
|
86
84
|
type SSEHandlerConfig,
|
|
87
|
-
type SSEClientInfo,
|
|
88
85
|
} from "./sse/handler";
|