@grafema/core 0.2.10 → 0.2.12-beta
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/Orchestrator.d.ts +13 -0
- package/dist/Orchestrator.d.ts.map +1 -1
- package/dist/Orchestrator.js +84 -2
- package/dist/Orchestrator.js.map +1 -1
- package/dist/ParallelAnalysisRunner.d.ts.map +1 -1
- package/dist/ParallelAnalysisRunner.js +28 -41
- package/dist/ParallelAnalysisRunner.js.map +1 -1
- package/dist/PhaseRunner.d.ts +2 -0
- package/dist/PhaseRunner.d.ts.map +1 -1
- package/dist/PhaseRunner.js +8 -3
- package/dist/PhaseRunner.js.map +1 -1
- package/dist/core/IncrementalReanalyzer.d.ts +3 -3
- package/dist/core/IncrementalReanalyzer.d.ts.map +1 -1
- package/dist/core/IncrementalReanalyzer.js +12 -12
- package/dist/core/IncrementalReanalyzer.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/plugins/analysis/JSASTAnalyzer.d.ts.map +1 -1
- package/dist/plugins/analysis/JSASTAnalyzer.js +35 -3
- package/dist/plugins/analysis/JSASTAnalyzer.js.map +1 -1
- package/dist/plugins/analysis/ast/GraphBuilder.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/GraphBuilder.js +8 -0
- package/dist/plugins/analysis/ast/GraphBuilder.js.map +1 -1
- package/dist/plugins/analysis/ast/builders/ModuleRuntimeBuilder.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/builders/ModuleRuntimeBuilder.js +4 -2
- package/dist/plugins/analysis/ast/builders/ModuleRuntimeBuilder.js.map +1 -1
- package/dist/plugins/analysis/ast/handlers/NewExpressionHandler.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/handlers/NewExpressionHandler.js +2 -1
- package/dist/plugins/analysis/ast/handlers/NewExpressionHandler.js.map +1 -1
- package/dist/plugins/analysis/ast/types.d.ts +3 -0
- package/dist/plugins/analysis/ast/types.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.d.ts.map +1 -1
- package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.js +6 -2
- package/dist/plugins/analysis/ast/visitors/ImportExportVisitor.js.map +1 -1
- package/dist/plugins/enrichment/ImportExportLinker.d.ts.map +1 -1
- package/dist/plugins/enrichment/ImportExportLinker.js +15 -5
- package/dist/plugins/enrichment/ImportExportLinker.js.map +1 -1
- package/dist/storage/backends/RFDBServerBackend.d.ts +21 -7
- package/dist/storage/backends/RFDBServerBackend.d.ts.map +1 -1
- package/dist/storage/backends/RFDBServerBackend.js +48 -48
- package/dist/storage/backends/RFDBServerBackend.js.map +1 -1
- package/dist/utils/startRfdbServer.d.ts +44 -0
- package/dist/utils/startRfdbServer.d.ts.map +1 -0
- package/dist/utils/startRfdbServer.js +79 -0
- package/dist/utils/startRfdbServer.js.map +1 -0
- package/package.json +3 -3
- package/src/Orchestrator.ts +91 -2
- package/src/ParallelAnalysisRunner.ts +30 -48
- package/src/PhaseRunner.ts +10 -3
- package/src/core/IncrementalReanalyzer.ts +15 -15
- package/src/index.ts +4 -0
- package/src/plugins/analysis/JSASTAnalyzer.ts +40 -3
- package/src/plugins/analysis/ast/GraphBuilder.ts +9 -0
- package/src/plugins/analysis/ast/builders/ModuleRuntimeBuilder.ts +4 -2
- package/src/plugins/analysis/ast/handlers/NewExpressionHandler.ts +2 -1
- package/src/plugins/analysis/ast/types.ts +3 -0
- package/src/plugins/analysis/ast/visitors/ImportExportVisitor.ts +8 -2
- package/src/plugins/enrichment/ImportExportLinker.ts +15 -5
- package/src/storage/backends/RFDBServerBackend.ts +52 -60
- package/src/utils/startRfdbServer.ts +126 -0
|
@@ -18,15 +18,14 @@
|
|
|
18
18
|
* await backend.flush();
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import { RFDBClient } from '@grafema/rfdb-client';
|
|
22
|
-
import {
|
|
23
|
-
import { spawn, type ChildProcess } from 'child_process';
|
|
21
|
+
import { RFDBClient, type BatchHandle } from '@grafema/rfdb-client';
|
|
22
|
+
import type { ChildProcess } from 'child_process';
|
|
24
23
|
import { join, dirname } from 'path';
|
|
25
|
-
import { setTimeout as sleep } from 'timers/promises';
|
|
26
24
|
|
|
27
25
|
import type { WireNode, WireEdge, FieldDeclaration, CommitDelta, AttrQuery as RFDBAttrQuery } from '@grafema/types';
|
|
28
26
|
import type { NodeType, EdgeType } from '@grafema/types';
|
|
29
|
-
import {
|
|
27
|
+
import { startRfdbServer } from '../../utils/startRfdbServer.js';
|
|
28
|
+
import { GRAFEMA_VERSION, getSchemaVersion } from '../../version.js';
|
|
30
29
|
import type { BaseNodeRecord, EdgeRecord, AnyBrandedNode } from '@grafema/types';
|
|
31
30
|
import { brandNodeInternal } from '../../core/brandNodeInternal.js';
|
|
32
31
|
import type { AttrQuery, GraphStats, GraphExport } from '../../core/GraphBackend.js';
|
|
@@ -201,66 +200,19 @@ export class RFDBServerBackend {
|
|
|
201
200
|
}
|
|
202
201
|
|
|
203
202
|
/**
|
|
204
|
-
*
|
|
205
|
-
* Delegates to findRfdbBinary() for consistent search across all entry points.
|
|
206
|
-
*/
|
|
207
|
-
private _findServerBinary(): string | null {
|
|
208
|
-
const binaryPath = findRfdbBinary();
|
|
209
|
-
if (binaryPath) {
|
|
210
|
-
this.log(`[RFDBServerBackend] Found binary: ${binaryPath}`);
|
|
211
|
-
}
|
|
212
|
-
return binaryPath;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Start RFDB server process
|
|
203
|
+
* Start RFDB server process using shared utility.
|
|
217
204
|
*/
|
|
218
205
|
private async _startServer(): Promise<void> {
|
|
219
206
|
if (!this.dbPath) {
|
|
220
207
|
throw new Error('dbPath required to start RFDB server');
|
|
221
208
|
}
|
|
222
209
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
'Install @grafema/rfdb: npm install @grafema/rfdb\n' +
|
|
229
|
-
'Or build from source: cargo build --release --bin rfdb-server'
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Remove stale socket
|
|
234
|
-
if (existsSync(this.socketPath)) {
|
|
235
|
-
unlinkSync(this.socketPath);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
this.log(`[RFDBServerBackend] Starting: ${binaryPath} ${this.dbPath} --socket ${this.socketPath}`);
|
|
239
|
-
|
|
240
|
-
this.serverProcess = spawn(binaryPath, [this.dbPath, '--socket', this.socketPath], {
|
|
241
|
-
stdio: ['ignore', 'ignore', 'inherit'], // stdin/stdout ignored, stderr inherited
|
|
242
|
-
detached: true, // Allow server to outlive this process
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
// Don't let server process prevent parent from exiting
|
|
246
|
-
this.serverProcess.unref();
|
|
247
|
-
|
|
248
|
-
this.serverProcess.on('error', (err: Error) => {
|
|
249
|
-
this.logError(`[RFDBServerBackend] Server process error:`, err);
|
|
210
|
+
await startRfdbServer({
|
|
211
|
+
dbPath: this.dbPath,
|
|
212
|
+
socketPath: this.socketPath,
|
|
213
|
+
waitTimeoutMs: 5000,
|
|
214
|
+
logger: this.silent ? undefined : { debug: (m: string) => this.log(m) },
|
|
250
215
|
});
|
|
251
|
-
|
|
252
|
-
// Wait for socket to appear
|
|
253
|
-
let attempts = 0;
|
|
254
|
-
while (!existsSync(this.socketPath) && attempts < 50) {
|
|
255
|
-
await sleep(100);
|
|
256
|
-
attempts++;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (!existsSync(this.socketPath)) {
|
|
260
|
-
throw new Error(`RFDB server failed to start (socket not created after ${attempts * 100}ms)`);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
this.log(`[RFDBServerBackend] Server started on ${this.socketPath}`);
|
|
264
216
|
}
|
|
265
217
|
|
|
266
218
|
/**
|
|
@@ -274,9 +226,28 @@ export class RFDBServerBackend {
|
|
|
274
226
|
try {
|
|
275
227
|
const hello = await this.client.hello(3);
|
|
276
228
|
this.protocolVersion = hello.protocolVersion;
|
|
229
|
+
this._checkServerVersion(hello.serverVersion);
|
|
277
230
|
} catch {
|
|
278
231
|
// Server predates hello command or doesn't support v3 — safe v2 fallback
|
|
279
232
|
this.protocolVersion = 2;
|
|
233
|
+
this.log('[RFDBServerBackend] WARNING: Server does not support version negotiation. Consider updating rfdb-server.');
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Validate server version against expected client version.
|
|
239
|
+
* Warns on mismatch but never fails — version differences are informational.
|
|
240
|
+
*/
|
|
241
|
+
private _checkServerVersion(serverVersion: string): void {
|
|
242
|
+
if (!serverVersion) return;
|
|
243
|
+
const expected = getSchemaVersion(GRAFEMA_VERSION);
|
|
244
|
+
const actual = getSchemaVersion(serverVersion);
|
|
245
|
+
if (actual !== expected) {
|
|
246
|
+
this.log(
|
|
247
|
+
`[RFDBServerBackend] WARNING: rfdb-server version mismatch — ` +
|
|
248
|
+
`server v${serverVersion}, expected v${GRAFEMA_VERSION}. ` +
|
|
249
|
+
`Update with: grafema server restart`
|
|
250
|
+
);
|
|
280
251
|
}
|
|
281
252
|
}
|
|
282
253
|
|
|
@@ -769,10 +740,13 @@ export class RFDBServerBackend {
|
|
|
769
740
|
/**
|
|
770
741
|
* Commit the current batch to the server atomically.
|
|
771
742
|
* Returns a CommitDelta describing what changed.
|
|
743
|
+
*
|
|
744
|
+
* @param tags - Optional tags for the commit
|
|
745
|
+
* @param deferIndex - When true, server writes data but skips index rebuild.
|
|
772
746
|
*/
|
|
773
|
-
async commitBatch(tags?: string[]): Promise<CommitDelta> {
|
|
747
|
+
async commitBatch(tags?: string[], deferIndex?: boolean, protectedTypes?: string[]): Promise<CommitDelta> {
|
|
774
748
|
if (!this.client) throw new Error('Not connected to RFDB server');
|
|
775
|
-
return this.client.commitBatch(tags);
|
|
749
|
+
return this.client.commitBatch(tags, deferIndex, protectedTypes);
|
|
776
750
|
}
|
|
777
751
|
|
|
778
752
|
/**
|
|
@@ -822,6 +796,24 @@ export class RFDBServerBackend {
|
|
|
822
796
|
this.client.abortBatch();
|
|
823
797
|
}
|
|
824
798
|
|
|
799
|
+
/**
|
|
800
|
+
* Rebuild all secondary indexes after deferred-index commits (REG-487).
|
|
801
|
+
* Call this once after a series of commitBatch(tags, true) calls.
|
|
802
|
+
*/
|
|
803
|
+
async rebuildIndexes(): Promise<void> {
|
|
804
|
+
if (!this.client) throw new Error('Not connected to RFDB server');
|
|
805
|
+
await this.client.rebuildIndexes();
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* Create an isolated batch handle for concurrent-safe batching (REG-487).
|
|
810
|
+
* Each handle has its own buffers — safe for parallel workers.
|
|
811
|
+
*/
|
|
812
|
+
createBatch(): BatchHandle {
|
|
813
|
+
if (!this.client) throw new Error('Not connected to RFDB server');
|
|
814
|
+
return this.client.createBatch();
|
|
815
|
+
}
|
|
816
|
+
|
|
825
817
|
// ===========================================================================
|
|
826
818
|
// Export/Import
|
|
827
819
|
// ===========================================================================
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utility for starting rfdb-server
|
|
3
|
+
*
|
|
4
|
+
* Single authoritative function for spawning rfdb-server. All spawn sites
|
|
5
|
+
* (RFDBServerBackend, CLI server command, ParallelAnalysisRunner) delegate here.
|
|
6
|
+
*
|
|
7
|
+
* Callers are responsible for checking if a server is already running before
|
|
8
|
+
* calling this function. This function always spawns a new server process.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, unlinkSync, writeFileSync } from 'fs';
|
|
12
|
+
import { dirname } from 'path';
|
|
13
|
+
import { spawn, type ChildProcess } from 'child_process';
|
|
14
|
+
import { setTimeout as sleep } from 'timers/promises';
|
|
15
|
+
import { findRfdbBinary } from './findRfdbBinary.js';
|
|
16
|
+
|
|
17
|
+
export interface StartRfdbServerOptions {
|
|
18
|
+
dbPath: string;
|
|
19
|
+
socketPath: string;
|
|
20
|
+
/** Override binary path; if absent, findRfdbBinary() is called */
|
|
21
|
+
binaryPath?: string;
|
|
22
|
+
/** If provided, PID file is written after spawn */
|
|
23
|
+
pidPath?: string;
|
|
24
|
+
/** Socket poll timeout in ms (default: 5000) */
|
|
25
|
+
waitTimeoutMs?: number;
|
|
26
|
+
/** Optional logger for debug messages */
|
|
27
|
+
logger?: { debug(msg: string): void };
|
|
28
|
+
/** Internal: dependency injection for testing */
|
|
29
|
+
_deps?: {
|
|
30
|
+
spawn?: typeof spawn;
|
|
31
|
+
findRfdbBinary?: () => string | null;
|
|
32
|
+
existsSync?: (path: string) => boolean;
|
|
33
|
+
unlinkSync?: (path: string) => void;
|
|
34
|
+
writeFileSync?: (path: string, data: string) => void;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Start an rfdb-server process.
|
|
40
|
+
*
|
|
41
|
+
* 1. Resolve binary (explicit or via findRfdbBinary)
|
|
42
|
+
* 2. Remove stale socket
|
|
43
|
+
* 3. Spawn detached process
|
|
44
|
+
* 4. Write PID file (if pidPath provided)
|
|
45
|
+
* 5. Poll for socket file up to waitTimeoutMs
|
|
46
|
+
* 6. Return ChildProcess (caller decides whether to kill later)
|
|
47
|
+
*/
|
|
48
|
+
export async function startRfdbServer(options: StartRfdbServerOptions): Promise<ChildProcess> {
|
|
49
|
+
const {
|
|
50
|
+
dbPath,
|
|
51
|
+
socketPath,
|
|
52
|
+
pidPath,
|
|
53
|
+
waitTimeoutMs = 5000,
|
|
54
|
+
logger,
|
|
55
|
+
_deps,
|
|
56
|
+
} = options;
|
|
57
|
+
|
|
58
|
+
const _spawn = _deps?.spawn ?? spawn;
|
|
59
|
+
const _findRfdbBinary = _deps?.findRfdbBinary ?? findRfdbBinary;
|
|
60
|
+
const _existsSync = _deps?.existsSync ?? existsSync;
|
|
61
|
+
const _unlinkSync = _deps?.unlinkSync ?? unlinkSync;
|
|
62
|
+
const _writeFileSync = _deps?.writeFileSync ?? writeFileSync;
|
|
63
|
+
|
|
64
|
+
// 1. Resolve binary
|
|
65
|
+
const binaryPath = options.binaryPath || _findRfdbBinary();
|
|
66
|
+
if (!binaryPath) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
'RFDB server binary not found.\n' +
|
|
69
|
+
'Install @grafema/rfdb: npm install @grafema/rfdb\n' +
|
|
70
|
+
'Or build from source: cargo build --release --bin rfdb-server'
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 2. Remove stale socket
|
|
75
|
+
if (_existsSync(socketPath)) {
|
|
76
|
+
_unlinkSync(socketPath);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const dataDir = dirname(socketPath);
|
|
80
|
+
logger?.debug(`Starting rfdb-server: ${binaryPath} ${dbPath} --socket ${socketPath} --data-dir ${dataDir}`);
|
|
81
|
+
|
|
82
|
+
// 3. Spawn server (detached, survives parent exit)
|
|
83
|
+
// Mutable container to capture async spawn errors (Dijkstra amendment B)
|
|
84
|
+
const state = { spawnError: null as Error | null };
|
|
85
|
+
|
|
86
|
+
const serverProcess = _spawn(binaryPath, [dbPath, '--socket', socketPath, '--data-dir', dataDir], {
|
|
87
|
+
stdio: ['ignore', 'ignore', 'inherit'],
|
|
88
|
+
detached: true,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
serverProcess.unref();
|
|
92
|
+
|
|
93
|
+
// Wire error handler to capture ENOENT and other spawn failures
|
|
94
|
+
serverProcess.on('error', (err: Error) => {
|
|
95
|
+
state.spawnError = err;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// 4. Write PID file if requested and pid is available
|
|
99
|
+
if (pidPath && serverProcess.pid) {
|
|
100
|
+
_writeFileSync(pidPath, String(serverProcess.pid));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 5. Poll for socket file
|
|
104
|
+
const maxAttempts = Math.ceil(waitTimeoutMs / 100);
|
|
105
|
+
let attempts = 0;
|
|
106
|
+
while (!_existsSync(socketPath) && attempts < maxAttempts) {
|
|
107
|
+
if (state.spawnError) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`RFDB server failed to start: ${state.spawnError.message} — check binary: ${binaryPath}`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
await sleep(100);
|
|
113
|
+
attempts++;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 6. Final check
|
|
117
|
+
if (!_existsSync(socketPath)) {
|
|
118
|
+
const detail = state.spawnError ? `: ${state.spawnError.message}` : '';
|
|
119
|
+
throw new Error(
|
|
120
|
+
`RFDB server failed to start after ${waitTimeoutMs}ms${detail} — check binary: ${binaryPath}`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
logger?.debug(`rfdb-server started on ${socketPath}`);
|
|
125
|
+
return serverProcess;
|
|
126
|
+
}
|