@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.
@@ -11,9 +11,9 @@
11
11
  */
12
12
 
13
13
  import { describe, expect, it } from "bun:test";
14
- import { type Update, applyUpdate, entity, mutation, query, t } from "@sylphx/lens-core";
14
+ import { applyUpdate, entity, lens, mutation, query, t, type Update } from "@sylphx/lens-core";
15
15
  import { z } from "zod";
16
- import { type WebSocketLike, createServer } from "../server/create";
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 mockPosts = [
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 }) => mockUsers.find((u) => u.id === input.id) ?? null);
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 }) => mockUsers.find((u) => u.id === input.id) ?? null);
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
- return mockUsers.find((u) => u.id === input.id) ?? null;
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
- return mockUsers.find((u) => u.id === input.id) ?? null;
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
- return mockUsers.find((u) => u.id === input.id) ?? null;
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
- return mockUsers.find((u) => u.id === input.id) ?? null;
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 }) => users.find((u) => u.id === input.id) ?? null);
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 for User.posts
572
- const resolvers = {
573
- getResolver: (entityName: string, fieldName: string) => {
574
- if (entityName === "User" && fieldName === "posts") {
575
- return (user: { id: string }) => posts.filter((p) => p.authorId === user.id);
576
- }
577
- return undefined;
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: resolvers as any,
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 batch resolver for User.posts (object with batch property)
629
- const resolvers = {
630
- getResolver: (entityName: string, fieldName: string) => {
631
- if (entityName === "User" && fieldName === "posts") {
632
- // Return batch resolver object (not a function with batch attached)
633
- return {
634
- batch: async (parents: { id: string }[]) => {
635
- batchCallCount++;
636
- return parents.map((parent) => posts.filter((p) => p.authorId === parent.id));
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: resolvers as any,
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
- // Should have batched the posts resolution into a single call
663
- expect(batchCallCount).toBe(1);
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
- // Operations
14
- query,
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
- type MutationDef,
18
+ // Operations
19
+ query,
20
+ type ResolverContext,
21
+ type ResolverFn,
22
22
  type RouterDef,
23
23
  type RouterRoutes,
24
- type ResolverFn,
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 WebSocketLike,
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 OperationMeta,
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";