@sylphx/lens-server 1.0.2
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 +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1437 -0
- package/dist/server/create.d.ts +226 -0
- package/dist/server/create.d.ts.map +1 -0
- package/dist/sse/handler.d.ts +78 -0
- package/dist/sse/handler.d.ts.map +1 -0
- package/dist/state/graph-state-manager.d.ts +146 -0
- package/dist/state/graph-state-manager.d.ts.map +1 -0
- package/dist/state/index.d.ts +7 -0
- package/dist/state/index.d.ts.map +1 -0
- package/package.json +39 -0
- package/src/e2e/server.test.ts +666 -0
- package/src/index.ts +67 -0
- package/src/server/create.test.ts +807 -0
- package/src/server/create.ts +1536 -0
- package/src/sse/handler.ts +185 -0
- package/src/state/graph-state-manager.test.ts +334 -0
- package/src/state/graph-state-manager.ts +443 -0
- package/src/state/index.ts +16 -0
|
@@ -0,0 +1,226 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/server/create.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,146 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-state-manager.d.ts","sourceRoot":"","sources":["../../src/state/graph-state-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sylphx/lens-server",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Server runtime for Lens API framework",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target node && bun run build:types",
|
|
16
|
+
"build:types": "tsc --emitDeclarationOnly --outDir ./dist",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"test": "bun test"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"src"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"lens",
|
|
26
|
+
"server",
|
|
27
|
+
"resolvers",
|
|
28
|
+
"graphql"
|
|
29
|
+
],
|
|
30
|
+
"author": "SylphxAI",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@sylphx/lens-core": "workspace:*"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"typescript": "^5.9.3",
|
|
37
|
+
"zod": "^4.1.13"
|
|
38
|
+
}
|
|
39
|
+
}
|