@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 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
- * @sylphx/lens-server
3
- *
4
- * Server runtime for Lens API framework.
5
- * Operations-based server with GraphStateManager for reactive updates.
6
- */
7
- export { createServer, type LensServer, type LensServerConfig as ServerConfig, type EntitiesMap, type RelationsArray, type QueriesMap, type MutationsMap, type WebSocketLike, type SelectionObject, type ServerMetadata, type OperationMeta, type OperationsMap, type LensOperation, type LensResult, type InferApi, type InferInput, type InferOutput, } from "./server/create";
8
- export { GraphStateManager, createGraphStateManager, type EntityKey, type StateClient, type StateUpdateMessage, type StateFullMessage, type Subscription, type GraphStateManagerConfig, } from "./state";
9
- export { SSEHandler, createSSEHandler, type SSEHandlerConfig, type SSEClientInfo, } from "./sse/handler";
10
- //# sourceMappingURL=index.d.ts.map
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
- // ../core/dist/index.js
2
- import { AsyncLocalStorage } from "node:async_hooks";
3
- var ENTITY_SYMBOL = Symbol("lens:entity");
4
- var valueStrategy = {
5
- name: "value",
6
- encode(_prev, next) {
7
- return { strategy: "value", data: next };
8
- },
9
- decode(_current, update) {
10
- return update.data;
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] = createUpdate(oldValue, newValue);
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.2",
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": "bun build ./src/index.ts --outdir ./dist --target node && bun run build:types",
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": "workspace:*"
32
+ "@sylphx/lens-core": "^1.0.4"
34
33
  },
35
34
  "devDependencies": {
36
35
  "typescript": "^5.9.3",
@@ -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"}
@@ -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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"}
@@ -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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"}
@@ -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"}