@grafema/util 0.3.18 → 0.3.20
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/federation/FederatedRouter.d.ts +124 -0
- package/dist/federation/FederatedRouter.d.ts.map +1 -0
- package/dist/federation/FederatedRouter.js +297 -0
- package/dist/federation/FederatedRouter.js.map +1 -0
- package/dist/federation/ShardDiscovery.d.ts +56 -0
- package/dist/federation/ShardDiscovery.d.ts.map +1 -0
- package/dist/federation/ShardDiscovery.js +100 -0
- package/dist/federation/ShardDiscovery.js.map +1 -0
- package/dist/federation/index.d.ts +28 -0
- package/dist/federation/index.d.ts.map +1 -0
- package/dist/federation/index.js +26 -0
- package/dist/federation/index.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/manifest/generator.d.ts.map +1 -1
- package/dist/manifest/generator.js +26 -4
- package/dist/manifest/generator.js.map +1 -1
- package/dist/manifest/index.d.ts +2 -0
- package/dist/manifest/index.d.ts.map +1 -1
- package/dist/manifest/index.js +1 -0
- package/dist/manifest/index.js.map +1 -1
- package/dist/manifest/registry.d.ts +116 -0
- package/dist/manifest/registry.d.ts.map +1 -0
- package/dist/manifest/registry.js +638 -0
- package/dist/manifest/registry.js.map +1 -0
- package/dist/manifest/resolver.d.ts +9 -0
- package/dist/manifest/resolver.d.ts.map +1 -1
- package/dist/manifest/resolver.js +31 -0
- package/dist/manifest/resolver.js.map +1 -1
- package/dist/notation/traceRenderer.d.ts +2 -0
- package/dist/notation/traceRenderer.d.ts.map +1 -1
- package/dist/notation/traceRenderer.js +6 -5
- package/dist/notation/traceRenderer.js.map +1 -1
- package/package.json +3 -3
- package/src/federation/FederatedRouter.ts +440 -0
- package/src/federation/ShardDiscovery.ts +130 -0
- package/src/federation/index.ts +35 -0
- package/src/index.ts +16 -1
- package/src/manifest/generator.ts +25 -4
- package/src/manifest/index.ts +2 -0
- package/src/manifest/registry.ts +769 -0
- package/src/manifest/resolver.ts +33 -0
- package/src/notation/traceRenderer.ts +8 -5
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShardDiscovery — discovers registered RFDB shards via /tmp/rfdb-shards/.
|
|
3
|
+
*
|
|
4
|
+
* Each rfdb-server running with --federate writes a JSON registration file
|
|
5
|
+
* containing its root path, socket, port, and PID. ShardDiscovery scans
|
|
6
|
+
* these files and resolves file paths to the appropriate shard.
|
|
7
|
+
*
|
|
8
|
+
* Discovery is by file path prefix: if a target file's absolute path
|
|
9
|
+
* starts with a shard's root, that shard owns the file.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readFileSync, readdirSync, existsSync } from 'fs';
|
|
13
|
+
import { join } from 'path';
|
|
14
|
+
|
|
15
|
+
export interface ShardRegistration {
|
|
16
|
+
/** Absolute path of the project root this shard covers */
|
|
17
|
+
root: string;
|
|
18
|
+
/** Unix socket path for this shard */
|
|
19
|
+
socket: string;
|
|
20
|
+
/** WebSocket port (if available) */
|
|
21
|
+
wsPort?: number;
|
|
22
|
+
/** Process ID of the rfdb-server */
|
|
23
|
+
pid: number;
|
|
24
|
+
/** Epoch timestamp when the server started */
|
|
25
|
+
started: number;
|
|
26
|
+
/** Server version */
|
|
27
|
+
serverVersion: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const SHARDS_DIR = '/tmp/rfdb-shards';
|
|
31
|
+
|
|
32
|
+
export class ShardDiscovery {
|
|
33
|
+
/** root path → registration (sorted by longest root first for prefix matching) */
|
|
34
|
+
private shards = new Map<string, ShardRegistration>();
|
|
35
|
+
|
|
36
|
+
/** Number of discovered shards */
|
|
37
|
+
get size(): number {
|
|
38
|
+
return this.shards.size;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** All registered shard roots */
|
|
42
|
+
get roots(): string[] {
|
|
43
|
+
return [...this.shards.keys()];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Scan /tmp/rfdb-shards/ for registered shards.
|
|
48
|
+
* Validates each registration: checks PID is alive, parses JSON.
|
|
49
|
+
* Call this periodically or before federated queries.
|
|
50
|
+
*/
|
|
51
|
+
scan(): number {
|
|
52
|
+
this.shards.clear();
|
|
53
|
+
|
|
54
|
+
if (!existsSync(SHARDS_DIR)) {
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const files = readdirSync(SHARDS_DIR).filter(f => f.endsWith('.json'));
|
|
59
|
+
|
|
60
|
+
for (const file of files) {
|
|
61
|
+
try {
|
|
62
|
+
const raw = readFileSync(join(SHARDS_DIR, file), 'utf-8');
|
|
63
|
+
const reg = JSON.parse(raw) as ShardRegistration;
|
|
64
|
+
|
|
65
|
+
if (!reg.root || !reg.socket || !reg.pid) continue;
|
|
66
|
+
|
|
67
|
+
// Check if process is still alive
|
|
68
|
+
if (!isProcessAlive(reg.pid)) continue;
|
|
69
|
+
|
|
70
|
+
// Normalize root path (ensure no trailing slash)
|
|
71
|
+
const root = reg.root.replace(/\/$/, '');
|
|
72
|
+
this.shards.set(root, { ...reg, root });
|
|
73
|
+
} catch {
|
|
74
|
+
// Skip malformed registration files
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return this.shards.size;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Find the shard that owns a given absolute file path.
|
|
83
|
+
* Uses longest-prefix matching: /repo/packages/api/ wins over /repo/.
|
|
84
|
+
*/
|
|
85
|
+
resolve(absoluteFilePath: string): ShardRegistration | null {
|
|
86
|
+
let bestMatch: ShardRegistration | null = null;
|
|
87
|
+
let bestLength = 0;
|
|
88
|
+
|
|
89
|
+
for (const [root, reg] of this.shards) {
|
|
90
|
+
if (absoluteFilePath.startsWith(root) && root.length > bestLength) {
|
|
91
|
+
bestMatch = reg;
|
|
92
|
+
bestLength = root.length;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return bestMatch;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get all known shards (for scatter-gather queries).
|
|
101
|
+
*/
|
|
102
|
+
all(): ShardRegistration[] {
|
|
103
|
+
return [...this.shards.values()];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Register a shard programmatically (for testing or remote shards).
|
|
108
|
+
*/
|
|
109
|
+
register(reg: ShardRegistration): void {
|
|
110
|
+
const root = reg.root.replace(/\/$/, '');
|
|
111
|
+
this.shards.set(root, { ...reg, root });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Remove a shard by root path.
|
|
116
|
+
*/
|
|
117
|
+
unregister(root: string): void {
|
|
118
|
+
this.shards.delete(root.replace(/\/$/, ''));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Check if a process with given PID is alive */
|
|
123
|
+
function isProcessAlive(pid: number): boolean {
|
|
124
|
+
try {
|
|
125
|
+
process.kill(pid, 0);
|
|
126
|
+
return true;
|
|
127
|
+
} catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Federation module — cross-shard query routing for RFDB.
|
|
3
|
+
*
|
|
4
|
+
* Enables multiple RFDB shards to discover each other and resolve
|
|
5
|
+
* cross-shard references at query time. Within a shard, the graph
|
|
6
|
+
* is fully materialized (analysis-time). Between shards, references
|
|
7
|
+
* are resolved lazily (execution-time) via the SUBGRAPH primitive.
|
|
8
|
+
*
|
|
9
|
+
* ## Architecture
|
|
10
|
+
*
|
|
11
|
+
* - ShardDiscovery: scans /tmp/rfdb-shards/ for registered shards
|
|
12
|
+
* - FederatedRouter: orchestrates cross-shard traversal and scatter-gather
|
|
13
|
+
* - Each shard is a separate rfdb-server process with its own graph
|
|
14
|
+
* - Discovery is by file path prefix matching (zero-config)
|
|
15
|
+
*
|
|
16
|
+
* ## Usage
|
|
17
|
+
*
|
|
18
|
+
* const discovery = new ShardDiscovery();
|
|
19
|
+
* await discovery.scan();
|
|
20
|
+
*
|
|
21
|
+
* const router = new FederatedRouter(discovery, localClient);
|
|
22
|
+
* const result = await router.traceDataflow('src/app.ts->userInput', 'forward', 10);
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export { ShardDiscovery } from './ShardDiscovery.js';
|
|
26
|
+
export type { ShardRegistration } from './ShardDiscovery.js';
|
|
27
|
+
|
|
28
|
+
export { FederatedRouter } from './FederatedRouter.js';
|
|
29
|
+
export type {
|
|
30
|
+
FederatedTraceResult,
|
|
31
|
+
FederatedTraceHop,
|
|
32
|
+
FrontierEdge,
|
|
33
|
+
SubgraphResponse,
|
|
34
|
+
ManifestResolvedNode,
|
|
35
|
+
} from './FederatedRouter.js';
|
package/src/index.ts
CHANGED
|
@@ -257,8 +257,19 @@ export type {
|
|
|
257
257
|
TraceNarrativeOptions,
|
|
258
258
|
} from './notation/index.js';
|
|
259
259
|
|
|
260
|
+
// Federation — cross-shard query routing
|
|
261
|
+
export { ShardDiscovery, FederatedRouter } from './federation/index.js';
|
|
262
|
+
export type {
|
|
263
|
+
ShardRegistration,
|
|
264
|
+
FederatedTraceResult,
|
|
265
|
+
FederatedTraceHop,
|
|
266
|
+
FrontierEdge,
|
|
267
|
+
SubgraphResponse,
|
|
268
|
+
ManifestResolvedNode,
|
|
269
|
+
} from './federation/index.js';
|
|
270
|
+
|
|
260
271
|
// Manifest generation & resolution (federation)
|
|
261
|
-
export { ManifestGenerator, ManifestResolver } from './manifest/index.js';
|
|
272
|
+
export { ManifestGenerator, ManifestResolver, RegistryBuilder, resolvePackageDir, detectSourceType, resolveEntryPoint } from './manifest/index.js';
|
|
262
273
|
export type {
|
|
263
274
|
Manifest,
|
|
264
275
|
ManifestExport,
|
|
@@ -269,6 +280,10 @@ export type {
|
|
|
269
280
|
ExportKind,
|
|
270
281
|
ResolveResult,
|
|
271
282
|
ManifestSummary,
|
|
283
|
+
RegistryEntry,
|
|
284
|
+
RegistryIndex,
|
|
285
|
+
RegistryBuilderOptions,
|
|
286
|
+
BuildResult,
|
|
272
287
|
} from './manifest/index.js';
|
|
273
288
|
|
|
274
289
|
// Re-export types for convenience
|
|
@@ -174,9 +174,13 @@ export class ManifestGenerator {
|
|
|
174
174
|
// Graph-based approach:
|
|
175
175
|
// EXPORT(named) --EXPORTS--> EXPORT_BINDING(name, source) --> definition in source file
|
|
176
176
|
await this.collectExportsViaBindings(entryFile, exports, seen, new Set());
|
|
177
|
-
}
|
|
178
|
-
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Fallback: if entry-file mode found 0 exports (e.g., CJS barrel,
|
|
180
|
+
// broken re-export chain in compiled_js), scan all exported definitions
|
|
181
|
+
if (exports.length === 0) {
|
|
179
182
|
const prefix = this.options.packagePrefix ?? '';
|
|
183
|
+
// Check standard definition types (FUNCTION, CLASS, CONSTANT, INTERFACE)
|
|
180
184
|
for (const type of ManifestGenerator.DEF_TYPES) {
|
|
181
185
|
for await (const node of this.backend.queryNodes({ type: type as never })) {
|
|
182
186
|
if (prefix && !node.file?.startsWith(prefix)) continue;
|
|
@@ -187,6 +191,21 @@ export class ManifestGenerator {
|
|
|
187
191
|
await this.addExportFromDefinition(node, exports);
|
|
188
192
|
}
|
|
189
193
|
}
|
|
194
|
+
// Also check EXPORT_BINDING nodes (from CJS exports.foo = ...)
|
|
195
|
+
if (exports.length === 0) {
|
|
196
|
+
for await (const node of this.backend.queryNodes({ type: 'EXPORT_BINDING' as never })) {
|
|
197
|
+
if (prefix && !node.file?.startsWith(prefix)) continue;
|
|
198
|
+
if (!node.name || node.name === 'named' || node.name === 'default') continue;
|
|
199
|
+
if (seen.has(node.name)) continue;
|
|
200
|
+
seen.add(node.name);
|
|
201
|
+
exports.push({
|
|
202
|
+
name: node.name,
|
|
203
|
+
kind: 'VARIABLE',
|
|
204
|
+
semanticId: node.id,
|
|
205
|
+
effects: ['UNKNOWN'],
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
190
209
|
}
|
|
191
210
|
|
|
192
211
|
exports.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -276,7 +295,8 @@ export class ManifestGenerator {
|
|
|
276
295
|
const dir = fromFile.substring(0, fromFile.lastIndexOf('/'));
|
|
277
296
|
let resolved = source.startsWith('.') ? `${dir}/${source}` : source;
|
|
278
297
|
resolved = resolved.replace(/\/\.\//g, '/');
|
|
279
|
-
|
|
298
|
+
// Only replace .js → .ts for source TypeScript code, not compiled_js
|
|
299
|
+
if (this.options.sourceType !== 'compiled_js' && resolved.endsWith('.js')) {
|
|
280
300
|
resolved = resolved.replace(/\.js$/, '.ts');
|
|
281
301
|
}
|
|
282
302
|
return resolved;
|
|
@@ -330,7 +350,8 @@ export class ManifestGenerator {
|
|
|
330
350
|
/** Enrich all exports with computed effects (transitive call graph analysis) */
|
|
331
351
|
private async enrichEffects(exports: ManifestExport[]): Promise<void> {
|
|
332
352
|
for (const entry of exports) {
|
|
333
|
-
|
|
353
|
+
// Enrich FUNCTION, CLASS, and VARIABLE (CJS exports may be VARIABLE kind)
|
|
354
|
+
if (entry.kind !== 'FUNCTION' && entry.kind !== 'CLASS' && entry.kind !== 'VARIABLE') continue;
|
|
334
355
|
|
|
335
356
|
const effects = new Set<EffectType>();
|
|
336
357
|
const visited = new Set<string>();
|
package/src/manifest/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { ManifestGenerator } from './generator.js';
|
|
2
2
|
export { ManifestResolver } from './resolver.js';
|
|
3
3
|
export type { ResolveResult, ManifestSummary } from './resolver.js';
|
|
4
|
+
export { RegistryBuilder, resolvePackageDir, detectSourceType, resolveEntryPoint } from './registry.js';
|
|
5
|
+
export type { RegistryEntry, RegistryIndex, RegistryBuilderOptions, BuildResult } from './registry.js';
|
|
4
6
|
export type {
|
|
5
7
|
Manifest,
|
|
6
8
|
ManifestExport,
|