@sylphx/lens-server 1.0.2 → 1.0.4
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 +438 -9
- package/dist/index.js +13 -278
- package/package.json +3 -4
- package/dist/index.d.ts.map +0 -1
- package/dist/server/create.d.ts +0 -226
- package/dist/server/create.d.ts.map +0 -1
- package/dist/sse/handler.d.ts +0 -78
- package/dist/sse/handler.d.ts.map +0 -1
- package/dist/state/graph-state-manager.d.ts +0 -146
- package/dist/state/graph-state-manager.d.ts.map +0 -1
- package/dist/state/index.d.ts +0 -7
- package/dist/state/index.d.ts.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,439 @@
|
|
|
1
|
+
import { ContextValue, EntityDef, EntityDefinition, EntityResolvers, EntityResolversDefinition, MutationDef, QueryDef, RelationDef, RelationTypeWithForeignKey, RouterDef } from "@sylphx/lens-core";
|
|
2
|
+
import { EntityKey, Update } from "@sylphx/lens-core";
|
|
3
|
+
/** Client connection interface */
|
|
4
|
+
interface StateClient {
|
|
5
|
+
id: string;
|
|
6
|
+
send: (message: StateUpdateMessage) => void;
|
|
7
|
+
}
|
|
8
|
+
/** Update message sent to clients */
|
|
9
|
+
interface StateUpdateMessage {
|
|
10
|
+
type: "update";
|
|
11
|
+
entity: string;
|
|
12
|
+
id: string;
|
|
13
|
+
/** Field-level updates with strategy */
|
|
14
|
+
updates: Record<string, Update>;
|
|
15
|
+
}
|
|
16
|
+
/** Full entity update message */
|
|
17
|
+
interface StateFullMessage {
|
|
18
|
+
type: "data";
|
|
19
|
+
entity: string;
|
|
20
|
+
id: string;
|
|
21
|
+
data: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
/** Subscription info */
|
|
24
|
+
interface Subscription {
|
|
25
|
+
clientId: string;
|
|
26
|
+
fields: Set<string> | "*";
|
|
27
|
+
}
|
|
28
|
+
/** Configuration */
|
|
29
|
+
interface GraphStateManagerConfig {
|
|
30
|
+
/** Called when an entity has no more subscribers */
|
|
31
|
+
onEntityUnsubscribed?: (entity: string, id: string) => void;
|
|
32
|
+
}
|
|
1
33
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
34
|
+
* Manages server-side canonical state and syncs to clients.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* const manager = new GraphStateManager();
|
|
39
|
+
*
|
|
40
|
+
* // Add client
|
|
41
|
+
* manager.addClient({
|
|
42
|
+
* id: "client-1",
|
|
43
|
+
* send: (msg) => ws.send(JSON.stringify(msg)),
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* // Subscribe client to entity
|
|
47
|
+
* manager.subscribe("client-1", "Post", "123", ["title", "content"]);
|
|
48
|
+
*
|
|
49
|
+
* // Emit updates (from resolvers)
|
|
50
|
+
* manager.emit("Post", "123", { content: "Updated content" });
|
|
51
|
+
* // → Automatically computes diff and sends to subscribed clients
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
declare class GraphStateManager {
|
|
55
|
+
/** Connected clients */
|
|
56
|
+
private clients;
|
|
57
|
+
/** Canonical state per entity (server truth) */
|
|
58
|
+
private canonical;
|
|
59
|
+
/** Per-client state tracking */
|
|
60
|
+
private clientStates;
|
|
61
|
+
/** Entity → subscribed client IDs */
|
|
62
|
+
private entitySubscribers;
|
|
63
|
+
/** Configuration */
|
|
64
|
+
private config;
|
|
65
|
+
constructor(config?: GraphStateManagerConfig);
|
|
66
|
+
/**
|
|
67
|
+
* Add a client connection
|
|
68
|
+
*/
|
|
69
|
+
addClient(client: StateClient): void;
|
|
70
|
+
/**
|
|
71
|
+
* Remove a client and cleanup all subscriptions
|
|
72
|
+
*/
|
|
73
|
+
removeClient(clientId: string): void;
|
|
74
|
+
/**
|
|
75
|
+
* Subscribe a client to an entity
|
|
76
|
+
*/
|
|
77
|
+
subscribe(clientId: string, entity: string, id: string, fields?: string[] | "*"): void;
|
|
78
|
+
/**
|
|
79
|
+
* Unsubscribe a client from an entity
|
|
80
|
+
*/
|
|
81
|
+
unsubscribe(clientId: string, entity: string, id: string): void;
|
|
82
|
+
/**
|
|
83
|
+
* Update subscription fields for a client
|
|
84
|
+
*/
|
|
85
|
+
updateSubscription(clientId: string, entity: string, id: string, fields: string[] | "*"): void;
|
|
86
|
+
/**
|
|
87
|
+
* Emit data for an entity.
|
|
88
|
+
* This is the core method called by resolvers.
|
|
89
|
+
*
|
|
90
|
+
* @param entity - Entity name
|
|
91
|
+
* @param id - Entity ID
|
|
92
|
+
* @param data - Full or partial entity data
|
|
93
|
+
* @param options - Emit options
|
|
94
|
+
*/
|
|
95
|
+
emit(entity: string, id: string, data: Record<string, unknown>, options?: {
|
|
96
|
+
replace?: boolean;
|
|
97
|
+
}): void;
|
|
98
|
+
/**
|
|
99
|
+
* Get current canonical state for an entity
|
|
100
|
+
*/
|
|
101
|
+
getState(entity: string, id: string): Record<string, unknown> | undefined;
|
|
102
|
+
/**
|
|
103
|
+
* Check if entity has any subscribers
|
|
104
|
+
*/
|
|
105
|
+
hasSubscribers(entity: string, id: string): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Push update to a specific client
|
|
108
|
+
*/
|
|
109
|
+
private pushToClient;
|
|
110
|
+
/**
|
|
111
|
+
* Send initial data to a newly subscribed client
|
|
112
|
+
*/
|
|
113
|
+
private sendInitialData;
|
|
114
|
+
/**
|
|
115
|
+
* Cleanup entity when no subscribers remain
|
|
116
|
+
*/
|
|
117
|
+
private cleanupEntity;
|
|
118
|
+
private makeKey;
|
|
119
|
+
/**
|
|
120
|
+
* Get statistics
|
|
121
|
+
*/
|
|
122
|
+
getStats(): {
|
|
123
|
+
clients: number;
|
|
124
|
+
entities: number;
|
|
125
|
+
totalSubscriptions: number;
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* Clear all state (for testing)
|
|
129
|
+
*/
|
|
130
|
+
clear(): void;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Create a GraphStateManager instance
|
|
134
|
+
*/
|
|
135
|
+
declare function createGraphStateManager(config?: GraphStateManagerConfig): GraphStateManager;
|
|
136
|
+
/** Selection object type for nested field selection */
|
|
137
|
+
interface SelectionObject {
|
|
138
|
+
[key: string]: boolean | SelectionObject | {
|
|
139
|
+
select: SelectionObject;
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/** Entity map type */
|
|
143
|
+
type EntitiesMap = Record<string, EntityDef<string, any>>;
|
|
144
|
+
/** Queries map type */
|
|
145
|
+
type QueriesMap = Record<string, QueryDef<unknown, unknown>>;
|
|
146
|
+
/** Mutations map type */
|
|
147
|
+
type MutationsMap = Record<string, MutationDef<unknown, unknown>>;
|
|
148
|
+
/** Relations array type */
|
|
149
|
+
type RelationsArray = RelationDef<EntityDef<string, EntityDefinition>, Record<string, RelationTypeWithForeignKey>>[];
|
|
150
|
+
/** Operation metadata for handshake */
|
|
151
|
+
interface OperationMeta {
|
|
152
|
+
type: "query" | "mutation";
|
|
153
|
+
optimistic?: unknown;
|
|
154
|
+
}
|
|
155
|
+
/** Nested operations structure for handshake */
|
|
156
|
+
type OperationsMap = {
|
|
157
|
+
[key: string]: OperationMeta | OperationsMap;
|
|
158
|
+
};
|
|
159
|
+
/** Server configuration */
|
|
160
|
+
interface LensServerConfig<TContext extends ContextValue = ContextValue> {
|
|
161
|
+
/** Entity definitions */
|
|
162
|
+
entities?: EntitiesMap;
|
|
163
|
+
/** Relation definitions */
|
|
164
|
+
relations?: RelationsArray;
|
|
165
|
+
/** Router definition (namespaced operations) */
|
|
166
|
+
router?: RouterDef;
|
|
167
|
+
/** Query definitions (flat, legacy) */
|
|
168
|
+
queries?: QueriesMap;
|
|
169
|
+
/** Mutation definitions (flat, legacy) */
|
|
170
|
+
mutations?: MutationsMap;
|
|
171
|
+
/** Entity resolvers */
|
|
172
|
+
resolvers?: EntityResolvers<EntityResolversDefinition>;
|
|
173
|
+
/** Context factory */
|
|
174
|
+
context?: (req?: unknown) => TContext | Promise<TContext>;
|
|
175
|
+
/** Server version */
|
|
176
|
+
version?: string;
|
|
177
|
+
}
|
|
178
|
+
/** Server metadata for transport handshake */
|
|
179
|
+
interface ServerMetadata {
|
|
180
|
+
/** Server version */
|
|
181
|
+
version: string;
|
|
182
|
+
/** Operations metadata map */
|
|
183
|
+
operations: OperationsMap;
|
|
184
|
+
}
|
|
185
|
+
/** Operation for in-process transport */
|
|
186
|
+
interface LensOperation {
|
|
187
|
+
/** Operation path (e.g., 'user.get', 'session.create') */
|
|
188
|
+
path: string;
|
|
189
|
+
/** Operation input */
|
|
190
|
+
input?: unknown;
|
|
191
|
+
}
|
|
192
|
+
/** Result from operation execution */
|
|
193
|
+
interface LensResult<T = unknown> {
|
|
194
|
+
/** Success data */
|
|
195
|
+
data?: T;
|
|
196
|
+
/** Error if operation failed */
|
|
197
|
+
error?: Error;
|
|
198
|
+
}
|
|
199
|
+
/** Lens server interface */
|
|
200
|
+
interface LensServer {
|
|
201
|
+
/** Get server metadata for transport handshake */
|
|
202
|
+
getMetadata(): ServerMetadata;
|
|
203
|
+
/** Execute operation - auto-detects query vs mutation from registered operations */
|
|
204
|
+
execute(op: LensOperation): Promise<LensResult>;
|
|
205
|
+
/** Execute a query (one-time) */
|
|
206
|
+
executeQuery<
|
|
207
|
+
TInput,
|
|
208
|
+
TOutput
|
|
209
|
+
>(name: string, input?: TInput): Promise<TOutput>;
|
|
210
|
+
/** Execute a mutation */
|
|
211
|
+
executeMutation<
|
|
212
|
+
TInput,
|
|
213
|
+
TOutput
|
|
214
|
+
>(name: string, input: TInput): Promise<TOutput>;
|
|
215
|
+
/** Handle WebSocket connection */
|
|
216
|
+
handleWebSocket(ws: WebSocketLike): void;
|
|
217
|
+
/** Handle HTTP request */
|
|
218
|
+
handleRequest(req: Request): Promise<Response>;
|
|
219
|
+
/** Get GraphStateManager for external access */
|
|
220
|
+
getStateManager(): GraphStateManager;
|
|
221
|
+
/** Start server */
|
|
222
|
+
listen(port: number): Promise<void>;
|
|
223
|
+
/** Close server */
|
|
224
|
+
close(): Promise<void>;
|
|
225
|
+
}
|
|
226
|
+
/** WebSocket interface */
|
|
227
|
+
interface WebSocketLike {
|
|
228
|
+
send(data: string): void;
|
|
229
|
+
close(): void;
|
|
230
|
+
onmessage?: ((event: {
|
|
231
|
+
data: string;
|
|
232
|
+
}) => void) | null;
|
|
233
|
+
onclose?: (() => void) | null;
|
|
234
|
+
onerror?: ((error: unknown) => void) | null;
|
|
235
|
+
}
|
|
236
|
+
declare class LensServerImpl<
|
|
237
|
+
Q extends QueriesMap = QueriesMap,
|
|
238
|
+
M extends MutationsMap = MutationsMap,
|
|
239
|
+
TContext extends ContextValue = ContextValue
|
|
240
|
+
> implements LensServer {
|
|
241
|
+
private queries;
|
|
242
|
+
private mutations;
|
|
243
|
+
private entities;
|
|
244
|
+
private resolvers?;
|
|
245
|
+
private contextFactory;
|
|
246
|
+
private version;
|
|
247
|
+
private ctx;
|
|
248
|
+
/** GraphStateManager for per-client state tracking */
|
|
249
|
+
private stateManager;
|
|
250
|
+
/** DataLoaders for N+1 batching (per-request) */
|
|
251
|
+
private loaders;
|
|
252
|
+
/** Client connections */
|
|
253
|
+
private connections;
|
|
254
|
+
private connectionCounter;
|
|
255
|
+
/** Server instance */
|
|
256
|
+
private server;
|
|
257
|
+
constructor(config: LensServerConfig<TContext> & {
|
|
258
|
+
queries?: Q;
|
|
259
|
+
mutations?: M;
|
|
260
|
+
});
|
|
261
|
+
getStateManager(): GraphStateManager;
|
|
262
|
+
/**
|
|
263
|
+
* Get server metadata for transport handshake.
|
|
264
|
+
* Used by inProcess transport for direct access.
|
|
265
|
+
*/
|
|
266
|
+
getMetadata(): ServerMetadata;
|
|
267
|
+
/**
|
|
268
|
+
* Execute operation - auto-detects query vs mutation from registered operations.
|
|
269
|
+
* Used by inProcess transport for direct server calls.
|
|
270
|
+
*/
|
|
271
|
+
execute(op: LensOperation): Promise<LensResult>;
|
|
272
|
+
/**
|
|
273
|
+
* Build nested operations map for handshake response
|
|
274
|
+
* Converts flat "user.get", "user.create" into nested { user: { get: {...}, create: {...} } }
|
|
275
|
+
*/
|
|
276
|
+
private buildOperationsMap;
|
|
277
|
+
handleWebSocket(ws: WebSocketLike): void;
|
|
278
|
+
private handleMessage;
|
|
279
|
+
private handleHandshake;
|
|
280
|
+
private handleSubscribe;
|
|
281
|
+
private executeSubscription;
|
|
282
|
+
private handleUpdateFields;
|
|
283
|
+
private handleUnsubscribe;
|
|
284
|
+
private handleQuery;
|
|
285
|
+
private handleMutation;
|
|
286
|
+
private handleDisconnect;
|
|
287
|
+
executeQuery<
|
|
288
|
+
TInput,
|
|
289
|
+
TOutput
|
|
290
|
+
>(name: string, input?: TInput): Promise<TOutput>;
|
|
291
|
+
executeMutation<
|
|
292
|
+
TInput,
|
|
293
|
+
TOutput
|
|
294
|
+
>(name: string, input: TInput): Promise<TOutput>;
|
|
295
|
+
handleRequest(req: Request): Promise<Response>;
|
|
296
|
+
listen(port: number): Promise<void>;
|
|
297
|
+
close(): Promise<void>;
|
|
298
|
+
private findConnectionByWs;
|
|
299
|
+
private getEntityNameFromOutput;
|
|
300
|
+
private getEntityNameFromMutation;
|
|
301
|
+
private extractEntities;
|
|
302
|
+
private applySelection;
|
|
303
|
+
private applySelectionToObject;
|
|
304
|
+
/**
|
|
305
|
+
* Execute entity resolvers for nested data.
|
|
306
|
+
* Processes the selection object and resolves relation fields.
|
|
307
|
+
*/
|
|
308
|
+
private executeEntityResolvers;
|
|
309
|
+
/**
|
|
310
|
+
* Get target entity name for a relation field.
|
|
311
|
+
*/
|
|
312
|
+
private getRelationTargetEntity;
|
|
313
|
+
/**
|
|
314
|
+
* Serialize entity data for transport.
|
|
315
|
+
* Auto-calls serialize() on field types (Date → ISO string, etc.)
|
|
316
|
+
*/
|
|
317
|
+
private serializeEntity;
|
|
318
|
+
/**
|
|
319
|
+
* Process query result: execute entity resolvers, apply selection, serialize
|
|
320
|
+
*/
|
|
321
|
+
private processQueryResult;
|
|
322
|
+
private computeUpdates;
|
|
323
|
+
private deepEqual;
|
|
324
|
+
private clearLoaders;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Infer input type from a query/mutation definition
|
|
328
|
+
*/
|
|
329
|
+
type InferInput<T> = T extends QueryDef<infer I, unknown> ? I extends void ? void : I : T extends MutationDef<infer I, unknown> ? I : never;
|
|
330
|
+
/**
|
|
331
|
+
* Infer output type from a query/mutation definition
|
|
332
|
+
*/
|
|
333
|
+
type InferOutput<T> = T extends QueryDef<unknown, infer O> ? O : T extends MutationDef<unknown, infer O> ? O : never;
|
|
334
|
+
/**
|
|
335
|
+
* API type for client inference
|
|
336
|
+
* Export this type for client-side type safety
|
|
337
|
+
*
|
|
338
|
+
* @example
|
|
339
|
+
* ```typescript
|
|
340
|
+
* // Server
|
|
341
|
+
* const server = createLensServer({ queries, mutations });
|
|
342
|
+
* type Api = InferApi<typeof server>;
|
|
343
|
+
*
|
|
344
|
+
* // Client (only imports TYPE)
|
|
345
|
+
* import type { Api } from './server';
|
|
346
|
+
* const client = createClient<Api>({ links: [...] });
|
|
347
|
+
* ```
|
|
348
|
+
*/
|
|
349
|
+
type InferApi<T extends LensServer> = T extends LensServerImpl<infer Q, infer M> ? {
|
|
350
|
+
queries: Q;
|
|
351
|
+
mutations: M;
|
|
352
|
+
} : never;
|
|
353
|
+
/**
|
|
354
|
+
* Create Lens server with Operations API + Optimization Layer
|
|
355
|
+
*/
|
|
356
|
+
declare function createServer<
|
|
357
|
+
TContext extends ContextValue = ContextValue,
|
|
358
|
+
Q extends QueriesMap = QueriesMap,
|
|
359
|
+
M extends MutationsMap = MutationsMap
|
|
360
|
+
>(config: LensServerConfig<TContext> & {
|
|
361
|
+
queries?: Q;
|
|
362
|
+
mutations?: M;
|
|
363
|
+
}): LensServer & {
|
|
364
|
+
_types: {
|
|
365
|
+
queries: Q;
|
|
366
|
+
mutations: M;
|
|
367
|
+
};
|
|
368
|
+
};
|
|
369
|
+
/** SSE handler configuration */
|
|
370
|
+
interface SSEHandlerConfig {
|
|
371
|
+
/** GraphStateManager instance (required) */
|
|
372
|
+
stateManager: GraphStateManager;
|
|
373
|
+
/** Heartbeat interval in ms (default: 30000) */
|
|
374
|
+
heartbeatInterval?: number;
|
|
375
|
+
}
|
|
376
|
+
/** SSE client info */
|
|
377
|
+
interface SSEClientInfo {
|
|
378
|
+
id: string;
|
|
379
|
+
connectedAt: number;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* SSE transport adapter for GraphStateManager.
|
|
383
|
+
*
|
|
384
|
+
* This is a thin adapter that:
|
|
385
|
+
* - Creates SSE connections
|
|
386
|
+
* - Registers clients with GraphStateManager
|
|
387
|
+
* - Forwards updates to SSE streams
|
|
388
|
+
*
|
|
389
|
+
* All state/subscription logic is handled by GraphStateManager.
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* ```typescript
|
|
393
|
+
* const stateManager = new GraphStateManager();
|
|
394
|
+
* const sse = new SSEHandler({ stateManager });
|
|
395
|
+
*
|
|
396
|
+
* // Handle SSE connection
|
|
397
|
+
* app.get('/events', (req) => sse.handleConnection(req));
|
|
398
|
+
*
|
|
399
|
+
* // Subscribe via separate endpoint or message
|
|
400
|
+
* stateManager.subscribe(clientId, "Post", "123", "*");
|
|
401
|
+
* ```
|
|
402
|
+
*/
|
|
403
|
+
declare class SSEHandler {
|
|
404
|
+
private stateManager;
|
|
405
|
+
private heartbeatInterval;
|
|
406
|
+
private clients;
|
|
407
|
+
private clientCounter;
|
|
408
|
+
constructor(config: SSEHandlerConfig);
|
|
409
|
+
/**
|
|
410
|
+
* Handle new SSE connection
|
|
411
|
+
* Returns a Response with SSE stream
|
|
412
|
+
*/
|
|
413
|
+
handleConnection(req?: Request): Response;
|
|
414
|
+
/**
|
|
415
|
+
* Remove client and cleanup
|
|
416
|
+
*/
|
|
417
|
+
private removeClient;
|
|
418
|
+
/**
|
|
419
|
+
* Close specific client connection
|
|
420
|
+
*/
|
|
421
|
+
closeClient(clientId: string): void;
|
|
422
|
+
/**
|
|
423
|
+
* Get connected client count
|
|
424
|
+
*/
|
|
425
|
+
getClientCount(): number;
|
|
426
|
+
/**
|
|
427
|
+
* Get connected client IDs
|
|
428
|
+
*/
|
|
429
|
+
getClientIds(): string[];
|
|
430
|
+
/**
|
|
431
|
+
* Close all connections
|
|
432
|
+
*/
|
|
433
|
+
closeAll(): void;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Create SSE handler (transport adapter)
|
|
437
|
+
*/
|
|
438
|
+
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 };
|
package/dist/index.js
CHANGED
|
@@ -1,282 +1,17 @@
|
|
|
1
|
-
//
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
},
|
|
12
|
-
estimateSize(update) {
|
|
13
|
-
return JSON.stringify(update.data).length;
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
var deltaStrategy = {
|
|
17
|
-
name: "delta",
|
|
18
|
-
encode(prev, next) {
|
|
19
|
-
const operations = computeStringDiff(prev, next);
|
|
20
|
-
const diffSize = JSON.stringify(operations).length;
|
|
21
|
-
const valueSize = next.length + 20;
|
|
22
|
-
if (diffSize >= valueSize) {
|
|
23
|
-
return { strategy: "value", data: next };
|
|
24
|
-
}
|
|
25
|
-
return { strategy: "delta", data: operations };
|
|
26
|
-
},
|
|
27
|
-
decode(current, update) {
|
|
28
|
-
if (update.strategy === "value") {
|
|
29
|
-
return update.data;
|
|
30
|
-
}
|
|
31
|
-
const operations = update.data;
|
|
32
|
-
return applyStringDiff(current, operations);
|
|
33
|
-
},
|
|
34
|
-
estimateSize(update) {
|
|
35
|
-
return JSON.stringify(update.data).length;
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
function computeStringDiff(prev, next) {
|
|
39
|
-
const operations = [];
|
|
40
|
-
let prefixLen = 0;
|
|
41
|
-
const minLen = Math.min(prev.length, next.length);
|
|
42
|
-
while (prefixLen < minLen && prev[prefixLen] === next[prefixLen]) {
|
|
43
|
-
prefixLen++;
|
|
44
|
-
}
|
|
45
|
-
let suffixLen = 0;
|
|
46
|
-
const remainingPrev = prev.length - prefixLen;
|
|
47
|
-
const remainingNext = next.length - prefixLen;
|
|
48
|
-
const maxSuffix = Math.min(remainingPrev, remainingNext);
|
|
49
|
-
while (suffixLen < maxSuffix && prev[prev.length - 1 - suffixLen] === next[next.length - 1 - suffixLen]) {
|
|
50
|
-
suffixLen++;
|
|
51
|
-
}
|
|
52
|
-
const deleteCount = prev.length - prefixLen - suffixLen;
|
|
53
|
-
const insertText = next.slice(prefixLen, next.length - suffixLen || undefined);
|
|
54
|
-
if (deleteCount > 0 || insertText.length > 0) {
|
|
55
|
-
operations.push({
|
|
56
|
-
position: prefixLen,
|
|
57
|
-
...deleteCount > 0 ? { delete: deleteCount } : {},
|
|
58
|
-
...insertText.length > 0 ? { insert: insertText } : {}
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
return operations;
|
|
62
|
-
}
|
|
63
|
-
function applyStringDiff(current, operations) {
|
|
64
|
-
let result = current;
|
|
65
|
-
const sortedOps = [...operations].sort((a, b) => b.position - a.position);
|
|
66
|
-
for (const op of sortedOps) {
|
|
67
|
-
const before = result.slice(0, op.position);
|
|
68
|
-
const after = result.slice(op.position + (op.delete ?? 0));
|
|
69
|
-
result = before + (op.insert ?? "") + after;
|
|
70
|
-
}
|
|
71
|
-
return result;
|
|
72
|
-
}
|
|
73
|
-
var patchStrategy = {
|
|
74
|
-
name: "patch",
|
|
75
|
-
encode(prev, next) {
|
|
76
|
-
const operations = computeJsonPatch(prev, next);
|
|
77
|
-
const patchSize = JSON.stringify(operations).length;
|
|
78
|
-
const valueSize = JSON.stringify(next).length + 20;
|
|
79
|
-
if (patchSize >= valueSize) {
|
|
80
|
-
return { strategy: "value", data: next };
|
|
81
|
-
}
|
|
82
|
-
return { strategy: "patch", data: operations };
|
|
83
|
-
},
|
|
84
|
-
decode(current, update) {
|
|
85
|
-
if (update.strategy === "value") {
|
|
86
|
-
return update.data;
|
|
87
|
-
}
|
|
88
|
-
const operations = update.data;
|
|
89
|
-
return applyJsonPatch(current, operations);
|
|
90
|
-
},
|
|
91
|
-
estimateSize(update) {
|
|
92
|
-
return JSON.stringify(update.data).length;
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
function computeJsonPatch(prev, next, basePath = "") {
|
|
96
|
-
const operations = [];
|
|
97
|
-
const prevObj = prev;
|
|
98
|
-
const nextObj = next;
|
|
99
|
-
for (const key of Object.keys(prevObj)) {
|
|
100
|
-
if (!(key in nextObj)) {
|
|
101
|
-
operations.push({ op: "remove", path: `${basePath}/${escapeJsonPointer(key)}` });
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
for (const [key, nextValue] of Object.entries(nextObj)) {
|
|
105
|
-
const path = `${basePath}/${escapeJsonPointer(key)}`;
|
|
106
|
-
const prevValue = prevObj[key];
|
|
107
|
-
if (!(key in prevObj)) {
|
|
108
|
-
operations.push({ op: "add", path, value: nextValue });
|
|
109
|
-
} else if (!deepEqual(prevValue, nextValue)) {
|
|
110
|
-
if (isPlainObject(prevValue) && isPlainObject(nextValue) && !Array.isArray(prevValue) && !Array.isArray(nextValue)) {
|
|
111
|
-
operations.push(...computeJsonPatch(prevValue, nextValue, path));
|
|
112
|
-
} else {
|
|
113
|
-
operations.push({ op: "replace", path, value: nextValue });
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return operations;
|
|
118
|
-
}
|
|
119
|
-
function applyJsonPatch(current, operations) {
|
|
120
|
-
const result = structuredClone(current);
|
|
121
|
-
for (const op of operations) {
|
|
122
|
-
const pathParts = parseJsonPointer(op.path);
|
|
123
|
-
switch (op.op) {
|
|
124
|
-
case "add":
|
|
125
|
-
case "replace":
|
|
126
|
-
setValueAtPath(result, pathParts, op.value);
|
|
127
|
-
break;
|
|
128
|
-
case "remove":
|
|
129
|
-
removeValueAtPath(result, pathParts);
|
|
130
|
-
break;
|
|
131
|
-
case "move":
|
|
132
|
-
if (op.from) {
|
|
133
|
-
const fromParts = parseJsonPointer(op.from);
|
|
134
|
-
const value = getValueAtPath(result, fromParts);
|
|
135
|
-
removeValueAtPath(result, fromParts);
|
|
136
|
-
setValueAtPath(result, pathParts, value);
|
|
137
|
-
}
|
|
138
|
-
break;
|
|
139
|
-
case "copy":
|
|
140
|
-
if (op.from) {
|
|
141
|
-
const fromParts = parseJsonPointer(op.from);
|
|
142
|
-
const value = structuredClone(getValueAtPath(result, fromParts));
|
|
143
|
-
setValueAtPath(result, pathParts, value);
|
|
144
|
-
}
|
|
145
|
-
break;
|
|
146
|
-
case "test":
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
return result;
|
|
151
|
-
}
|
|
152
|
-
var THRESHOLDS = {
|
|
153
|
-
STRING_DELTA_MIN: 100,
|
|
154
|
-
OBJECT_PATCH_MIN: 50
|
|
155
|
-
};
|
|
156
|
-
function selectStrategy(prev, next) {
|
|
157
|
-
if (typeof prev === "string" && typeof next === "string") {
|
|
158
|
-
if (next.length >= THRESHOLDS.STRING_DELTA_MIN) {
|
|
159
|
-
return deltaStrategy;
|
|
160
|
-
}
|
|
161
|
-
return valueStrategy;
|
|
162
|
-
}
|
|
163
|
-
if (typeof next !== "object" || next === null) {
|
|
164
|
-
return valueStrategy;
|
|
165
|
-
}
|
|
166
|
-
if (isPlainObject(prev) && isPlainObject(next)) {
|
|
167
|
-
const prevSize = JSON.stringify(prev).length;
|
|
168
|
-
if (prevSize >= THRESHOLDS.OBJECT_PATCH_MIN) {
|
|
169
|
-
return patchStrategy;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return valueStrategy;
|
|
173
|
-
}
|
|
174
|
-
function createUpdate(prev, next) {
|
|
175
|
-
const strategy = selectStrategy(prev, next);
|
|
176
|
-
return strategy.encode(prev, next);
|
|
177
|
-
}
|
|
178
|
-
function isPlainObject(value) {
|
|
179
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
180
|
-
}
|
|
181
|
-
function deepEqual(a, b) {
|
|
182
|
-
if (a === b)
|
|
183
|
-
return true;
|
|
184
|
-
if (typeof a !== typeof b)
|
|
185
|
-
return false;
|
|
186
|
-
if (typeof a !== "object" || a === null || b === null)
|
|
187
|
-
return false;
|
|
188
|
-
const aKeys = Object.keys(a);
|
|
189
|
-
const bKeys = Object.keys(b);
|
|
190
|
-
if (aKeys.length !== bKeys.length)
|
|
191
|
-
return false;
|
|
192
|
-
for (const key of aKeys) {
|
|
193
|
-
if (!deepEqual(a[key], b[key])) {
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
return true;
|
|
198
|
-
}
|
|
199
|
-
function escapeJsonPointer(str) {
|
|
200
|
-
return str.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
201
|
-
}
|
|
202
|
-
function parseJsonPointer(path) {
|
|
203
|
-
if (!path || path === "/")
|
|
204
|
-
return [];
|
|
205
|
-
return path.slice(1).split("/").map((p) => p.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
206
|
-
}
|
|
207
|
-
function getValueAtPath(obj, path) {
|
|
208
|
-
let current = obj;
|
|
209
|
-
for (const key of path) {
|
|
210
|
-
if (current === null || typeof current !== "object")
|
|
211
|
-
return;
|
|
212
|
-
current = current[key];
|
|
213
|
-
}
|
|
214
|
-
return current;
|
|
215
|
-
}
|
|
216
|
-
function setValueAtPath(obj, path, value) {
|
|
217
|
-
if (path.length === 0)
|
|
218
|
-
return;
|
|
219
|
-
let current = obj;
|
|
220
|
-
for (let i = 0;i < path.length - 1; i++) {
|
|
221
|
-
const key = path[i];
|
|
222
|
-
if (!(key in current) || typeof current[key] !== "object") {
|
|
223
|
-
current[key] = {};
|
|
224
|
-
}
|
|
225
|
-
current = current[key];
|
|
226
|
-
}
|
|
227
|
-
current[path[path.length - 1]] = value;
|
|
228
|
-
}
|
|
229
|
-
function removeValueAtPath(obj, path) {
|
|
230
|
-
if (path.length === 0)
|
|
231
|
-
return;
|
|
232
|
-
let current = obj;
|
|
233
|
-
for (let i = 0;i < path.length - 1; i++) {
|
|
234
|
-
const key = path[i];
|
|
235
|
-
if (!(key in current) || typeof current[key] !== "object")
|
|
236
|
-
return;
|
|
237
|
-
current = current[key];
|
|
238
|
-
}
|
|
239
|
-
delete current[path[path.length - 1]];
|
|
240
|
-
}
|
|
241
|
-
function isQueryDef(value) {
|
|
242
|
-
return typeof value === "object" && value !== null && value._type === "query";
|
|
243
|
-
}
|
|
244
|
-
function isMutationDef(value) {
|
|
245
|
-
return typeof value === "object" && value !== null && value._type === "mutation";
|
|
246
|
-
}
|
|
247
|
-
function isRouterDef(value) {
|
|
248
|
-
return typeof value === "object" && value !== null && value._type === "router";
|
|
249
|
-
}
|
|
250
|
-
function flattenRouter(routerDef, prefix = "") {
|
|
251
|
-
const result = new Map;
|
|
252
|
-
for (const [key, value] of Object.entries(routerDef._routes)) {
|
|
253
|
-
const path = prefix ? `${prefix}.${key}` : key;
|
|
254
|
-
if (isRouterDef(value)) {
|
|
255
|
-
const nested = flattenRouter(value, path);
|
|
256
|
-
for (const [nestedPath, procedure] of nested) {
|
|
257
|
-
result.set(nestedPath, procedure);
|
|
258
|
-
}
|
|
259
|
-
} else {
|
|
260
|
-
result.set(path, value);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
return result;
|
|
264
|
-
}
|
|
265
|
-
function isBatchResolver(resolver) {
|
|
266
|
-
return typeof resolver === "object" && resolver !== null && "batch" in resolver;
|
|
267
|
-
}
|
|
268
|
-
var globalContextStore = new AsyncLocalStorage;
|
|
269
|
-
function createContext() {
|
|
270
|
-
return globalContextStore;
|
|
271
|
-
}
|
|
272
|
-
function runWithContext(_context, value, fn) {
|
|
273
|
-
return globalContextStore.run(value, fn);
|
|
274
|
-
}
|
|
275
|
-
function makeEntityKey(entity2, id) {
|
|
276
|
-
return `${entity2}:${id}`;
|
|
277
|
-
}
|
|
1
|
+
// src/server/create.ts
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
createUpdate as createUpdate2,
|
|
5
|
+
flattenRouter,
|
|
6
|
+
isBatchResolver,
|
|
7
|
+
isMutationDef,
|
|
8
|
+
isQueryDef,
|
|
9
|
+
runWithContext
|
|
10
|
+
} from "@sylphx/lens-core";
|
|
278
11
|
|
|
279
12
|
// src/state/graph-state-manager.ts
|
|
13
|
+
import { createUpdate, makeEntityKey } from "@sylphx/lens-core";
|
|
14
|
+
|
|
280
15
|
class GraphStateManager {
|
|
281
16
|
clients = new Map;
|
|
282
17
|
canonical = new Map;
|
|
@@ -1301,7 +1036,7 @@ class LensServerImpl {
|
|
|
1301
1036
|
const oldValue = oldObj[key];
|
|
1302
1037
|
const newValue = newObj[key];
|
|
1303
1038
|
if (!this.deepEqual(oldValue, newValue)) {
|
|
1304
|
-
updates[key] =
|
|
1039
|
+
updates[key] = createUpdate2(oldValue, newValue);
|
|
1305
1040
|
}
|
|
1306
1041
|
}
|
|
1307
1042
|
return Object.keys(updates).length > 0 ? updates : null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sylphx/lens-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Server runtime for Lens API framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
|
-
"build": "
|
|
16
|
-
"build:types": "tsc --emitDeclarationOnly --outDir ./dist",
|
|
15
|
+
"build": "bunup",
|
|
17
16
|
"typecheck": "tsc --noEmit",
|
|
18
17
|
"test": "bun test"
|
|
19
18
|
},
|
|
@@ -30,7 +29,7 @@
|
|
|
30
29
|
"author": "SylphxAI",
|
|
31
30
|
"license": "MIT",
|
|
32
31
|
"dependencies": {
|
|
33
|
-
"@sylphx/lens-core": "
|
|
32
|
+
"@sylphx/lens-core": "^1.0.4"
|
|
34
33
|
},
|
|
35
34
|
"devDependencies": {
|
|
36
35
|
"typescript": "^5.9.3",
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAEN,YAAY,EAEZ,KAAK,UAAU,EACf,KAAK,gBAAgB,IAAI,YAAY,EACrC,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,eAAe,EAEpB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAElB,KAAK,aAAa,EAClB,KAAK,UAAU,EAEf,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,WAAW,GAChB,MAAM,iBAAiB,CAAC;AAMzB,OAAO,EAEN,iBAAiB,EAEjB,uBAAuB,EAEvB,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,uBAAuB,GAC5B,MAAM,SAAS,CAAC;AAMjB,OAAO,EAEN,UAAU,EAEV,gBAAgB,EAEhB,KAAK,gBAAgB,EACrB,KAAK,aAAa,GAClB,MAAM,eAAe,CAAC"}
|
package/dist/server/create.d.ts
DELETED
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @sylphx/lens-server - Lens Server
|
|
3
|
-
*
|
|
4
|
-
* Core server implementation:
|
|
5
|
-
* - Free Operations (query/mutation definitions)
|
|
6
|
-
* - GraphStateManager (per-client state tracking, minimal diffs)
|
|
7
|
-
* - Field-level subscriptions
|
|
8
|
-
* - Entity Resolvers with DataLoader batching
|
|
9
|
-
*/
|
|
10
|
-
import { type ContextValue, type EntityDef, type EntityDefinition, type EntityResolvers, type EntityResolversDefinition, type MutationDef, type QueryDef, type RelationDef, type RelationTypeWithForeignKey, type RouterDef } from "@sylphx/lens-core";
|
|
11
|
-
/** Selection object type for nested field selection */
|
|
12
|
-
export interface SelectionObject {
|
|
13
|
-
[key: string]: boolean | SelectionObject | {
|
|
14
|
-
select: SelectionObject;
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
import { GraphStateManager } from "../state/graph-state-manager";
|
|
18
|
-
/** Entity map type */
|
|
19
|
-
export type EntitiesMap = Record<string, EntityDef<string, any>>;
|
|
20
|
-
/** Queries map type */
|
|
21
|
-
export type QueriesMap = Record<string, QueryDef<unknown, unknown>>;
|
|
22
|
-
/** Mutations map type */
|
|
23
|
-
export type MutationsMap = Record<string, MutationDef<unknown, unknown>>;
|
|
24
|
-
/** Relations array type */
|
|
25
|
-
export type RelationsArray = RelationDef<EntityDef<string, EntityDefinition>, Record<string, RelationTypeWithForeignKey>>[];
|
|
26
|
-
/** Operation metadata for handshake */
|
|
27
|
-
export interface OperationMeta {
|
|
28
|
-
type: "query" | "mutation";
|
|
29
|
-
optimistic?: unknown;
|
|
30
|
-
}
|
|
31
|
-
/** Nested operations structure for handshake */
|
|
32
|
-
export type OperationsMap = {
|
|
33
|
-
[key: string]: OperationMeta | OperationsMap;
|
|
34
|
-
};
|
|
35
|
-
/** Server configuration */
|
|
36
|
-
export interface LensServerConfig<TContext extends ContextValue = ContextValue> {
|
|
37
|
-
/** Entity definitions */
|
|
38
|
-
entities?: EntitiesMap;
|
|
39
|
-
/** Relation definitions */
|
|
40
|
-
relations?: RelationsArray;
|
|
41
|
-
/** Router definition (namespaced operations) */
|
|
42
|
-
router?: RouterDef;
|
|
43
|
-
/** Query definitions (flat, legacy) */
|
|
44
|
-
queries?: QueriesMap;
|
|
45
|
-
/** Mutation definitions (flat, legacy) */
|
|
46
|
-
mutations?: MutationsMap;
|
|
47
|
-
/** Entity resolvers */
|
|
48
|
-
resolvers?: EntityResolvers<EntityResolversDefinition>;
|
|
49
|
-
/** Context factory */
|
|
50
|
-
context?: (req?: unknown) => TContext | Promise<TContext>;
|
|
51
|
-
/** Server version */
|
|
52
|
-
version?: string;
|
|
53
|
-
}
|
|
54
|
-
/** Server metadata for transport handshake */
|
|
55
|
-
export interface ServerMetadata {
|
|
56
|
-
/** Server version */
|
|
57
|
-
version: string;
|
|
58
|
-
/** Operations metadata map */
|
|
59
|
-
operations: OperationsMap;
|
|
60
|
-
}
|
|
61
|
-
/** Operation for in-process transport */
|
|
62
|
-
export interface LensOperation {
|
|
63
|
-
/** Operation path (e.g., 'user.get', 'session.create') */
|
|
64
|
-
path: string;
|
|
65
|
-
/** Operation input */
|
|
66
|
-
input?: unknown;
|
|
67
|
-
}
|
|
68
|
-
/** Result from operation execution */
|
|
69
|
-
export interface LensResult<T = unknown> {
|
|
70
|
-
/** Success data */
|
|
71
|
-
data?: T;
|
|
72
|
-
/** Error if operation failed */
|
|
73
|
-
error?: Error;
|
|
74
|
-
}
|
|
75
|
-
/** Lens server interface */
|
|
76
|
-
export interface LensServer {
|
|
77
|
-
/** Get server metadata for transport handshake */
|
|
78
|
-
getMetadata(): ServerMetadata;
|
|
79
|
-
/** Execute operation - auto-detects query vs mutation from registered operations */
|
|
80
|
-
execute(op: LensOperation): Promise<LensResult>;
|
|
81
|
-
/** Execute a query (one-time) */
|
|
82
|
-
executeQuery<TInput, TOutput>(name: string, input?: TInput): Promise<TOutput>;
|
|
83
|
-
/** Execute a mutation */
|
|
84
|
-
executeMutation<TInput, TOutput>(name: string, input: TInput): Promise<TOutput>;
|
|
85
|
-
/** Handle WebSocket connection */
|
|
86
|
-
handleWebSocket(ws: WebSocketLike): void;
|
|
87
|
-
/** Handle HTTP request */
|
|
88
|
-
handleRequest(req: Request): Promise<Response>;
|
|
89
|
-
/** Get GraphStateManager for external access */
|
|
90
|
-
getStateManager(): GraphStateManager;
|
|
91
|
-
/** Start server */
|
|
92
|
-
listen(port: number): Promise<void>;
|
|
93
|
-
/** Close server */
|
|
94
|
-
close(): Promise<void>;
|
|
95
|
-
}
|
|
96
|
-
/** WebSocket interface */
|
|
97
|
-
export interface WebSocketLike {
|
|
98
|
-
send(data: string): void;
|
|
99
|
-
close(): void;
|
|
100
|
-
onmessage?: ((event: {
|
|
101
|
-
data: string;
|
|
102
|
-
}) => void) | null;
|
|
103
|
-
onclose?: (() => void) | null;
|
|
104
|
-
onerror?: ((error: unknown) => void) | null;
|
|
105
|
-
}
|
|
106
|
-
declare class LensServerImpl<Q extends QueriesMap = QueriesMap, M extends MutationsMap = MutationsMap, TContext extends ContextValue = ContextValue> implements LensServer {
|
|
107
|
-
private queries;
|
|
108
|
-
private mutations;
|
|
109
|
-
private entities;
|
|
110
|
-
private resolvers?;
|
|
111
|
-
private contextFactory;
|
|
112
|
-
private version;
|
|
113
|
-
private ctx;
|
|
114
|
-
/** GraphStateManager for per-client state tracking */
|
|
115
|
-
private stateManager;
|
|
116
|
-
/** DataLoaders for N+1 batching (per-request) */
|
|
117
|
-
private loaders;
|
|
118
|
-
/** Client connections */
|
|
119
|
-
private connections;
|
|
120
|
-
private connectionCounter;
|
|
121
|
-
/** Server instance */
|
|
122
|
-
private server;
|
|
123
|
-
constructor(config: LensServerConfig<TContext> & {
|
|
124
|
-
queries?: Q;
|
|
125
|
-
mutations?: M;
|
|
126
|
-
});
|
|
127
|
-
getStateManager(): GraphStateManager;
|
|
128
|
-
/**
|
|
129
|
-
* Get server metadata for transport handshake.
|
|
130
|
-
* Used by inProcess transport for direct access.
|
|
131
|
-
*/
|
|
132
|
-
getMetadata(): ServerMetadata;
|
|
133
|
-
/**
|
|
134
|
-
* Execute operation - auto-detects query vs mutation from registered operations.
|
|
135
|
-
* Used by inProcess transport for direct server calls.
|
|
136
|
-
*/
|
|
137
|
-
execute(op: LensOperation): Promise<LensResult>;
|
|
138
|
-
/**
|
|
139
|
-
* Build nested operations map for handshake response
|
|
140
|
-
* Converts flat "user.get", "user.create" into nested { user: { get: {...}, create: {...} } }
|
|
141
|
-
*/
|
|
142
|
-
private buildOperationsMap;
|
|
143
|
-
handleWebSocket(ws: WebSocketLike): void;
|
|
144
|
-
private handleMessage;
|
|
145
|
-
private handleHandshake;
|
|
146
|
-
private handleSubscribe;
|
|
147
|
-
private executeSubscription;
|
|
148
|
-
private handleUpdateFields;
|
|
149
|
-
private handleUnsubscribe;
|
|
150
|
-
private handleQuery;
|
|
151
|
-
private handleMutation;
|
|
152
|
-
private handleDisconnect;
|
|
153
|
-
executeQuery<TInput, TOutput>(name: string, input?: TInput): Promise<TOutput>;
|
|
154
|
-
executeMutation<TInput, TOutput>(name: string, input: TInput): Promise<TOutput>;
|
|
155
|
-
handleRequest(req: Request): Promise<Response>;
|
|
156
|
-
listen(port: number): Promise<void>;
|
|
157
|
-
close(): Promise<void>;
|
|
158
|
-
private findConnectionByWs;
|
|
159
|
-
private getEntityNameFromOutput;
|
|
160
|
-
private getEntityNameFromMutation;
|
|
161
|
-
private extractEntities;
|
|
162
|
-
private applySelection;
|
|
163
|
-
private applySelectionToObject;
|
|
164
|
-
/**
|
|
165
|
-
* Execute entity resolvers for nested data.
|
|
166
|
-
* Processes the selection object and resolves relation fields.
|
|
167
|
-
*/
|
|
168
|
-
private executeEntityResolvers;
|
|
169
|
-
/**
|
|
170
|
-
* Get target entity name for a relation field.
|
|
171
|
-
*/
|
|
172
|
-
private getRelationTargetEntity;
|
|
173
|
-
/**
|
|
174
|
-
* Serialize entity data for transport.
|
|
175
|
-
* Auto-calls serialize() on field types (Date → ISO string, etc.)
|
|
176
|
-
*/
|
|
177
|
-
private serializeEntity;
|
|
178
|
-
/**
|
|
179
|
-
* Process query result: execute entity resolvers, apply selection, serialize
|
|
180
|
-
*/
|
|
181
|
-
private processQueryResult;
|
|
182
|
-
private computeUpdates;
|
|
183
|
-
private deepEqual;
|
|
184
|
-
private clearLoaders;
|
|
185
|
-
}
|
|
186
|
-
/**
|
|
187
|
-
* Infer input type from a query/mutation definition
|
|
188
|
-
*/
|
|
189
|
-
export type InferInput<T> = T extends QueryDef<infer I, unknown> ? I extends void ? void : I : T extends MutationDef<infer I, unknown> ? I : never;
|
|
190
|
-
/**
|
|
191
|
-
* Infer output type from a query/mutation definition
|
|
192
|
-
*/
|
|
193
|
-
export type InferOutput<T> = T extends QueryDef<unknown, infer O> ? O : T extends MutationDef<unknown, infer O> ? O : never;
|
|
194
|
-
/**
|
|
195
|
-
* API type for client inference
|
|
196
|
-
* Export this type for client-side type safety
|
|
197
|
-
*
|
|
198
|
-
* @example
|
|
199
|
-
* ```typescript
|
|
200
|
-
* // Server
|
|
201
|
-
* const server = createLensServer({ queries, mutations });
|
|
202
|
-
* export type Api = InferApi<typeof server>;
|
|
203
|
-
*
|
|
204
|
-
* // Client (only imports TYPE)
|
|
205
|
-
* import type { Api } from './server';
|
|
206
|
-
* const client = createClient<Api>({ links: [...] });
|
|
207
|
-
* ```
|
|
208
|
-
*/
|
|
209
|
-
export type InferApi<T extends LensServer> = T extends LensServerImpl<infer Q, infer M> ? {
|
|
210
|
-
queries: Q;
|
|
211
|
-
mutations: M;
|
|
212
|
-
} : never;
|
|
213
|
-
/**
|
|
214
|
-
* Create Lens server with Operations API + Optimization Layer
|
|
215
|
-
*/
|
|
216
|
-
export declare function createServer<TContext extends ContextValue = ContextValue, Q extends QueriesMap = QueriesMap, M extends MutationsMap = MutationsMap>(config: LensServerConfig<TContext> & {
|
|
217
|
-
queries?: Q;
|
|
218
|
-
mutations?: M;
|
|
219
|
-
}): LensServer & {
|
|
220
|
-
_types: {
|
|
221
|
-
queries: Q;
|
|
222
|
-
mutations: M;
|
|
223
|
-
};
|
|
224
|
-
};
|
|
225
|
-
export {};
|
|
226
|
-
//# sourceMappingURL=create.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/server/create.ts"],"names":[],"mappings":"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"}
|
package/dist/sse/handler.d.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @sylphx/lens-server - SSE Transport Adapter
|
|
3
|
-
*
|
|
4
|
-
* Thin transport adapter for Server-Sent Events.
|
|
5
|
-
* Connects SSE streams to GraphStateManager.
|
|
6
|
-
*/
|
|
7
|
-
import type { GraphStateManager } from "../state/graph-state-manager";
|
|
8
|
-
/** SSE handler configuration */
|
|
9
|
-
export interface SSEHandlerConfig {
|
|
10
|
-
/** GraphStateManager instance (required) */
|
|
11
|
-
stateManager: GraphStateManager;
|
|
12
|
-
/** Heartbeat interval in ms (default: 30000) */
|
|
13
|
-
heartbeatInterval?: number;
|
|
14
|
-
}
|
|
15
|
-
/** SSE client info */
|
|
16
|
-
export interface SSEClientInfo {
|
|
17
|
-
id: string;
|
|
18
|
-
connectedAt: number;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* SSE transport adapter for GraphStateManager.
|
|
22
|
-
*
|
|
23
|
-
* This is a thin adapter that:
|
|
24
|
-
* - Creates SSE connections
|
|
25
|
-
* - Registers clients with GraphStateManager
|
|
26
|
-
* - Forwards updates to SSE streams
|
|
27
|
-
*
|
|
28
|
-
* All state/subscription logic is handled by GraphStateManager.
|
|
29
|
-
*
|
|
30
|
-
* @example
|
|
31
|
-
* ```typescript
|
|
32
|
-
* const stateManager = new GraphStateManager();
|
|
33
|
-
* const sse = new SSEHandler({ stateManager });
|
|
34
|
-
*
|
|
35
|
-
* // Handle SSE connection
|
|
36
|
-
* app.get('/events', (req) => sse.handleConnection(req));
|
|
37
|
-
*
|
|
38
|
-
* // Subscribe via separate endpoint or message
|
|
39
|
-
* stateManager.subscribe(clientId, "Post", "123", "*");
|
|
40
|
-
* ```
|
|
41
|
-
*/
|
|
42
|
-
export declare class SSEHandler {
|
|
43
|
-
private stateManager;
|
|
44
|
-
private heartbeatInterval;
|
|
45
|
-
private clients;
|
|
46
|
-
private clientCounter;
|
|
47
|
-
constructor(config: SSEHandlerConfig);
|
|
48
|
-
/**
|
|
49
|
-
* Handle new SSE connection
|
|
50
|
-
* Returns a Response with SSE stream
|
|
51
|
-
*/
|
|
52
|
-
handleConnection(req?: Request): Response;
|
|
53
|
-
/**
|
|
54
|
-
* Remove client and cleanup
|
|
55
|
-
*/
|
|
56
|
-
private removeClient;
|
|
57
|
-
/**
|
|
58
|
-
* Close specific client connection
|
|
59
|
-
*/
|
|
60
|
-
closeClient(clientId: string): void;
|
|
61
|
-
/**
|
|
62
|
-
* Get connected client count
|
|
63
|
-
*/
|
|
64
|
-
getClientCount(): number;
|
|
65
|
-
/**
|
|
66
|
-
* Get connected client IDs
|
|
67
|
-
*/
|
|
68
|
-
getClientIds(): string[];
|
|
69
|
-
/**
|
|
70
|
-
* Close all connections
|
|
71
|
-
*/
|
|
72
|
-
closeAll(): void;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Create SSE handler (transport adapter)
|
|
76
|
-
*/
|
|
77
|
-
export declare function createSSEHandler(config: SSEHandlerConfig): SSEHandler;
|
|
78
|
-
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/sse/handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAe,MAAM,8BAA8B,CAAC;AAMnF,gCAAgC;AAChC,MAAM,WAAW,gBAAgB;IAChC,4CAA4C;IAC5C,YAAY,EAAE,iBAAiB,CAAC;IAChC,gDAAgD;IAChD,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,sBAAsB;AACtB,MAAM,WAAW,aAAa;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;CACpB;AAMD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,UAAU;IACtB,OAAO,CAAC,YAAY,CAAoB;IACxC,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,OAAO,CAGX;IACJ,OAAO,CAAC,aAAa,CAAK;gBAEd,MAAM,EAAE,gBAAgB;IAKpC;;;OAGG;IACH,gBAAgB,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,QAAQ;IAqDzC;;OAEG;IACH,OAAO,CAAC,YAAY;IASpB;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAYnC;;OAEG;IACH,cAAc,IAAI,MAAM;IAIxB;;OAEG;IACH,YAAY,IAAI,MAAM,EAAE;IAIxB;;OAEG;IACH,QAAQ,IAAI,IAAI;CAKhB;AAMD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CAErE"}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @sylphx/lens-server - Graph State Manager
|
|
3
|
-
*
|
|
4
|
-
* Core orchestration layer that:
|
|
5
|
-
* - Maintains canonical state per entity (server truth)
|
|
6
|
-
* - Tracks per-client last known state
|
|
7
|
-
* - Computes minimal diffs when state changes
|
|
8
|
-
* - Auto-selects transfer strategy (value/delta/patch)
|
|
9
|
-
* - Pushes updates to subscribed clients
|
|
10
|
-
*/
|
|
11
|
-
import { type EntityKey, type Update } from "@sylphx/lens-core";
|
|
12
|
-
export type { EntityKey };
|
|
13
|
-
/** Client connection interface */
|
|
14
|
-
export interface StateClient {
|
|
15
|
-
id: string;
|
|
16
|
-
send: (message: StateUpdateMessage) => void;
|
|
17
|
-
}
|
|
18
|
-
/** Update message sent to clients */
|
|
19
|
-
export interface StateUpdateMessage {
|
|
20
|
-
type: "update";
|
|
21
|
-
entity: string;
|
|
22
|
-
id: string;
|
|
23
|
-
/** Field-level updates with strategy */
|
|
24
|
-
updates: Record<string, Update>;
|
|
25
|
-
}
|
|
26
|
-
/** Full entity update message */
|
|
27
|
-
export interface StateFullMessage {
|
|
28
|
-
type: "data";
|
|
29
|
-
entity: string;
|
|
30
|
-
id: string;
|
|
31
|
-
data: Record<string, unknown>;
|
|
32
|
-
}
|
|
33
|
-
/** Subscription info */
|
|
34
|
-
export interface Subscription {
|
|
35
|
-
clientId: string;
|
|
36
|
-
fields: Set<string> | "*";
|
|
37
|
-
}
|
|
38
|
-
/** Configuration */
|
|
39
|
-
export interface GraphStateManagerConfig {
|
|
40
|
-
/** Called when an entity has no more subscribers */
|
|
41
|
-
onEntityUnsubscribed?: (entity: string, id: string) => void;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Manages server-side canonical state and syncs to clients.
|
|
45
|
-
*
|
|
46
|
-
* @example
|
|
47
|
-
* ```typescript
|
|
48
|
-
* const manager = new GraphStateManager();
|
|
49
|
-
*
|
|
50
|
-
* // Add client
|
|
51
|
-
* manager.addClient({
|
|
52
|
-
* id: "client-1",
|
|
53
|
-
* send: (msg) => ws.send(JSON.stringify(msg)),
|
|
54
|
-
* });
|
|
55
|
-
*
|
|
56
|
-
* // Subscribe client to entity
|
|
57
|
-
* manager.subscribe("client-1", "Post", "123", ["title", "content"]);
|
|
58
|
-
*
|
|
59
|
-
* // Emit updates (from resolvers)
|
|
60
|
-
* manager.emit("Post", "123", { content: "Updated content" });
|
|
61
|
-
* // → Automatically computes diff and sends to subscribed clients
|
|
62
|
-
* ```
|
|
63
|
-
*/
|
|
64
|
-
export declare class GraphStateManager {
|
|
65
|
-
/** Connected clients */
|
|
66
|
-
private clients;
|
|
67
|
-
/** Canonical state per entity (server truth) */
|
|
68
|
-
private canonical;
|
|
69
|
-
/** Per-client state tracking */
|
|
70
|
-
private clientStates;
|
|
71
|
-
/** Entity → subscribed client IDs */
|
|
72
|
-
private entitySubscribers;
|
|
73
|
-
/** Configuration */
|
|
74
|
-
private config;
|
|
75
|
-
constructor(config?: GraphStateManagerConfig);
|
|
76
|
-
/**
|
|
77
|
-
* Add a client connection
|
|
78
|
-
*/
|
|
79
|
-
addClient(client: StateClient): void;
|
|
80
|
-
/**
|
|
81
|
-
* Remove a client and cleanup all subscriptions
|
|
82
|
-
*/
|
|
83
|
-
removeClient(clientId: string): void;
|
|
84
|
-
/**
|
|
85
|
-
* Subscribe a client to an entity
|
|
86
|
-
*/
|
|
87
|
-
subscribe(clientId: string, entity: string, id: string, fields?: string[] | "*"): void;
|
|
88
|
-
/**
|
|
89
|
-
* Unsubscribe a client from an entity
|
|
90
|
-
*/
|
|
91
|
-
unsubscribe(clientId: string, entity: string, id: string): void;
|
|
92
|
-
/**
|
|
93
|
-
* Update subscription fields for a client
|
|
94
|
-
*/
|
|
95
|
-
updateSubscription(clientId: string, entity: string, id: string, fields: string[] | "*"): void;
|
|
96
|
-
/**
|
|
97
|
-
* Emit data for an entity.
|
|
98
|
-
* This is the core method called by resolvers.
|
|
99
|
-
*
|
|
100
|
-
* @param entity - Entity name
|
|
101
|
-
* @param id - Entity ID
|
|
102
|
-
* @param data - Full or partial entity data
|
|
103
|
-
* @param options - Emit options
|
|
104
|
-
*/
|
|
105
|
-
emit(entity: string, id: string, data: Record<string, unknown>, options?: {
|
|
106
|
-
replace?: boolean;
|
|
107
|
-
}): void;
|
|
108
|
-
/**
|
|
109
|
-
* Get current canonical state for an entity
|
|
110
|
-
*/
|
|
111
|
-
getState(entity: string, id: string): Record<string, unknown> | undefined;
|
|
112
|
-
/**
|
|
113
|
-
* Check if entity has any subscribers
|
|
114
|
-
*/
|
|
115
|
-
hasSubscribers(entity: string, id: string): boolean;
|
|
116
|
-
/**
|
|
117
|
-
* Push update to a specific client
|
|
118
|
-
*/
|
|
119
|
-
private pushToClient;
|
|
120
|
-
/**
|
|
121
|
-
* Send initial data to a newly subscribed client
|
|
122
|
-
*/
|
|
123
|
-
private sendInitialData;
|
|
124
|
-
/**
|
|
125
|
-
* Cleanup entity when no subscribers remain
|
|
126
|
-
*/
|
|
127
|
-
private cleanupEntity;
|
|
128
|
-
private makeKey;
|
|
129
|
-
/**
|
|
130
|
-
* Get statistics
|
|
131
|
-
*/
|
|
132
|
-
getStats(): {
|
|
133
|
-
clients: number;
|
|
134
|
-
entities: number;
|
|
135
|
-
totalSubscriptions: number;
|
|
136
|
-
};
|
|
137
|
-
/**
|
|
138
|
-
* Clear all state (for testing)
|
|
139
|
-
*/
|
|
140
|
-
clear(): void;
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Create a GraphStateManager instance
|
|
144
|
-
*/
|
|
145
|
-
export declare function createGraphStateManager(config?: GraphStateManagerConfig): GraphStateManager;
|
|
146
|
-
//# sourceMappingURL=graph-state-manager.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"graph-state-manager.d.ts","sourceRoot":"","sources":["../../src/state/graph-state-manager.ts"],"names":[],"mappings":"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"}
|
package/dist/state/index.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @sylphx/lens-server - State Management
|
|
3
|
-
*
|
|
4
|
-
* Server-side state management for reactive data sync.
|
|
5
|
-
*/
|
|
6
|
-
export { GraphStateManager, createGraphStateManager, type EntityKey, type StateClient, type StateUpdateMessage, type StateFullMessage, type Subscription, type GraphStateManagerConfig, } from "./graph-state-manager";
|
|
7
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/state/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACN,iBAAiB,EACjB,uBAAuB,EACvB,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,uBAAuB,GAC5B,MAAM,uBAAuB,CAAC"}
|