@sylphx/lens-server 1.0.4 → 1.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 CHANGED
@@ -1,5 +1,6 @@
1
- import { ContextValue, EntityDef, EntityDefinition, EntityResolvers, EntityResolversDefinition, MutationDef, QueryDef, RelationDef, RelationTypeWithForeignKey, RouterDef } from "@sylphx/lens-core";
2
- import { EntityKey, Update } from "@sylphx/lens-core";
1
+ import { query, mutation, router, QueryBuilder, MutationBuilder, QueryDef as QueryDef2, MutationDef as MutationDef2, RouterDef as RouterDef2, RouterRoutes, ResolverFn, ResolverContext, InferRouterContext as InferRouterContext2 } from "@sylphx/lens-core";
2
+ import { ContextValue, EntityDef, EntityDefinition, EntityResolvers, EntityResolversDefinition, InferRouterContext, MutationDef, QueryDef, RelationDef, RelationTypeWithForeignKey, RouterDef } from "@sylphx/lens-core";
3
+ import { EntityKey, Update, EmitCommand, InternalFieldUpdate, ArrayOperation } from "@sylphx/lens-core";
3
4
  /** Client connection interface */
4
5
  interface StateClient {
5
6
  id: string;
@@ -56,8 +57,12 @@ declare class GraphStateManager {
56
57
  private clients;
57
58
  /** Canonical state per entity (server truth) */
58
59
  private canonical;
60
+ /** Canonical array state per entity (server truth for array outputs) */
61
+ private canonicalArrays;
59
62
  /** Per-client state tracking */
60
63
  private clientStates;
64
+ /** Per-client array state tracking */
65
+ private clientArrayStates;
61
66
  /** Entity → subscribed client IDs */
62
67
  private entitySubscribers;
63
68
  /** Configuration */
@@ -96,6 +101,63 @@ declare class GraphStateManager {
96
101
  replace?: boolean;
97
102
  }): void;
98
103
  /**
104
+ * Emit a field-level update with a specific strategy.
105
+ * Applies the update to canonical state and pushes to clients.
106
+ *
107
+ * @param entity - Entity name
108
+ * @param id - Entity ID
109
+ * @param field - Field name to update
110
+ * @param update - Update with strategy (value/delta/patch)
111
+ */
112
+ emitField(entity: string, id: string, field: string, update: Update): void;
113
+ /**
114
+ * Emit multiple field updates in a batch.
115
+ * More efficient than multiple emitField calls.
116
+ *
117
+ * @param entity - Entity name
118
+ * @param id - Entity ID
119
+ * @param updates - Array of field updates
120
+ */
121
+ emitBatch(entity: string, id: string, updates: InternalFieldUpdate[]): void;
122
+ /**
123
+ * Process an EmitCommand from the Emit API.
124
+ * Routes to appropriate emit method.
125
+ *
126
+ * @param entity - Entity name
127
+ * @param id - Entity ID
128
+ * @param command - Emit command from resolver
129
+ */
130
+ processCommand(entity: string, id: string, command: EmitCommand): void;
131
+ /**
132
+ * Emit array data (replace entire array).
133
+ *
134
+ * @param entity - Entity name
135
+ * @param id - Entity ID
136
+ * @param items - Array items
137
+ */
138
+ emitArray(entity: string, id: string, items: unknown[]): void;
139
+ /**
140
+ * Apply an array operation to the canonical state.
141
+ *
142
+ * @param entity - Entity name
143
+ * @param id - Entity ID
144
+ * @param operation - Array operation to apply
145
+ */
146
+ emitArrayOperation(entity: string, id: string, operation: ArrayOperation): void;
147
+ /**
148
+ * Apply an array operation and return new array.
149
+ */
150
+ private applyArrayOperation;
151
+ /**
152
+ * Push array update to a specific client.
153
+ * Computes optimal diff strategy.
154
+ */
155
+ private pushArrayToClient;
156
+ /**
157
+ * Get current canonical array state
158
+ */
159
+ getArrayState(entity: string, id: string): unknown[] | undefined;
160
+ /**
99
161
  * Get current canonical state for an entity
100
162
  */
101
163
  getState(entity: string, id: string): Record<string, unknown> | undefined;
@@ -108,6 +170,16 @@ declare class GraphStateManager {
108
170
  */
109
171
  private pushToClient;
110
172
  /**
173
+ * Push a single field update to a client.
174
+ * Computes optimal transfer strategy.
175
+ */
176
+ private pushFieldToClient;
177
+ /**
178
+ * Push multiple field updates to a client.
179
+ * Computes optimal transfer strategy for each field.
180
+ */
181
+ private pushFieldsToClient;
182
+ /**
111
183
  * Send initial data to a newly subscribed client
112
184
  */
113
185
  private sendInitialData;
@@ -157,20 +229,23 @@ type OperationsMap = {
157
229
  [key: string]: OperationMeta | OperationsMap;
158
230
  };
159
231
  /** Server configuration */
160
- interface LensServerConfig<TContext extends ContextValue = ContextValue> {
232
+ interface LensServerConfig<
233
+ TContext extends ContextValue = ContextValue,
234
+ TRouter extends RouterDef = RouterDef
235
+ > {
161
236
  /** Entity definitions */
162
237
  entities?: EntitiesMap;
163
238
  /** Relation definitions */
164
239
  relations?: RelationsArray;
165
- /** Router definition (namespaced operations) */
166
- router?: RouterDef;
240
+ /** Router definition (namespaced operations) - context type is inferred */
241
+ router?: TRouter;
167
242
  /** Query definitions (flat, legacy) */
168
243
  queries?: QueriesMap;
169
244
  /** Mutation definitions (flat, legacy) */
170
245
  mutations?: MutationsMap;
171
246
  /** Entity resolvers */
172
247
  resolvers?: EntityResolvers<EntityResolversDefinition>;
173
- /** Context factory */
248
+ /** Context factory - must return the context type expected by the router */
174
249
  context?: (req?: unknown) => TContext | Promise<TContext>;
175
250
  /** Server version */
176
251
  version?: string;
@@ -351,19 +426,78 @@ type InferApi<T extends LensServer> = T extends LensServerImpl<infer Q, infer M>
351
426
  mutations: M;
352
427
  } : never;
353
428
  /**
354
- * Create Lens server with Operations API + Optimization Layer
429
+ * Config helper type that infers context from router
355
430
  */
356
- declare function createServer<
431
+ type ServerConfigWithInferredContext<
432
+ TRouter extends RouterDef,
433
+ Q extends QueriesMap = QueriesMap,
434
+ M extends MutationsMap = MutationsMap
435
+ > = {
436
+ entities?: EntitiesMap;
437
+ relations?: RelationsArray;
438
+ router: TRouter;
439
+ queries?: Q;
440
+ mutations?: M;
441
+ resolvers?: EntityResolvers<EntityResolversDefinition>;
442
+ /** Context factory - type is inferred from router's procedures */
443
+ context?: (req?: unknown) => InferRouterContext<TRouter> | Promise<InferRouterContext<TRouter>>;
444
+ version?: string;
445
+ };
446
+ /**
447
+ * Config without router (legacy flat queries/mutations)
448
+ */
449
+ type ServerConfigLegacy<
357
450
  TContext extends ContextValue = ContextValue,
358
451
  Q extends QueriesMap = QueriesMap,
359
452
  M extends MutationsMap = MutationsMap
360
- >(config: LensServerConfig<TContext> & {
453
+ > = {
454
+ entities?: EntitiesMap;
455
+ relations?: RelationsArray;
456
+ router?: undefined;
361
457
  queries?: Q;
362
458
  mutations?: M;
363
- }): LensServer & {
459
+ resolvers?: EntityResolvers<EntityResolversDefinition>;
460
+ context?: (req?: unknown) => TContext | Promise<TContext>;
461
+ version?: string;
462
+ };
463
+ /**
464
+ * Create Lens server with Operations API + Optimization Layer
465
+ *
466
+ * When using a router with typed context (from initLens), the context
467
+ * function's return type is automatically enforced to match.
468
+ *
469
+ * @example
470
+ * ```typescript
471
+ * // Context type is inferred from router's procedures
472
+ * const server = createServer({
473
+ * router: appRouter, // RouterDef with MyContext
474
+ * context: () => ({
475
+ * db: prisma,
476
+ * user: null,
477
+ * }), // Must match MyContext!
478
+ * })
479
+ * ```
480
+ */
481
+ declare function createServer<
482
+ TRouter extends RouterDef,
483
+ Q extends QueriesMap = QueriesMap,
484
+ M extends MutationsMap = MutationsMap
485
+ >(config: ServerConfigWithInferredContext<TRouter, Q, M>): LensServer & {
486
+ _types: {
487
+ queries: Q;
488
+ mutations: M;
489
+ context: InferRouterContext<TRouter>;
490
+ };
491
+ };
492
+ declare function createServer<
493
+ TContext extends ContextValue = ContextValue,
494
+ Q extends QueriesMap = QueriesMap,
495
+ M extends MutationsMap = MutationsMap
496
+ >(config: ServerConfigLegacy<TContext, Q, M>): LensServer & {
364
497
  _types: {
365
498
  queries: Q;
366
499
  mutations: M;
500
+ context: TContext;
367
501
  };
368
502
  };
369
503
  /** SSE handler configuration */
@@ -436,4 +570,4 @@ declare class SSEHandler {
436
570
  * Create SSE handler (transport adapter)
437
571
  */
438
572
  declare function createSSEHandler(config: SSEHandlerConfig): SSEHandler;
439
- export { createServer, createSSEHandler, createGraphStateManager, WebSocketLike, Subscription, StateUpdateMessage, StateFullMessage, StateClient, ServerMetadata, LensServerConfig as ServerConfig, SelectionObject, SSEHandlerConfig, SSEHandler, SSEClientInfo, RelationsArray, QueriesMap, OperationsMap, OperationMeta, MutationsMap, LensServer, LensResult, LensOperation, InferOutput, InferInput, InferApi, GraphStateManagerConfig, GraphStateManager, EntityKey, EntitiesMap };
573
+ export { router, query, mutation, createServer, createSSEHandler, createGraphStateManager, WebSocketLike, Subscription, StateUpdateMessage, StateFullMessage, StateClient, ServerMetadata, LensServerConfig as ServerConfig, SelectionObject, SSEHandlerConfig, SSEHandler, SSEClientInfo, RouterRoutes, RouterDef2 as RouterDef, ResolverFn, ResolverContext, RelationsArray, QueryDef2 as QueryDef, QueryBuilder, QueriesMap, OperationsMap, OperationMeta, MutationsMap, MutationDef2 as MutationDef, MutationBuilder, LensServer, LensResult, LensOperation, InferRouterContext2 as InferRouterContext, InferOutput, InferInput, InferApi, GraphStateManagerConfig, GraphStateManager, EntityKey, EntitiesMap };
package/dist/index.js CHANGED
@@ -1,6 +1,14 @@
1
+ // src/index.ts
2
+ import {
3
+ query,
4
+ mutation,
5
+ router
6
+ } from "@sylphx/lens-core";
7
+
1
8
  // src/server/create.ts
2
9
  import {
3
10
  createContext,
11
+ createEmit,
4
12
  createUpdate as createUpdate2,
5
13
  flattenRouter,
6
14
  isBatchResolver,
@@ -10,12 +18,18 @@ import {
10
18
  } from "@sylphx/lens-core";
11
19
 
12
20
  // src/state/graph-state-manager.ts
13
- import { createUpdate, makeEntityKey } from "@sylphx/lens-core";
21
+ import {
22
+ createUpdate,
23
+ applyUpdate,
24
+ makeEntityKey
25
+ } from "@sylphx/lens-core";
14
26
 
15
27
  class GraphStateManager {
16
28
  clients = new Map;
17
29
  canonical = new Map;
30
+ canonicalArrays = new Map;
18
31
  clientStates = new Map;
32
+ clientArrayStates = new Map;
19
33
  entitySubscribers = new Map;
20
34
  config;
21
35
  constructor(config = {}) {
@@ -24,6 +38,7 @@ class GraphStateManager {
24
38
  addClient(client) {
25
39
  this.clients.set(client.id, client);
26
40
  this.clientStates.set(client.id, new Map);
41
+ this.clientArrayStates.set(client.id, new Map);
27
42
  }
28
43
  removeClient(clientId) {
29
44
  for (const [key, subscribers] of this.entitySubscribers) {
@@ -34,6 +49,7 @@ class GraphStateManager {
34
49
  }
35
50
  this.clients.delete(clientId);
36
51
  this.clientStates.delete(clientId);
52
+ this.clientArrayStates.delete(clientId);
37
53
  }
38
54
  subscribe(clientId, entity, id, fields = "*") {
39
55
  const key = this.makeKey(entity, id);
@@ -96,6 +112,148 @@ class GraphStateManager {
96
112
  this.pushToClient(clientId, entity, id, key, currentCanonical);
97
113
  }
98
114
  }
115
+ emitField(entity, id, field, update) {
116
+ const key = this.makeKey(entity, id);
117
+ let currentCanonical = this.canonical.get(key);
118
+ if (!currentCanonical) {
119
+ currentCanonical = {};
120
+ }
121
+ const oldValue = currentCanonical[field];
122
+ const newValue = applyUpdate(oldValue, update);
123
+ currentCanonical = { ...currentCanonical, [field]: newValue };
124
+ this.canonical.set(key, currentCanonical);
125
+ const subscribers = this.entitySubscribers.get(key);
126
+ if (!subscribers)
127
+ return;
128
+ for (const clientId of subscribers) {
129
+ this.pushFieldToClient(clientId, entity, id, key, field, newValue);
130
+ }
131
+ }
132
+ emitBatch(entity, id, updates) {
133
+ const key = this.makeKey(entity, id);
134
+ let currentCanonical = this.canonical.get(key);
135
+ if (!currentCanonical) {
136
+ currentCanonical = {};
137
+ }
138
+ const changedFields = [];
139
+ for (const { field, update } of updates) {
140
+ const oldValue = currentCanonical[field];
141
+ const newValue = applyUpdate(oldValue, update);
142
+ currentCanonical[field] = newValue;
143
+ changedFields.push(field);
144
+ }
145
+ this.canonical.set(key, currentCanonical);
146
+ const subscribers = this.entitySubscribers.get(key);
147
+ if (!subscribers)
148
+ return;
149
+ for (const clientId of subscribers) {
150
+ this.pushFieldsToClient(clientId, entity, id, key, changedFields, currentCanonical);
151
+ }
152
+ }
153
+ processCommand(entity, id, command) {
154
+ switch (command.type) {
155
+ case "full":
156
+ this.emit(entity, id, command.data, {
157
+ replace: command.replace
158
+ });
159
+ break;
160
+ case "field":
161
+ this.emitField(entity, id, command.field, command.update);
162
+ break;
163
+ case "batch":
164
+ this.emitBatch(entity, id, command.updates);
165
+ break;
166
+ case "array":
167
+ this.emitArrayOperation(entity, id, command.operation);
168
+ break;
169
+ }
170
+ }
171
+ emitArray(entity, id, items) {
172
+ const key = this.makeKey(entity, id);
173
+ this.canonicalArrays.set(key, [...items]);
174
+ const subscribers = this.entitySubscribers.get(key);
175
+ if (!subscribers)
176
+ return;
177
+ for (const clientId of subscribers) {
178
+ this.pushArrayToClient(clientId, entity, id, key, items);
179
+ }
180
+ }
181
+ emitArrayOperation(entity, id, operation) {
182
+ const key = this.makeKey(entity, id);
183
+ let currentArray = this.canonicalArrays.get(key);
184
+ if (!currentArray) {
185
+ currentArray = [];
186
+ }
187
+ const newArray = this.applyArrayOperation([...currentArray], operation);
188
+ this.canonicalArrays.set(key, newArray);
189
+ const subscribers = this.entitySubscribers.get(key);
190
+ if (!subscribers)
191
+ return;
192
+ for (const clientId of subscribers) {
193
+ this.pushArrayToClient(clientId, entity, id, key, newArray);
194
+ }
195
+ }
196
+ applyArrayOperation(array, operation) {
197
+ switch (operation.op) {
198
+ case "push":
199
+ return [...array, operation.item];
200
+ case "unshift":
201
+ return [operation.item, ...array];
202
+ case "insert":
203
+ return [
204
+ ...array.slice(0, operation.index),
205
+ operation.item,
206
+ ...array.slice(operation.index)
207
+ ];
208
+ case "remove":
209
+ return [...array.slice(0, operation.index), ...array.slice(operation.index + 1)];
210
+ case "removeById": {
211
+ const idx = array.findIndex((item) => typeof item === "object" && item !== null && ("id" in item) && item.id === operation.id);
212
+ if (idx === -1)
213
+ return array;
214
+ return [...array.slice(0, idx), ...array.slice(idx + 1)];
215
+ }
216
+ case "update":
217
+ return array.map((item, i) => i === operation.index ? operation.item : item);
218
+ case "updateById":
219
+ return array.map((item) => typeof item === "object" && item !== null && ("id" in item) && item.id === operation.id ? operation.item : item);
220
+ case "merge":
221
+ return array.map((item, i) => i === operation.index && typeof item === "object" && item !== null ? { ...item, ...operation.partial } : item);
222
+ case "mergeById":
223
+ return array.map((item) => typeof item === "object" && item !== null && ("id" in item) && item.id === operation.id ? { ...item, ...operation.partial } : item);
224
+ default:
225
+ return array;
226
+ }
227
+ }
228
+ pushArrayToClient(clientId, entity, id, key, newArray) {
229
+ const client = this.clients.get(clientId);
230
+ if (!client)
231
+ return;
232
+ const clientArrayStateMap = this.clientArrayStates.get(clientId);
233
+ if (!clientArrayStateMap)
234
+ return;
235
+ let clientArrayState = clientArrayStateMap.get(key);
236
+ if (!clientArrayState) {
237
+ clientArrayState = { lastState: [] };
238
+ clientArrayStateMap.set(key, clientArrayState);
239
+ }
240
+ const { lastState } = clientArrayState;
241
+ if (JSON.stringify(lastState) === JSON.stringify(newArray)) {
242
+ return;
243
+ }
244
+ client.send({
245
+ type: "update",
246
+ entity,
247
+ id,
248
+ updates: {
249
+ _items: { strategy: "value", data: newArray }
250
+ }
251
+ });
252
+ clientArrayState.lastState = [...newArray];
253
+ }
254
+ getArrayState(entity, id) {
255
+ return this.canonicalArrays.get(this.makeKey(entity, id));
256
+ }
99
257
  getState(entity, id) {
100
258
  return this.canonical.get(this.makeKey(entity, id));
101
259
  }
@@ -143,6 +301,77 @@ class GraphStateManager {
143
301
  }
144
302
  }
145
303
  }
304
+ pushFieldToClient(clientId, entity, id, key, field, newValue) {
305
+ const client = this.clients.get(clientId);
306
+ if (!client)
307
+ return;
308
+ const clientStateMap = this.clientStates.get(clientId);
309
+ if (!clientStateMap)
310
+ return;
311
+ const clientEntityState = clientStateMap.get(key);
312
+ if (!clientEntityState)
313
+ return;
314
+ const { lastState, fields } = clientEntityState;
315
+ if (fields !== "*" && !fields.has(field)) {
316
+ return;
317
+ }
318
+ const oldValue = lastState[field];
319
+ if (oldValue === newValue)
320
+ return;
321
+ if (typeof oldValue === "object" && typeof newValue === "object" && JSON.stringify(oldValue) === JSON.stringify(newValue)) {
322
+ return;
323
+ }
324
+ const update = createUpdate(oldValue, newValue);
325
+ client.send({
326
+ type: "update",
327
+ entity,
328
+ id,
329
+ updates: { [field]: update }
330
+ });
331
+ clientEntityState.lastState[field] = newValue;
332
+ }
333
+ pushFieldsToClient(clientId, entity, id, key, changedFields, newState) {
334
+ const client = this.clients.get(clientId);
335
+ if (!client)
336
+ return;
337
+ const clientStateMap = this.clientStates.get(clientId);
338
+ if (!clientStateMap)
339
+ return;
340
+ const clientEntityState = clientStateMap.get(key);
341
+ if (!clientEntityState)
342
+ return;
343
+ const { lastState, fields } = clientEntityState;
344
+ const updates = {};
345
+ let hasChanges = false;
346
+ for (const field of changedFields) {
347
+ if (fields !== "*" && !fields.has(field)) {
348
+ continue;
349
+ }
350
+ const oldValue = lastState[field];
351
+ const newValue = newState[field];
352
+ if (oldValue === newValue)
353
+ continue;
354
+ if (typeof oldValue === "object" && typeof newValue === "object" && JSON.stringify(oldValue) === JSON.stringify(newValue)) {
355
+ continue;
356
+ }
357
+ const update = createUpdate(oldValue, newValue);
358
+ updates[field] = update;
359
+ hasChanges = true;
360
+ }
361
+ if (!hasChanges)
362
+ return;
363
+ client.send({
364
+ type: "update",
365
+ entity,
366
+ id,
367
+ updates
368
+ });
369
+ for (const field of changedFields) {
370
+ if (newState[field] !== undefined) {
371
+ clientEntityState.lastState[field] = newState[field];
372
+ }
373
+ }
374
+ }
146
375
  sendInitialData(clientId, entity, id, state, fields) {
147
376
  const client = this.clients.get(clientId);
148
377
  if (!client)
@@ -195,7 +424,9 @@ class GraphStateManager {
195
424
  clear() {
196
425
  this.clients.clear();
197
426
  this.canonical.clear();
427
+ this.canonicalArrays.clear();
198
428
  this.clientStates.clear();
429
+ this.clientArrayStates.clear();
199
430
  this.entitySubscribers.clear();
200
431
  }
201
432
  }
@@ -505,21 +736,31 @@ class LensServerImpl {
505
736
  if (!resolver) {
506
737
  throw new Error(`Query ${sub.operation} has no resolver`);
507
738
  }
508
- const contextWithHelpers = {
509
- ...context,
510
- emit: emitData,
511
- onCleanup: (fn) => {
512
- sub.cleanups.push(fn);
513
- return () => {
514
- const idx = sub.cleanups.indexOf(fn);
515
- if (idx >= 0)
516
- sub.cleanups.splice(idx, 1);
517
- };
739
+ const emit = createEmit((command) => {
740
+ const entityName = this.getEntityNameFromOutput(queryDef._output);
741
+ if (entityName) {
742
+ const entities = this.extractEntities(entityName, command.type === "full" ? command.data : {});
743
+ for (const { entity, id } of entities) {
744
+ this.stateManager.processCommand(entity, id, command);
745
+ }
518
746
  }
747
+ if (command.type === "full") {
748
+ emitData(command.data);
749
+ }
750
+ });
751
+ const onCleanup = (fn) => {
752
+ sub.cleanups.push(fn);
753
+ return () => {
754
+ const idx = sub.cleanups.indexOf(fn);
755
+ if (idx >= 0)
756
+ sub.cleanups.splice(idx, 1);
757
+ };
519
758
  };
520
759
  const result = resolver({
521
760
  input: sub.input,
522
- ctx: contextWithHelpers
761
+ ctx: context,
762
+ emit,
763
+ onCleanup
523
764
  });
524
765
  if (isAsyncIterable(result)) {
525
766
  for await (const value of result) {
@@ -668,13 +909,14 @@ class LensServerImpl {
668
909
  if (!resolver) {
669
910
  throw new Error(`Query ${name} has no resolver`);
670
911
  }
671
- const resolverCtx = {
912
+ const emit = createEmit(() => {});
913
+ const onCleanup = () => () => {};
914
+ const result = resolver({
672
915
  input: cleanInput,
673
916
  ctx: context,
674
- emit: () => {},
675
- onCleanup: () => () => {}
676
- };
677
- const result = resolver(resolverCtx);
917
+ emit,
918
+ onCleanup
919
+ });
678
920
  let data;
679
921
  if (isAsyncIterable(result)) {
680
922
  for await (const value of result) {
@@ -711,9 +953,13 @@ class LensServerImpl {
711
953
  if (!resolver) {
712
954
  throw new Error(`Mutation ${name} has no resolver`);
713
955
  }
956
+ const emit = createEmit(() => {});
957
+ const onCleanup = () => () => {};
714
958
  const result = await resolver({
715
959
  input,
716
- ctx: context
960
+ ctx: context,
961
+ emit,
962
+ onCleanup
717
963
  });
718
964
  const entityName = this.getEntityNameFromMutation(name);
719
965
  const entities = this.extractEntities(entityName, result);
@@ -1164,6 +1410,9 @@ function createSSEHandler(config) {
1164
1410
  return new SSEHandler(config);
1165
1411
  }
1166
1412
  export {
1413
+ router,
1414
+ query,
1415
+ mutation,
1167
1416
  createServer,
1168
1417
  createSSEHandler,
1169
1418
  createGraphStateManager,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sylphx/lens-server",
3
- "version": "1.0.4",
3
+ "version": "1.3.0",
4
4
  "description": "Server runtime for Lens API framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -29,7 +29,7 @@
29
29
  "author": "SylphxAI",
30
30
  "license": "MIT",
31
31
  "dependencies": {
32
- "@sylphx/lens-core": "^1.0.4"
32
+ "@sylphx/lens-core": "^1.3.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "typescript": "^5.9.3",
@@ -306,14 +306,14 @@ describe("E2E - Subscriptions", () => {
306
306
  expect(received[0]).toMatchObject({ name: "Alice" });
307
307
  });
308
308
 
309
- it("subscribe receives updates via ctx.emit", async () => {
309
+ it("subscribe receives updates via emit", async () => {
310
310
  let emitFn: ((data: unknown) => void) | null = null;
311
311
 
312
312
  const watchUser = query()
313
313
  .input(z.object({ id: z.string() }))
314
314
  .returns(User)
315
- .resolve(({ input, ctx }) => {
316
- emitFn = ctx.emit;
315
+ .resolve(({ input, emit }) => {
316
+ emitFn = emit;
317
317
  return mockUsers.find((u) => u.id === input.id) ?? null;
318
318
  });
319
319
 
@@ -355,8 +355,8 @@ describe("E2E - Subscriptions", () => {
355
355
  const watchUser = query()
356
356
  .input(z.object({ id: z.string() }))
357
357
  .returns(User)
358
- .resolve(({ input, ctx }) => {
359
- emitFn = ctx.emit;
358
+ .resolve(({ input, emit }) => {
359
+ emitFn = emit;
360
360
  return mockUsers.find((u) => u.id === input.id) ?? null;
361
361
  });
362
362
 
@@ -447,7 +447,7 @@ describe("E2E - Server API", () => {
447
447
  });
448
448
 
449
449
  // =============================================================================
450
- // Test: Cleanup (ctx.onCleanup)
450
+ // Test: Cleanup (onCleanup)
451
451
  // =============================================================================
452
452
 
453
453
  describe("E2E - Cleanup", () => {
@@ -457,8 +457,8 @@ describe("E2E - Cleanup", () => {
457
457
  const watchUser = query()
458
458
  .input(z.object({ id: z.string() }))
459
459
  .returns(User)
460
- .resolve(({ input, ctx }) => {
461
- ctx.onCleanup(() => {
460
+ .resolve(({ input, onCleanup }) => {
461
+ onCleanup(() => {
462
462
  cleanedUp = true;
463
463
  });
464
464
  return mockUsers.find((u) => u.id === input.id) ?? null;
@@ -497,8 +497,8 @@ describe("E2E - GraphStateManager", () => {
497
497
  const getUser = query()
498
498
  .input(z.object({ id: z.string() }))
499
499
  .returns(User)
500
- .resolve(({ input, ctx }) => {
501
- emitFn = ctx.emit;
500
+ .resolve(({ input, emit }) => {
501
+ emitFn = emit;
502
502
  return mockUsers.find((u) => u.id === input.id) ?? null;
503
503
  });
504
504
 
package/src/index.ts CHANGED
@@ -5,6 +5,27 @@
5
5
  * Operations-based server with GraphStateManager for reactive updates.
6
6
  */
7
7
 
8
+ // =============================================================================
9
+ // Re-exports from Core (commonly used with server)
10
+ // =============================================================================
11
+
12
+ export {
13
+ // Operations
14
+ query,
15
+ mutation,
16
+ router,
17
+ // Types
18
+ type QueryBuilder,
19
+ type MutationBuilder,
20
+ type QueryDef,
21
+ type MutationDef,
22
+ type RouterDef,
23
+ type RouterRoutes,
24
+ type ResolverFn,
25
+ type ResolverContext,
26
+ type InferRouterContext,
27
+ } from "@sylphx/lens-core";
28
+
8
29
  // =============================================================================
9
30
  // Server
10
31
  // =============================================================================