@grafema/util 0.3.21 → 0.3.23

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.
Files changed (56) hide show
  1. package/dist/config/ConfigLoader.d.ts +7 -3
  2. package/dist/config/ConfigLoader.d.ts.map +1 -1
  3. package/dist/config/ConfigLoader.js +23 -9
  4. package/dist/config/ConfigLoader.js.map +1 -1
  5. package/dist/index.d.ts +5 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +3 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/manifest/generator.d.ts +4 -0
  10. package/dist/manifest/generator.d.ts.map +1 -1
  11. package/dist/manifest/generator.js +27 -7
  12. package/dist/manifest/generator.js.map +1 -1
  13. package/dist/manifest/types.d.ts +1 -1
  14. package/dist/manifest/types.d.ts.map +1 -1
  15. package/dist/notation/archetypes.d.ts.map +1 -1
  16. package/dist/notation/archetypes.js +1 -0
  17. package/dist/notation/archetypes.js.map +1 -1
  18. package/dist/queries/findCallsInFunction.d.ts +2 -1
  19. package/dist/queries/findCallsInFunction.d.ts.map +1 -1
  20. package/dist/queries/findCallsInFunction.js +11 -5
  21. package/dist/queries/findCallsInFunction.js.map +1 -1
  22. package/dist/queries/getShape.d.ts +65 -0
  23. package/dist/queries/getShape.d.ts.map +1 -0
  24. package/dist/queries/getShape.js +170 -0
  25. package/dist/queries/getShape.js.map +1 -0
  26. package/dist/queries/index.d.ts +3 -0
  27. package/dist/queries/index.d.ts.map +1 -1
  28. package/dist/queries/index.js +2 -0
  29. package/dist/queries/index.js.map +1 -1
  30. package/dist/queries/traceCallChain.d.ts +77 -0
  31. package/dist/queries/traceCallChain.d.ts.map +1 -0
  32. package/dist/queries/traceCallChain.js +235 -0
  33. package/dist/queries/traceCallChain.js.map +1 -0
  34. package/dist/queries/types.d.ts +2 -0
  35. package/dist/queries/types.d.ts.map +1 -1
  36. package/dist/storage/backends/RFDBServerBackend.d.ts +5 -0
  37. package/dist/storage/backends/RFDBServerBackend.d.ts.map +1 -1
  38. package/dist/storage/backends/RFDBServerBackend.js +70 -2
  39. package/dist/storage/backends/RFDBServerBackend.js.map +1 -1
  40. package/dist/version.d.ts +22 -0
  41. package/dist/version.d.ts.map +1 -1
  42. package/dist/version.js +34 -0
  43. package/dist/version.js.map +1 -1
  44. package/package.json +3 -3
  45. package/src/config/ConfigLoader.ts +28 -9
  46. package/src/index.ts +5 -1
  47. package/src/manifest/generator.ts +46 -10
  48. package/src/manifest/types.ts +2 -1
  49. package/src/notation/archetypes.ts +1 -0
  50. package/src/queries/findCallsInFunction.ts +15 -7
  51. package/src/queries/getShape.ts +241 -0
  52. package/src/queries/index.ts +3 -0
  53. package/src/queries/traceCallChain.ts +341 -0
  54. package/src/queries/types.ts +2 -0
  55. package/src/storage/backends/RFDBServerBackend.ts +81 -3
  56. package/src/version.ts +39 -0
@@ -21,6 +21,7 @@
21
21
  import { RFDBClient, type BatchHandle } from '@grafema/rfdb-client';
22
22
  import type { ChildProcess } from 'child_process';
23
23
  import { join, dirname } from 'path';
24
+ import { createHash } from 'crypto';
24
25
 
25
26
  import type { WireNode, WireEdge, FieldDeclaration, CommitDelta, AttrQuery as RFDBAttrQuery, DatalogExplainResult, ServerStats, CypherResult } from '@grafema/types';
26
27
  import type { NodeType, EdgeType } from '@grafema/types';
@@ -119,11 +120,21 @@ export class RFDBServerBackend {
119
120
  this.silent = options.silent ?? false;
120
121
  this._clientName = options.clientName ?? 'core';
121
122
  // Default socket path: next to the database in .grafema folder
122
- // This ensures each project has its own socket, avoiding conflicts
123
+ // This ensures each project has its own socket, avoiding conflicts.
124
+ // Unix socket paths have a hard limit (SUN_LEN: 104 on macOS, 108 on Linux).
125
+ // If the project path is too deep, fall back to /tmp with a hash to avoid conflicts.
123
126
  if (options.socketPath) {
124
127
  this.socketPath = options.socketPath;
125
128
  } else if (this.dbPath) {
126
- this.socketPath = join(dirname(this.dbPath), 'rfdb.sock');
129
+ const localSocket = join(dirname(this.dbPath), 'rfdb.sock');
130
+ const SUN_LEN = process.platform === 'darwin' ? 104 : 108;
131
+ if (Buffer.byteLength(localSocket) < SUN_LEN) {
132
+ this.socketPath = localSocket;
133
+ } else {
134
+ // Hash the project path to create a unique but short socket path
135
+ const hash = createHash('md5').update(dirname(this.dbPath)).digest('hex').slice(0, 12);
136
+ this.socketPath = join('/tmp', `grafema-${hash}.sock`);
137
+ }
127
138
  } else {
128
139
  this.socketPath = '/tmp/rfdb.sock'; // fallback, not recommended
129
140
  }
@@ -283,6 +294,22 @@ export class RFDBServerBackend {
283
294
  this.serverProcess = null;
284
295
  }
285
296
 
297
+ /**
298
+ * Shutdown the RFDB server gracefully (flush + exit).
299
+ * Use before operations that invalidate the socket (e.g. clear + restart).
300
+ */
301
+ async shutdownServer(): Promise<void> {
302
+ if (!this.client) return;
303
+ try {
304
+ await this.client.shutdown();
305
+ } catch {
306
+ // Server may already be gone
307
+ }
308
+ this.client = null;
309
+ this.connected = false;
310
+ this.serverProcess = null;
311
+ }
312
+
286
313
  /**
287
314
  * Clear the database
288
315
  */
@@ -326,6 +353,23 @@ export class RFDBServerBackend {
326
353
  if (!this.client) throw new Error('Not connected');
327
354
  if (!nodes.length) return;
328
355
 
356
+ // Validate required fields
357
+ for (let i = 0; i < nodes.length; i++) {
358
+ const n = nodes[i];
359
+ if (!n.id) {
360
+ throw new Error(
361
+ `addNodes: node at index ${i} is missing required 'id' field. ` +
362
+ `Got keys: [${Object.keys(n).join(', ')}]`
363
+ );
364
+ }
365
+ if (!n.type && !n.nodeType && !n.node_type) {
366
+ throw new Error(
367
+ `addNodes: node '${n.id}' is missing type. ` +
368
+ `Provide one of: type, nodeType, or node_type`
369
+ );
370
+ }
371
+ }
372
+
329
373
  const useV3 = this.protocolVersion >= 3;
330
374
  const wireNodes: WireNode[] = nodes.map(n => {
331
375
  // Extract metadata from node
@@ -364,9 +408,43 @@ export class RFDBServerBackend {
364
408
  if (!this.client) throw new Error('Not connected');
365
409
  if (!edges.length) return;
366
410
 
411
+ // Validate required fields
412
+ if (!skipValidation) {
413
+ for (let i = 0; i < edges.length; i++) {
414
+ const e = edges[i];
415
+ // Check for src/dst (required)
416
+ if (!e.src) {
417
+ // Common mistake: using 'source'/'target' instead of 'src'/'dst'
418
+ const hint = (e as Record<string, unknown>).source
419
+ ? ` Did you mean 'src'? Found 'source' = '${(e as Record<string, unknown>).source}'`
420
+ : '';
421
+ throw new Error(
422
+ `addEdges: edge at index ${i} is missing required 'src' field.${hint} ` +
423
+ `Got keys: [${Object.keys(e).join(', ')}]`
424
+ );
425
+ }
426
+ if (!e.dst) {
427
+ const hint = (e as Record<string, unknown>).target
428
+ ? ` Did you mean 'dst'? Found 'target' = '${(e as Record<string, unknown>).target}'`
429
+ : '';
430
+ throw new Error(
431
+ `addEdges: edge at index ${i} is missing required 'dst' field.${hint} ` +
432
+ `Got keys: [${Object.keys(e).join(', ')}]`
433
+ );
434
+ }
435
+ // Check for edge type
436
+ if (!e.edgeType && !e.edge_type && !(e as Record<string, unknown>).etype && !e.type) {
437
+ throw new Error(
438
+ `addEdges: edge ${e.src} → ${e.dst} is missing edge type. ` +
439
+ `Provide one of: edgeType, edge_type, etype, or type`
440
+ );
441
+ }
442
+ }
443
+ }
444
+
367
445
  // Track edge types
368
446
  for (const e of edges) {
369
- const edgeType = e.edgeType || e.edge_type || e.etype || e.type;
447
+ const edgeType = e.edgeType || e.edge_type || (e as Record<string, unknown>).etype as string || e.type;
370
448
  if (typeof edgeType === 'string') this.edgeTypes.add(edgeType);
371
449
  }
372
450
 
package/src/version.ts CHANGED
@@ -26,3 +26,42 @@ export function getSchemaVersion(version: string): string {
26
26
  const base = version.split('-')[0];
27
27
  return base;
28
28
  }
29
+
30
+ export interface ParsedVersion {
31
+ major: number;
32
+ minor: number;
33
+ patch: number;
34
+ }
35
+
36
+ /**
37
+ * Parse a version string into structured major.minor.patch components.
38
+ * Strips pre-release tags before parsing.
39
+ *
40
+ * @param version - Version string (e.g., "0.2.5-beta")
41
+ * @returns Parsed version or null if the string is not a valid version
42
+ */
43
+ export function parseVersion(version: string): ParsedVersion | null {
44
+ const base = getSchemaVersion(version);
45
+ const parts = base.split('.');
46
+ if (parts.length < 2) return null;
47
+ const major = parseInt(parts[0], 10);
48
+ const minor = parseInt(parts[1], 10);
49
+ const patch = parts.length >= 3 ? parseInt(parts[2], 10) : 0;
50
+ if (isNaN(major) || isNaN(minor) || isNaN(patch)) return null;
51
+ return { major, minor, patch };
52
+ }
53
+
54
+ /**
55
+ * Check if two versions are compatible (same major.minor).
56
+ * Patch differences are allowed — only major or minor mismatch is incompatible.
57
+ *
58
+ * @param configVersion - Version from the config file
59
+ * @param currentVersion - Currently running Grafema version
60
+ * @returns true if versions share the same major.minor
61
+ */
62
+ export function isCompatibleVersion(configVersion: string, currentVersion: string): boolean {
63
+ const config = parseVersion(configVersion);
64
+ const current = parseVersion(currentVersion);
65
+ if (!config || !current) return false;
66
+ return config.major === current.major && config.minor === current.minor;
67
+ }