@grafema/rfdb-client 0.1.0-alpha.1
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/LICENSE +190 -0
- package/README.md +77 -0
- package/dist/client.d.ts +178 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +429 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/protocol.d.ts +7 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +6 -0
- package/package.json +54 -0
- package/ts/client.ts +507 -0
- package/ts/index.ts +24 -0
- package/ts/protocol.ts +54 -0
package/ts/client.ts
ADDED
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFDBClient - Unix socket client for RFDB server
|
|
3
|
+
*
|
|
4
|
+
* Provides the same API as GraphEngine NAPI binding but communicates
|
|
5
|
+
* with a separate rfdb-server process over Unix socket + MessagePack.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createConnection, Socket } from 'net';
|
|
9
|
+
import { encode, decode } from '@msgpack/msgpack';
|
|
10
|
+
import { EventEmitter } from 'events';
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
RFDBCommand,
|
|
14
|
+
WireNode,
|
|
15
|
+
WireEdge,
|
|
16
|
+
RFDBResponse,
|
|
17
|
+
IRFDBClient,
|
|
18
|
+
AttrQuery,
|
|
19
|
+
DatalogResult,
|
|
20
|
+
NodeType,
|
|
21
|
+
EdgeType,
|
|
22
|
+
} from '@grafema/types';
|
|
23
|
+
|
|
24
|
+
interface PendingRequest {
|
|
25
|
+
resolve: (value: RFDBResponse) => void;
|
|
26
|
+
reject: (error: Error) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class RFDBClient extends EventEmitter implements IRFDBClient {
|
|
30
|
+
readonly socketPath: string;
|
|
31
|
+
private socket: Socket | null;
|
|
32
|
+
connected: boolean;
|
|
33
|
+
private pending: Map<number, PendingRequest>;
|
|
34
|
+
private reqId: number;
|
|
35
|
+
private buffer: Buffer;
|
|
36
|
+
|
|
37
|
+
constructor(socketPath: string = '/tmp/rfdb.sock') {
|
|
38
|
+
super();
|
|
39
|
+
this.socketPath = socketPath;
|
|
40
|
+
this.socket = null;
|
|
41
|
+
this.connected = false;
|
|
42
|
+
this.pending = new Map();
|
|
43
|
+
this.reqId = 0;
|
|
44
|
+
this.buffer = Buffer.alloc(0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Connect to RFDB server
|
|
49
|
+
*/
|
|
50
|
+
async connect(): Promise<void> {
|
|
51
|
+
if (this.connected) return;
|
|
52
|
+
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
this.socket = createConnection(this.socketPath);
|
|
55
|
+
|
|
56
|
+
this.socket.on('connect', () => {
|
|
57
|
+
this.connected = true;
|
|
58
|
+
this.emit('connected');
|
|
59
|
+
resolve();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
this.socket.on('error', (err: Error) => {
|
|
63
|
+
if (!this.connected) {
|
|
64
|
+
reject(err);
|
|
65
|
+
} else {
|
|
66
|
+
this.emit('error', err);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
this.socket.on('close', () => {
|
|
71
|
+
this.connected = false;
|
|
72
|
+
this.emit('disconnected');
|
|
73
|
+
// Reject all pending requests
|
|
74
|
+
for (const [, { reject }] of this.pending) {
|
|
75
|
+
reject(new Error('Connection closed'));
|
|
76
|
+
}
|
|
77
|
+
this.pending.clear();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
this.socket.on('data', (chunk: Buffer) => {
|
|
81
|
+
this._handleData(chunk);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Handle incoming data, parse framed messages
|
|
88
|
+
*/
|
|
89
|
+
private _handleData(chunk: Buffer): void {
|
|
90
|
+
this.buffer = Buffer.concat([this.buffer, chunk]);
|
|
91
|
+
|
|
92
|
+
while (this.buffer.length >= 4) {
|
|
93
|
+
// Read length prefix (4 bytes, big-endian)
|
|
94
|
+
const msgLen = this.buffer.readUInt32BE(0);
|
|
95
|
+
|
|
96
|
+
if (this.buffer.length < 4 + msgLen) {
|
|
97
|
+
// Not enough data yet
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Extract message
|
|
102
|
+
const msgBytes = this.buffer.subarray(4, 4 + msgLen);
|
|
103
|
+
this.buffer = this.buffer.subarray(4 + msgLen);
|
|
104
|
+
|
|
105
|
+
// Decode and dispatch
|
|
106
|
+
try {
|
|
107
|
+
const response = decode(msgBytes) as RFDBResponse;
|
|
108
|
+
this._handleResponse(response);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
this.emit('error', new Error(`Failed to decode response: ${(err as Error).message}`));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Handle decoded response
|
|
117
|
+
*/
|
|
118
|
+
private _handleResponse(response: RFDBResponse): void {
|
|
119
|
+
if (this.pending.size === 0) {
|
|
120
|
+
this.emit('error', new Error('Received response with no pending request'));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Get the oldest pending request (FIFO)
|
|
125
|
+
const [id, { resolve, reject }] = this.pending.entries().next().value as [number, PendingRequest];
|
|
126
|
+
this.pending.delete(id);
|
|
127
|
+
|
|
128
|
+
if (response.error) {
|
|
129
|
+
reject(new Error(response.error));
|
|
130
|
+
} else {
|
|
131
|
+
resolve(response);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Send a request and wait for response
|
|
137
|
+
*/
|
|
138
|
+
private async _send(cmd: RFDBCommand, payload: Record<string, unknown> = {}): Promise<RFDBResponse> {
|
|
139
|
+
if (!this.connected || !this.socket) {
|
|
140
|
+
throw new Error('Not connected to RFDB server');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const request = { cmd, ...payload };
|
|
144
|
+
const msgBytes = encode(request);
|
|
145
|
+
|
|
146
|
+
return new Promise((resolve, reject) => {
|
|
147
|
+
const id = this.reqId++;
|
|
148
|
+
this.pending.set(id, { resolve, reject });
|
|
149
|
+
|
|
150
|
+
// Write length prefix + message
|
|
151
|
+
const header = Buffer.alloc(4);
|
|
152
|
+
header.writeUInt32BE(msgBytes.length);
|
|
153
|
+
|
|
154
|
+
this.socket!.write(Buffer.concat([header, Buffer.from(msgBytes)]));
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ===========================================================================
|
|
159
|
+
// Write Operations
|
|
160
|
+
// ===========================================================================
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Add nodes to the graph
|
|
164
|
+
*/
|
|
165
|
+
async addNodes(nodes: Array<Partial<WireNode> & { id: string; type?: string; node_type?: string; nodeType?: string }>): Promise<RFDBResponse> {
|
|
166
|
+
const wireNodes: WireNode[] = nodes.map(n => ({
|
|
167
|
+
id: String(n.id),
|
|
168
|
+
nodeType: (n.node_type || n.nodeType || n.type || 'UNKNOWN') as NodeType,
|
|
169
|
+
name: n.name || '',
|
|
170
|
+
file: n.file || '',
|
|
171
|
+
exported: n.exported || false,
|
|
172
|
+
metadata: typeof n.metadata === 'string' ? n.metadata : JSON.stringify(n.metadata || {}),
|
|
173
|
+
}));
|
|
174
|
+
|
|
175
|
+
return this._send('addNodes', { nodes: wireNodes });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Add edges to the graph
|
|
180
|
+
*/
|
|
181
|
+
async addEdges(
|
|
182
|
+
edges: Array<Partial<WireEdge> & { src: string; dst: string; type?: string; edge_type?: string; edgeType?: string }>,
|
|
183
|
+
skipValidation: boolean = false
|
|
184
|
+
): Promise<RFDBResponse> {
|
|
185
|
+
const wireEdges: WireEdge[] = edges.map(e => ({
|
|
186
|
+
src: String(e.src),
|
|
187
|
+
dst: String(e.dst),
|
|
188
|
+
edgeType: (e.edge_type || e.edgeType || e.type || 'UNKNOWN') as EdgeType,
|
|
189
|
+
metadata: typeof e.metadata === 'string' ? e.metadata : JSON.stringify(e.metadata || {}),
|
|
190
|
+
}));
|
|
191
|
+
|
|
192
|
+
return this._send('addEdges', { edges: wireEdges, skipValidation });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Delete a node
|
|
197
|
+
*/
|
|
198
|
+
async deleteNode(id: string): Promise<RFDBResponse> {
|
|
199
|
+
return this._send('deleteNode', { id: String(id) });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Delete an edge
|
|
204
|
+
*/
|
|
205
|
+
async deleteEdge(src: string, dst: string, edgeType: EdgeType): Promise<RFDBResponse> {
|
|
206
|
+
return this._send('deleteEdge', {
|
|
207
|
+
src: String(src),
|
|
208
|
+
dst: String(dst),
|
|
209
|
+
edgeType
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ===========================================================================
|
|
214
|
+
// Read Operations
|
|
215
|
+
// ===========================================================================
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Get a node by ID
|
|
219
|
+
*/
|
|
220
|
+
async getNode(id: string): Promise<WireNode | null> {
|
|
221
|
+
const response = await this._send('getNode', { id: String(id) });
|
|
222
|
+
return (response as { node?: WireNode }).node || null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Check if node exists
|
|
227
|
+
*/
|
|
228
|
+
async nodeExists(id: string): Promise<boolean> {
|
|
229
|
+
const response = await this._send('nodeExists', { id: String(id) });
|
|
230
|
+
return (response as { value: boolean }).value;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Find nodes by type
|
|
235
|
+
*/
|
|
236
|
+
async findByType(nodeType: NodeType): Promise<string[]> {
|
|
237
|
+
const response = await this._send('findByType', { nodeType });
|
|
238
|
+
return (response as { ids?: string[] }).ids || [];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Find nodes by attributes
|
|
243
|
+
*/
|
|
244
|
+
async findByAttr(query: Record<string, unknown>): Promise<string[]> {
|
|
245
|
+
const response = await this._send('findByAttr', { query });
|
|
246
|
+
return (response as { ids?: string[] }).ids || [];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ===========================================================================
|
|
250
|
+
// Graph Traversal
|
|
251
|
+
// ===========================================================================
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get neighbors of a node
|
|
255
|
+
*/
|
|
256
|
+
async neighbors(id: string, edgeTypes: EdgeType[] = []): Promise<string[]> {
|
|
257
|
+
const response = await this._send('neighbors', {
|
|
258
|
+
id: String(id),
|
|
259
|
+
edgeTypes
|
|
260
|
+
});
|
|
261
|
+
return (response as { ids?: string[] }).ids || [];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Breadth-first search
|
|
266
|
+
*/
|
|
267
|
+
async bfs(startIds: string[], maxDepth: number, edgeTypes: EdgeType[] = []): Promise<string[]> {
|
|
268
|
+
const response = await this._send('bfs', {
|
|
269
|
+
startIds: startIds.map(String),
|
|
270
|
+
maxDepth,
|
|
271
|
+
edgeTypes
|
|
272
|
+
});
|
|
273
|
+
return (response as { ids?: string[] }).ids || [];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Depth-first search
|
|
278
|
+
*/
|
|
279
|
+
async dfs(startIds: string[], maxDepth: number, edgeTypes: EdgeType[] = []): Promise<string[]> {
|
|
280
|
+
const response = await this._send('dfs', {
|
|
281
|
+
startIds: startIds.map(String),
|
|
282
|
+
maxDepth,
|
|
283
|
+
edgeTypes
|
|
284
|
+
});
|
|
285
|
+
return (response as { ids?: string[] }).ids || [];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Get outgoing edges from a node
|
|
290
|
+
*/
|
|
291
|
+
async getOutgoingEdges(id: string, edgeTypes: EdgeType[] | null = null): Promise<WireEdge[]> {
|
|
292
|
+
const response = await this._send('getOutgoingEdges', {
|
|
293
|
+
id: String(id),
|
|
294
|
+
edgeTypes
|
|
295
|
+
});
|
|
296
|
+
return (response as { edges?: WireEdge[] }).edges || [];
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Get incoming edges to a node
|
|
301
|
+
*/
|
|
302
|
+
async getIncomingEdges(id: string, edgeTypes: EdgeType[] | null = null): Promise<WireEdge[]> {
|
|
303
|
+
const response = await this._send('getIncomingEdges', {
|
|
304
|
+
id: String(id),
|
|
305
|
+
edgeTypes
|
|
306
|
+
});
|
|
307
|
+
return (response as { edges?: WireEdge[] }).edges || [];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ===========================================================================
|
|
311
|
+
// Stats
|
|
312
|
+
// ===========================================================================
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Get node count
|
|
316
|
+
*/
|
|
317
|
+
async nodeCount(): Promise<number> {
|
|
318
|
+
const response = await this._send('nodeCount');
|
|
319
|
+
return (response as { count: number }).count;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Get edge count
|
|
324
|
+
*/
|
|
325
|
+
async edgeCount(): Promise<number> {
|
|
326
|
+
const response = await this._send('edgeCount');
|
|
327
|
+
return (response as { count: number }).count;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Count nodes by type
|
|
332
|
+
*/
|
|
333
|
+
async countNodesByType(types: NodeType[] | null = null): Promise<Record<string, number>> {
|
|
334
|
+
const response = await this._send('countNodesByType', { types });
|
|
335
|
+
return (response as { counts?: Record<string, number> }).counts || {};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Count edges by type
|
|
340
|
+
*/
|
|
341
|
+
async countEdgesByType(edgeTypes: EdgeType[] | null = null): Promise<Record<string, number>> {
|
|
342
|
+
const response = await this._send('countEdgesByType', { edgeTypes });
|
|
343
|
+
return (response as { counts?: Record<string, number> }).counts || {};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ===========================================================================
|
|
347
|
+
// Control
|
|
348
|
+
// ===========================================================================
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Flush data to disk
|
|
352
|
+
*/
|
|
353
|
+
async flush(): Promise<RFDBResponse> {
|
|
354
|
+
return this._send('flush');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Compact the database
|
|
359
|
+
*/
|
|
360
|
+
async compact(): Promise<RFDBResponse> {
|
|
361
|
+
return this._send('compact');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Clear the database
|
|
366
|
+
*/
|
|
367
|
+
async clear(): Promise<RFDBResponse> {
|
|
368
|
+
return this._send('clear');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ===========================================================================
|
|
372
|
+
// Bulk Read Operations
|
|
373
|
+
// ===========================================================================
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Query nodes (async generator)
|
|
377
|
+
*/
|
|
378
|
+
async *queryNodes(query: AttrQuery): AsyncGenerator<WireNode, void, unknown> {
|
|
379
|
+
const serverQuery: Record<string, unknown> = {};
|
|
380
|
+
if (query.nodeType) serverQuery.nodeType = query.nodeType;
|
|
381
|
+
if (query.type) serverQuery.nodeType = query.type;
|
|
382
|
+
if (query.name) serverQuery.name = query.name;
|
|
383
|
+
if (query.file) serverQuery.file = query.file;
|
|
384
|
+
if (query.exported !== undefined) serverQuery.exported = query.exported;
|
|
385
|
+
|
|
386
|
+
const response = await this._send('queryNodes', { query: serverQuery });
|
|
387
|
+
const nodes = (response as { nodes?: WireNode[] }).nodes || [];
|
|
388
|
+
|
|
389
|
+
for (const node of nodes) {
|
|
390
|
+
yield node;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Get all nodes matching query
|
|
396
|
+
*/
|
|
397
|
+
async getAllNodes(query: AttrQuery = {}): Promise<WireNode[]> {
|
|
398
|
+
const nodes: WireNode[] = [];
|
|
399
|
+
for await (const node of this.queryNodes(query)) {
|
|
400
|
+
nodes.push(node);
|
|
401
|
+
}
|
|
402
|
+
return nodes;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Get all edges
|
|
407
|
+
*/
|
|
408
|
+
async getAllEdges(): Promise<WireEdge[]> {
|
|
409
|
+
const response = await this._send('getAllEdges');
|
|
410
|
+
return (response as { edges?: WireEdge[] }).edges || [];
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ===========================================================================
|
|
414
|
+
// Node Utility Methods
|
|
415
|
+
// ===========================================================================
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Check if node is an endpoint (has no outgoing edges)
|
|
419
|
+
*/
|
|
420
|
+
async isEndpoint(id: string): Promise<boolean> {
|
|
421
|
+
const response = await this._send('isEndpoint', { id: String(id) });
|
|
422
|
+
return (response as { value: boolean }).value;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Get node identifier string
|
|
427
|
+
*/
|
|
428
|
+
async getNodeIdentifier(id: string): Promise<string | null> {
|
|
429
|
+
const response = await this._send('getNodeIdentifier', { id: String(id) });
|
|
430
|
+
return (response as { identifier?: string | null }).identifier || null;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Update node version
|
|
435
|
+
*/
|
|
436
|
+
async updateNodeVersion(id: string, version: string): Promise<RFDBResponse> {
|
|
437
|
+
return this._send('updateNodeVersion', { id: String(id), version });
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// ===========================================================================
|
|
441
|
+
// Datalog API
|
|
442
|
+
// ===========================================================================
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Load Datalog rules
|
|
446
|
+
*/
|
|
447
|
+
async datalogLoadRules(source: string): Promise<number> {
|
|
448
|
+
const response = await this._send('datalogLoadRules', { source });
|
|
449
|
+
return (response as { count: number }).count;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Clear Datalog rules
|
|
454
|
+
*/
|
|
455
|
+
async datalogClearRules(): Promise<RFDBResponse> {
|
|
456
|
+
return this._send('datalogClearRules');
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Execute Datalog query
|
|
461
|
+
*/
|
|
462
|
+
async datalogQuery(query: string): Promise<DatalogResult[]> {
|
|
463
|
+
const response = await this._send('datalogQuery', { query });
|
|
464
|
+
return (response as { results?: DatalogResult[] }).results || [];
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Check a guarantee (Datalog rule) and return violations
|
|
469
|
+
*/
|
|
470
|
+
async checkGuarantee(ruleSource: string): Promise<DatalogResult[]> {
|
|
471
|
+
const response = await this._send('checkGuarantee', { ruleSource });
|
|
472
|
+
return (response as { violations?: DatalogResult[] }).violations || [];
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Ping the server
|
|
477
|
+
*/
|
|
478
|
+
async ping(): Promise<string | false> {
|
|
479
|
+
const response = await this._send('ping') as { pong?: boolean; version?: string };
|
|
480
|
+
return response.pong && response.version ? response.version : false;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Close connection
|
|
485
|
+
*/
|
|
486
|
+
async close(): Promise<void> {
|
|
487
|
+
if (this.socket) {
|
|
488
|
+
this.socket.destroy();
|
|
489
|
+
this.socket = null;
|
|
490
|
+
this.connected = false;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Shutdown the server
|
|
496
|
+
*/
|
|
497
|
+
async shutdown(): Promise<void> {
|
|
498
|
+
try {
|
|
499
|
+
await this._send('shutdown');
|
|
500
|
+
} catch {
|
|
501
|
+
// Expected - server closes connection
|
|
502
|
+
}
|
|
503
|
+
await this.close();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
export default RFDBClient;
|
package/ts/index.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @grafema/rfdb-client - High-performance graph database for code analysis
|
|
3
|
+
*
|
|
4
|
+
* This package provides:
|
|
5
|
+
* - RFDBClient: Socket-based client for out-of-process communication
|
|
6
|
+
* - Protocol types: Wire format types for RFDB communication
|
|
7
|
+
*
|
|
8
|
+
* For NAPI bindings (in-process), see the platform-specific packages.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Client
|
|
12
|
+
export { RFDBClient } from './client.js';
|
|
13
|
+
|
|
14
|
+
// Protocol types (re-exported from @grafema/types for convenience)
|
|
15
|
+
export type {
|
|
16
|
+
RFDBCommand,
|
|
17
|
+
WireNode,
|
|
18
|
+
WireEdge,
|
|
19
|
+
RFDBRequest,
|
|
20
|
+
RFDBResponse,
|
|
21
|
+
AttrQuery,
|
|
22
|
+
DatalogResult,
|
|
23
|
+
IRFDBClient,
|
|
24
|
+
} from './protocol.js';
|
package/ts/protocol.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFDB Protocol Types - re-export from @grafema/types
|
|
3
|
+
*
|
|
4
|
+
* This module provides wire format types for RFDB client-server communication.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type {
|
|
8
|
+
// Commands
|
|
9
|
+
RFDBCommand,
|
|
10
|
+
|
|
11
|
+
// Wire formats
|
|
12
|
+
WireNode,
|
|
13
|
+
WireEdge,
|
|
14
|
+
|
|
15
|
+
// Request types
|
|
16
|
+
RFDBRequest,
|
|
17
|
+
AddNodesRequest,
|
|
18
|
+
AddEdgesRequest,
|
|
19
|
+
DeleteNodeRequest,
|
|
20
|
+
DeleteEdgeRequest,
|
|
21
|
+
GetNodeRequest,
|
|
22
|
+
NodeExistsRequest,
|
|
23
|
+
FindByTypeRequest,
|
|
24
|
+
FindByAttrRequest,
|
|
25
|
+
NeighborsRequest,
|
|
26
|
+
BfsRequest,
|
|
27
|
+
GetOutgoingEdgesRequest,
|
|
28
|
+
GetIncomingEdgesRequest,
|
|
29
|
+
CountNodesByTypeRequest,
|
|
30
|
+
CountEdgesByTypeRequest,
|
|
31
|
+
|
|
32
|
+
// Response types
|
|
33
|
+
RFDBResponse,
|
|
34
|
+
AddNodesResponse,
|
|
35
|
+
AddEdgesResponse,
|
|
36
|
+
GetNodeResponse,
|
|
37
|
+
NodeExistsResponse,
|
|
38
|
+
FindByTypeResponse,
|
|
39
|
+
FindByAttrResponse,
|
|
40
|
+
NeighborsResponse,
|
|
41
|
+
BfsResponse,
|
|
42
|
+
GetEdgesResponse,
|
|
43
|
+
CountResponse,
|
|
44
|
+
CountsByTypeResponse,
|
|
45
|
+
PingResponse,
|
|
46
|
+
|
|
47
|
+
// Query types
|
|
48
|
+
AttrQuery,
|
|
49
|
+
DatalogBinding,
|
|
50
|
+
DatalogResult,
|
|
51
|
+
|
|
52
|
+
// Client interface
|
|
53
|
+
IRFDBClient,
|
|
54
|
+
} from '@grafema/types';
|