leangraph 1.0.0

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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +456 -0
  3. package/dist/auth.d.ts +66 -0
  4. package/dist/auth.d.ts.map +1 -0
  5. package/dist/auth.js +148 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/backup.d.ts +51 -0
  8. package/dist/backup.d.ts.map +1 -0
  9. package/dist/backup.js +201 -0
  10. package/dist/backup.js.map +1 -0
  11. package/dist/cli-helpers.d.ts +17 -0
  12. package/dist/cli-helpers.d.ts.map +1 -0
  13. package/dist/cli-helpers.js +121 -0
  14. package/dist/cli-helpers.js.map +1 -0
  15. package/dist/cli.d.ts +3 -0
  16. package/dist/cli.d.ts.map +1 -0
  17. package/dist/cli.js +660 -0
  18. package/dist/cli.js.map +1 -0
  19. package/dist/db.d.ts +118 -0
  20. package/dist/db.d.ts.map +1 -0
  21. package/dist/db.js +720 -0
  22. package/dist/db.js.map +1 -0
  23. package/dist/executor.d.ts +663 -0
  24. package/dist/executor.d.ts.map +1 -0
  25. package/dist/executor.js +8578 -0
  26. package/dist/executor.js.map +1 -0
  27. package/dist/index.d.ts +62 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +86 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/local.d.ts +7 -0
  32. package/dist/local.d.ts.map +1 -0
  33. package/dist/local.js +119 -0
  34. package/dist/local.js.map +1 -0
  35. package/dist/parser.d.ts +365 -0
  36. package/dist/parser.d.ts.map +1 -0
  37. package/dist/parser.js +2711 -0
  38. package/dist/parser.js.map +1 -0
  39. package/dist/property-value.d.ts +3 -0
  40. package/dist/property-value.d.ts.map +1 -0
  41. package/dist/property-value.js +30 -0
  42. package/dist/property-value.js.map +1 -0
  43. package/dist/remote.d.ts +6 -0
  44. package/dist/remote.d.ts.map +1 -0
  45. package/dist/remote.js +93 -0
  46. package/dist/remote.js.map +1 -0
  47. package/dist/routes.d.ts +31 -0
  48. package/dist/routes.d.ts.map +1 -0
  49. package/dist/routes.js +202 -0
  50. package/dist/routes.js.map +1 -0
  51. package/dist/server.d.ts +16 -0
  52. package/dist/server.d.ts.map +1 -0
  53. package/dist/server.js +25 -0
  54. package/dist/server.js.map +1 -0
  55. package/dist/translator.d.ts +330 -0
  56. package/dist/translator.d.ts.map +1 -0
  57. package/dist/translator.js +13712 -0
  58. package/dist/translator.js.map +1 -0
  59. package/dist/types.d.ts +136 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +21 -0
  62. package/dist/types.js.map +1 -0
  63. package/package.json +77 -0
package/dist/db.js ADDED
@@ -0,0 +1,720 @@
1
+ // Database Wrapper for SQLite
2
+ import Database from "better-sqlite3";
3
+ // ============================================================================
4
+ // Schema
5
+ // ============================================================================
6
+ const SCHEMA = `
7
+ CREATE TABLE IF NOT EXISTS nodes (
8
+ id TEXT PRIMARY KEY,
9
+ label JSON NOT NULL,
10
+ properties JSON DEFAULT '{}'
11
+ );
12
+
13
+ CREATE TABLE IF NOT EXISTS edges (
14
+ id TEXT PRIMARY KEY,
15
+ type TEXT NOT NULL,
16
+ source_id TEXT NOT NULL,
17
+ target_id TEXT NOT NULL,
18
+ properties JSON DEFAULT '{}',
19
+ FOREIGN KEY (source_id) REFERENCES nodes(id) ON DELETE CASCADE,
20
+ FOREIGN KEY (target_id) REFERENCES nodes(id) ON DELETE CASCADE
21
+ );
22
+
23
+ CREATE INDEX IF NOT EXISTS idx_edges_type ON edges(type);
24
+ CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);
25
+ CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);
26
+ `;
27
+ // ============================================================================
28
+ // Helpers
29
+ // ============================================================================
30
+ /**
31
+ * Convert a parameter value for SQLite binding.
32
+ * Large integers (outside JavaScript's safe integer range) are converted to BigInt
33
+ * to ensure SQLite treats them as INTEGER rather than REAL (which loses precision).
34
+ *
35
+ * Important: We convert via string representation to preserve the value that JavaScript
36
+ * would serialize (e.g., to JSON), rather than the internal floating-point representation
37
+ * which may differ for large integers.
38
+ */
39
+ function convertParamForSqlite(value) {
40
+ if (typeof value === "number" && Number.isInteger(value) && !Number.isSafeInteger(value)) {
41
+ // Large integer: convert to BigInt via string to preserve the serialized representation
42
+ // This ensures consistency with JSON.stringify() behavior
43
+ return BigInt(String(value));
44
+ }
45
+ return value;
46
+ }
47
+ /**
48
+ * Convert all params in an array for SQLite binding.
49
+ */
50
+ function convertParamsForSqlite(params) {
51
+ return params.map(convertParamForSqlite);
52
+ }
53
+ // ============================================================================
54
+ // Database Class
55
+ // ============================================================================
56
+ // ============================================================================
57
+ // Custom SQL Functions for Cypher Semantics
58
+ // ============================================================================
59
+ /**
60
+ * Deep equality comparison with Cypher's three-valued logic.
61
+ * Returns: 1 (true), 0 (false), or null (unknown when comparing with null)
62
+ */
63
+ function deepCypherEquals(a, b) {
64
+ // Both null/undefined -> null (unknown if null equals null)
65
+ if (a === null && b === null)
66
+ return null;
67
+ // One null -> null (unknown)
68
+ if (a === null || b === null)
69
+ return null;
70
+ // Arrays
71
+ if (Array.isArray(a) && Array.isArray(b)) {
72
+ if (a.length !== b.length)
73
+ return 0; // false
74
+ if (a.length === 0)
75
+ return 1; // true
76
+ let hasNull = false;
77
+ for (let i = 0; i < a.length; i++) {
78
+ const cmp = deepCypherEquals(a[i], b[i]);
79
+ if (cmp === null)
80
+ hasNull = true;
81
+ else if (cmp === 0)
82
+ return 0; // false
83
+ }
84
+ return hasNull ? null : 1;
85
+ }
86
+ // Objects (maps)
87
+ if (typeof a === "object" && typeof b === "object" && a !== null && b !== null && !Array.isArray(a) && !Array.isArray(b)) {
88
+ const keysA = Object.keys(a).sort();
89
+ const keysB = Object.keys(b).sort();
90
+ if (keysA.length !== keysB.length)
91
+ return 0;
92
+ if (keysA.join(",") !== keysB.join(","))
93
+ return 0;
94
+ let hasNull = false;
95
+ for (const k of keysA) {
96
+ const cmp = deepCypherEquals(a[k], b[k]);
97
+ if (cmp === null)
98
+ hasNull = true;
99
+ else if (cmp === 0)
100
+ return 0;
101
+ }
102
+ return hasNull ? null : 1;
103
+ }
104
+ // Primitives
105
+ return a === b ? 1 : 0;
106
+ }
107
+ /**
108
+ * Get the Cypher type category for ordering comparisons.
109
+ * Returns a type string for values that can be ordered, or null for non-orderable types.
110
+ *
111
+ * When using SQLite's -> operator for JSON extraction, values come as JSON-formatted strings:
112
+ * - 'true' / 'false' for booleans
113
+ * - '"string"' for strings (with quotes)
114
+ * - '123' or '3.14' for numbers (no quotes)
115
+ * - '[...]' for arrays
116
+ * - '{...}' for objects
117
+ */
118
+ // Regex patterns for temporal types
119
+ const TIME_PATTERN = /^\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
120
+ const LOCALTIME_PATTERN = /^\d{2}:\d{2}(:\d{2})?(\.\d+)?$/;
121
+ const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
122
+ const DATETIME_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:\d{2})?(\[.+\])?$/;
123
+ const LOCALDATETIME_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?(\.\d+)?$/;
124
+ function getCypherTypeForOrdering(value) {
125
+ if (value === null)
126
+ return null;
127
+ const jsType = typeof value;
128
+ // Numbers (integer and real) are in the same ordering category
129
+ if (jsType === "number" || jsType === "bigint")
130
+ return "number";
131
+ // Strings - could be raw strings OR JSON-formatted values from -> operator
132
+ if (jsType === "string") {
133
+ const s = value;
134
+ // Check for JSON boolean literals (from -> operator)
135
+ if (s === "true" || s === "false")
136
+ return "boolean";
137
+ // Check for JSON null
138
+ if (s === "null")
139
+ return null;
140
+ // Check for JSON array
141
+ if (s.startsWith("[") && s.endsWith("]")) {
142
+ try {
143
+ JSON.parse(s);
144
+ return "array"; // arrays are not orderable
145
+ }
146
+ catch {
147
+ // Not valid JSON, treat as string
148
+ }
149
+ }
150
+ // Check for JSON object
151
+ if (s.startsWith("{") && s.endsWith("}")) {
152
+ try {
153
+ JSON.parse(s);
154
+ return "object"; // objects are not orderable
155
+ }
156
+ catch {
157
+ // Not valid JSON, treat as string
158
+ }
159
+ }
160
+ // Check for JSON string literal (starts and ends with quotes)
161
+ if (s.startsWith('"') && s.endsWith('"') && s.length >= 2) {
162
+ return "string";
163
+ }
164
+ // Check for JSON number (no quotes, valid number)
165
+ if (/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(s)) {
166
+ return "number";
167
+ }
168
+ // Check for temporal types (times with timezone need special comparison)
169
+ if (TIME_PATTERN.test(s))
170
+ return "time";
171
+ if (DATETIME_PATTERN.test(s))
172
+ return "datetime";
173
+ if (DATE_PATTERN.test(s))
174
+ return "date";
175
+ if (LOCALTIME_PATTERN.test(s))
176
+ return "localtime";
177
+ if (LOCALDATETIME_PATTERN.test(s))
178
+ return "localdatetime";
179
+ // Otherwise treat as a plain string
180
+ return "string";
181
+ }
182
+ // Booleans - SQLite stores these as integers, but if we somehow get a JS boolean
183
+ if (jsType === "boolean")
184
+ return "boolean";
185
+ // Objects and arrays in JS form (shouldn't normally happen with SQLite)
186
+ if (Array.isArray(value))
187
+ return "array";
188
+ if (jsType === "object")
189
+ return "object";
190
+ return null;
191
+ }
192
+ /**
193
+ * Check if two Cypher types are compatible for ordering comparisons (<, <=, >, >=).
194
+ */
195
+ function areCypherTypesOrderable(typeA, typeB) {
196
+ if (typeA === null || typeB === null)
197
+ return false;
198
+ // Arrays, objects, nodes, relationships are not orderable
199
+ if (typeA === "array" || typeB === "array")
200
+ return false;
201
+ if (typeA === "object" || typeB === "object")
202
+ return false;
203
+ // Same type is always orderable
204
+ if (typeA === typeB)
205
+ return true;
206
+ // Numbers (integer/real) are orderable with each other - already handled by "number" category
207
+ return false;
208
+ }
209
+ /**
210
+ * Parse timezone offset to minutes from UTC
211
+ */
212
+ function parseTimezoneOffset(tz) {
213
+ if (tz === "Z" || tz === "+00:00")
214
+ return 0;
215
+ const sign = tz[0] === "-" ? -1 : 1;
216
+ const hours = parseInt(tz.slice(1, 3), 10);
217
+ const minutes = parseInt(tz.slice(4, 6), 10);
218
+ return sign * (hours * 60 + minutes);
219
+ }
220
+ /**
221
+ * Convert time string to nanoseconds from midnight UTC for comparison
222
+ */
223
+ function timeToNanosUTC(timeStr) {
224
+ // Format: HH:MM or HH:MM:SS or HH:MM:SS.nnnnnnnnn followed by Z or +HH:MM or -HH:MM
225
+ // May also have [timezone] suffix - strip it
226
+ const withoutTzName = timeStr.replace(/\[.+\]$/, "");
227
+ // Find timezone part
228
+ let tzOffset = 0;
229
+ let timePart = withoutTzName;
230
+ if (withoutTzName.endsWith("Z")) {
231
+ timePart = withoutTzName.slice(0, -1);
232
+ tzOffset = 0;
233
+ }
234
+ else {
235
+ const tzMatch = withoutTzName.match(/([+-]\d{2}:\d{2})$/);
236
+ if (tzMatch) {
237
+ tzOffset = parseTimezoneOffset(tzMatch[1]);
238
+ timePart = withoutTzName.slice(0, -6);
239
+ }
240
+ }
241
+ // Parse time components
242
+ const parts = timePart.split(":");
243
+ const hours = parseInt(parts[0], 10);
244
+ const minutes = parseInt(parts[1], 10);
245
+ let seconds = 0;
246
+ let nanos = 0;
247
+ if (parts[2]) {
248
+ const secParts = parts[2].split(".");
249
+ seconds = parseInt(secParts[0], 10);
250
+ if (secParts[1]) {
251
+ // Pad or truncate to 9 digits
252
+ const fracStr = secParts[1].padEnd(9, "0").slice(0, 9);
253
+ nanos = parseInt(fracStr, 10);
254
+ }
255
+ }
256
+ // Convert to total nanoseconds from midnight, then adjust for timezone
257
+ const totalMinutes = hours * 60 + minutes - tzOffset;
258
+ const totalNanos = (totalMinutes * 60 + seconds) * 1_000_000_000 + nanos;
259
+ // Normalize to 24-hour range (handle negative from timezone adjustment)
260
+ const dayInNanos = 24 * 60 * 60 * 1_000_000_000;
261
+ return ((totalNanos % dayInNanos) + dayInNanos) % dayInNanos;
262
+ }
263
+ /**
264
+ * Convert a value to its comparable form.
265
+ * For JSON-formatted strings from -> operator, parse to get actual value.
266
+ * For temporal types, convert to a form suitable for comparison.
267
+ */
268
+ function toComparableValue(value, type) {
269
+ if (typeof value === "string") {
270
+ if (type === "number") {
271
+ return parseFloat(value);
272
+ }
273
+ if (type === "boolean") {
274
+ return value === "true";
275
+ }
276
+ if (type === "string") {
277
+ // JSON string literal - remove outer quotes
278
+ if (value.startsWith('"') && value.endsWith('"')) {
279
+ return value.slice(1, -1);
280
+ }
281
+ return value;
282
+ }
283
+ if (type === "time") {
284
+ // Convert to nanoseconds from midnight UTC for comparison
285
+ return timeToNanosUTC(value);
286
+ }
287
+ if (type === "datetime") {
288
+ // Strip [timezone] suffix and compare lexically (ISO format is naturally sortable when in UTC or same TZ)
289
+ // For proper comparison, we'd need to convert to UTC, but for same-offset datetimes, lexical works
290
+ // TODO: Full timezone-aware datetime comparison
291
+ return value.replace(/\[.+\]$/, "");
292
+ }
293
+ // date, localtime, localdatetime can be compared lexically (ISO format is sortable)
294
+ if (type === "date" || type === "localtime" || type === "localdatetime") {
295
+ return value;
296
+ }
297
+ }
298
+ return value;
299
+ }
300
+ /**
301
+ * Helper to convert SQLite boolean representation to JavaScript boolean.
302
+ * SQLite can represent booleans as: 1, 0, 'true', 'false'
303
+ */
304
+ function toBoolValue(x) {
305
+ if (x === null || x === undefined)
306
+ return null;
307
+ if (x === 1 || x === true || x === 'true')
308
+ return true;
309
+ if (x === 0 || x === false || x === 'false')
310
+ return false;
311
+ return null;
312
+ }
313
+ /**
314
+ * Register custom SQL functions for Cypher semantics on a database instance.
315
+ */
316
+ function registerCypherFunctions(db) {
317
+ // cypher_not: Proper boolean negation that works with both JSON booleans and integers
318
+ // Converts json('true')/1 -> 0, json('false')/0 -> 1, null -> null
319
+ // Returns integers for SQLite compatibility in WHERE clauses
320
+ db.function("cypher_not", { deterministic: true }, (x) => {
321
+ const b = toBoolValue(x);
322
+ if (b === null)
323
+ return null;
324
+ return b ? 0 : 1;
325
+ });
326
+ // cypher_and: Proper boolean AND that works with both JSON booleans and integers
327
+ // Returns integers for SQLite compatibility in WHERE clauses
328
+ db.function("cypher_and", { deterministic: true }, (a, b) => {
329
+ const boolA = toBoolValue(a);
330
+ const boolB = toBoolValue(b);
331
+ // Cypher AND with NULL: false AND NULL = false, true AND NULL = NULL
332
+ if (boolA === false || boolB === false)
333
+ return 0;
334
+ if (boolA === null || boolB === null)
335
+ return null;
336
+ return boolA && boolB ? 1 : 0;
337
+ });
338
+ // cypher_or: Proper boolean OR that works with both JSON booleans and integers
339
+ // Returns integers for SQLite compatibility in WHERE clauses
340
+ db.function("cypher_or", { deterministic: true }, (a, b) => {
341
+ const boolA = toBoolValue(a);
342
+ const boolB = toBoolValue(b);
343
+ // Cypher OR with NULL: true OR NULL = true, false OR NULL = NULL
344
+ if (boolA === true || boolB === true)
345
+ return 1;
346
+ if (boolA === null || boolB === null)
347
+ return null;
348
+ return boolA || boolB ? 1 : 0;
349
+ });
350
+ // cypher_compare: Type-aware comparison for ordering operators (<, <=, >, >=)
351
+ // Returns: 1 if condition is true, 0 if false, null if types are incompatible
352
+ db.function("cypher_lt", { deterministic: true }, (a, b) => {
353
+ if (a === null || a === undefined || b === null || b === undefined)
354
+ return null;
355
+ const typeA = getCypherTypeForOrdering(a);
356
+ const typeB = getCypherTypeForOrdering(b);
357
+ if (!areCypherTypesOrderable(typeA, typeB))
358
+ return null;
359
+ const valA = toComparableValue(a, typeA);
360
+ const valB = toComparableValue(b, typeB);
361
+ return valA < valB ? 1 : 0;
362
+ });
363
+ db.function("cypher_lte", { deterministic: true }, (a, b) => {
364
+ if (a === null || a === undefined || b === null || b === undefined)
365
+ return null;
366
+ const typeA = getCypherTypeForOrdering(a);
367
+ const typeB = getCypherTypeForOrdering(b);
368
+ if (!areCypherTypesOrderable(typeA, typeB))
369
+ return null;
370
+ const valA = toComparableValue(a, typeA);
371
+ const valB = toComparableValue(b, typeB);
372
+ return valA <= valB ? 1 : 0;
373
+ });
374
+ db.function("cypher_gt", { deterministic: true }, (a, b) => {
375
+ if (a === null || a === undefined || b === null || b === undefined)
376
+ return null;
377
+ const typeA = getCypherTypeForOrdering(a);
378
+ const typeB = getCypherTypeForOrdering(b);
379
+ if (!areCypherTypesOrderable(typeA, typeB))
380
+ return null;
381
+ const valA = toComparableValue(a, typeA);
382
+ const valB = toComparableValue(b, typeB);
383
+ return valA > valB ? 1 : 0;
384
+ });
385
+ db.function("cypher_gte", { deterministic: true }, (a, b) => {
386
+ if (a === null || a === undefined || b === null || b === undefined)
387
+ return null;
388
+ const typeA = getCypherTypeForOrdering(a);
389
+ const typeB = getCypherTypeForOrdering(b);
390
+ if (!areCypherTypesOrderable(typeA, typeB))
391
+ return null;
392
+ const valA = toComparableValue(a, typeA);
393
+ const valB = toComparableValue(b, typeB);
394
+ return valA >= valB ? 1 : 0;
395
+ });
396
+ // cypher_equals: Null-aware deep equality for lists and maps
397
+ db.function("cypher_equals", { deterministic: true }, (a, b) => {
398
+ // Handle SQL NULL
399
+ if (a === null && b === null)
400
+ return null;
401
+ if (a === null || b === null)
402
+ return null;
403
+ // Try to parse as JSON (for arrays/objects stored as JSON strings)
404
+ let parsedA, parsedB;
405
+ try {
406
+ parsedA = typeof a === "string" ? JSON.parse(a) : a;
407
+ }
408
+ catch {
409
+ parsedA = a;
410
+ }
411
+ try {
412
+ parsedB = typeof b === "string" ? JSON.parse(b) : b;
413
+ }
414
+ catch {
415
+ parsedB = b;
416
+ }
417
+ return deepCypherEquals(parsedA, parsedB);
418
+ });
419
+ // cypher_case_eq: Type-aware equality for CASE expressions
420
+ // Takes value+type pairs to preserve type information across SQLite's type coercion
421
+ // Returns: 1 if equal (same type and value), 0 if not equal
422
+ db.function("cypher_case_eq", { deterministic: true }, (val1, type1, val2, type2) => {
423
+ // NULL handling: if either is null, return null (unknown)
424
+ if (val1 === null || val2 === null)
425
+ return null;
426
+ if (type1 === "null" || type2 === "null")
427
+ return null;
428
+ // Helper to get runtime type from a value
429
+ const getRuntimeType = (val) => {
430
+ if (val === null)
431
+ return "null";
432
+ const jsType = typeof val;
433
+ if (jsType === "boolean" || val === 0 || val === 1) {
434
+ // SQLite stores booleans as 0/1, so we can't distinguish at runtime
435
+ // We rely on the compile-time type info for this
436
+ return "unknown_number_or_boolean";
437
+ }
438
+ if (jsType === "number" || jsType === "bigint")
439
+ return "number";
440
+ if (jsType === "string") {
441
+ // Check if it's a JSON array/object
442
+ const str = val;
443
+ if (str.startsWith("["))
444
+ return "list";
445
+ if (str.startsWith("{"))
446
+ return "map";
447
+ return "string";
448
+ }
449
+ return "unknown";
450
+ };
451
+ // Resolve "dynamic" types using runtime type detection
452
+ let resolvedType1 = type1;
453
+ let resolvedType2 = type2;
454
+ if (type1 === "dynamic") {
455
+ resolvedType1 = getRuntimeType(val1);
456
+ }
457
+ if (type2 === "dynamic") {
458
+ resolvedType2 = getRuntimeType(val2);
459
+ }
460
+ // Normalize numeric types: integer, float, and number are all comparable
461
+ const normalizeNumericType = (t) => {
462
+ if (t === "integer" || t === "float" || t === "number")
463
+ return "numeric";
464
+ return t;
465
+ };
466
+ const normType1 = normalizeNumericType(resolvedType1);
467
+ const normType2 = normalizeNumericType(resolvedType2);
468
+ // Different types are never equal in CASE expressions
469
+ // (except numeric types which are comparable)
470
+ if (normType1 !== normType2)
471
+ return 0;
472
+ // Same type - compare values
473
+ // For lists/maps, use deep comparison
474
+ if (normType1 === "list" || normType1 === "map") {
475
+ let parsed1, parsed2;
476
+ try {
477
+ parsed1 = typeof val1 === "string" ? JSON.parse(val1) : val1;
478
+ }
479
+ catch {
480
+ parsed1 = val1;
481
+ }
482
+ try {
483
+ parsed2 = typeof val2 === "string" ? JSON.parse(val2) : val2;
484
+ }
485
+ catch {
486
+ parsed2 = val2;
487
+ }
488
+ const result = deepCypherEquals(parsed1, parsed2);
489
+ return result === null ? null : result;
490
+ }
491
+ // For numeric types, compare as numbers
492
+ if (normType1 === "numeric") {
493
+ const num1 = Number(val1);
494
+ const num2 = Number(val2);
495
+ return num1 === num2 ? 1 : 0;
496
+ }
497
+ // For primitives (boolean, string), direct comparison
498
+ return val1 === val2 ? 1 : 0;
499
+ });
500
+ }
501
+ export class GraphDatabase {
502
+ db;
503
+ initialized = false;
504
+ constructor(path = ":memory:") {
505
+ this.db = new Database(path);
506
+ this.db.pragma("journal_mode = WAL");
507
+ this.db.pragma("foreign_keys = ON");
508
+ // Register custom Cypher functions
509
+ registerCypherFunctions(this.db);
510
+ }
511
+ /**
512
+ * Initialize the database schema
513
+ */
514
+ initialize() {
515
+ if (this.initialized)
516
+ return;
517
+ this.db.exec(SCHEMA);
518
+ this.initialized = true;
519
+ }
520
+ /**
521
+ * Execute a SQL statement and return results
522
+ */
523
+ execute(sql, params = []) {
524
+ this.ensureInitialized();
525
+ // Convert large integers to BigInt for proper SQLite INTEGER binding
526
+ const convertedParams = convertParamsForSqlite(params);
527
+ const stmt = this.db.prepare(sql);
528
+ const trimmedSql = sql.trim().toUpperCase();
529
+ // Check if it's a query (SELECT or WITH for CTEs)
530
+ const isQuery = trimmedSql.startsWith("SELECT") || trimmedSql.startsWith("WITH");
531
+ if (isQuery) {
532
+ const rows = stmt.all(...convertedParams);
533
+ return { rows, changes: 0, lastInsertRowid: 0 };
534
+ }
535
+ else {
536
+ const result = stmt.run(...convertedParams);
537
+ return {
538
+ rows: [],
539
+ changes: result.changes,
540
+ lastInsertRowid: result.lastInsertRowid,
541
+ };
542
+ }
543
+ }
544
+ /**
545
+ * Execute multiple statements in a transaction
546
+ */
547
+ transaction(fn) {
548
+ this.ensureInitialized();
549
+ return this.db.transaction(fn)();
550
+ }
551
+ /**
552
+ * Insert a node
553
+ */
554
+ insertNode(id, label, properties = {}) {
555
+ // Normalize label to array format for storage
556
+ const labelArray = Array.isArray(label) ? label : [label];
557
+ this.execute("INSERT INTO nodes (id, label, properties) VALUES (?, ?, ?)", [id, JSON.stringify(labelArray), JSON.stringify(properties)]);
558
+ }
559
+ /**
560
+ * Insert an edge
561
+ */
562
+ insertEdge(id, type, sourceId, targetId, properties = {}) {
563
+ this.execute("INSERT INTO edges (id, type, source_id, target_id, properties) VALUES (?, ?, ?, ?, ?)", [id, type, sourceId, targetId, JSON.stringify(properties)]);
564
+ }
565
+ /**
566
+ * Get a node by ID
567
+ */
568
+ getNode(id) {
569
+ const result = this.execute("SELECT * FROM nodes WHERE id = ?", [id]);
570
+ if (result.rows.length === 0)
571
+ return null;
572
+ const row = result.rows[0];
573
+ const labelArray = JSON.parse(row.label);
574
+ return {
575
+ id: row.id,
576
+ label: labelArray,
577
+ properties: JSON.parse(row.properties),
578
+ };
579
+ }
580
+ /**
581
+ * Get an edge by ID
582
+ */
583
+ getEdge(id) {
584
+ const result = this.execute("SELECT * FROM edges WHERE id = ?", [id]);
585
+ if (result.rows.length === 0)
586
+ return null;
587
+ const row = result.rows[0];
588
+ return {
589
+ id: row.id,
590
+ type: row.type,
591
+ source_id: row.source_id,
592
+ target_id: row.target_id,
593
+ properties: JSON.parse(row.properties),
594
+ };
595
+ }
596
+ /**
597
+ * Get all nodes with a given label
598
+ */
599
+ getNodesByLabel(label) {
600
+ const result = this.execute("SELECT * FROM nodes WHERE EXISTS (SELECT 1 FROM json_each(label) WHERE value = ?)", [label]);
601
+ return result.rows.map((row) => {
602
+ const r = row;
603
+ const labelArray = JSON.parse(r.label);
604
+ return {
605
+ id: r.id,
606
+ label: labelArray,
607
+ properties: JSON.parse(r.properties),
608
+ };
609
+ });
610
+ }
611
+ /**
612
+ * Get all edges with a given type
613
+ */
614
+ getEdgesByType(type) {
615
+ const result = this.execute("SELECT * FROM edges WHERE type = ?", [type]);
616
+ return result.rows.map((row) => {
617
+ const r = row;
618
+ return {
619
+ id: r.id,
620
+ type: r.type,
621
+ source_id: r.source_id,
622
+ target_id: r.target_id,
623
+ properties: JSON.parse(r.properties),
624
+ };
625
+ });
626
+ }
627
+ /**
628
+ * Delete a node by ID
629
+ */
630
+ deleteNode(id) {
631
+ const result = this.execute("DELETE FROM nodes WHERE id = ?", [id]);
632
+ return result.changes > 0;
633
+ }
634
+ /**
635
+ * Delete an edge by ID
636
+ */
637
+ deleteEdge(id) {
638
+ const result = this.execute("DELETE FROM edges WHERE id = ?", [id]);
639
+ return result.changes > 0;
640
+ }
641
+ /**
642
+ * Update node properties
643
+ */
644
+ updateNodeProperties(id, properties) {
645
+ const result = this.execute("UPDATE nodes SET properties = ? WHERE id = ?", [JSON.stringify(properties), id]);
646
+ return result.changes > 0;
647
+ }
648
+ /**
649
+ * Count nodes
650
+ */
651
+ countNodes() {
652
+ const result = this.execute("SELECT COUNT(*) as count FROM nodes");
653
+ return result.rows[0].count;
654
+ }
655
+ /**
656
+ * Count edges
657
+ */
658
+ countEdges() {
659
+ const result = this.execute("SELECT COUNT(*) as count FROM edges");
660
+ return result.rows[0].count;
661
+ }
662
+ /**
663
+ * Close the database connection
664
+ */
665
+ close() {
666
+ this.db.close();
667
+ }
668
+ /**
669
+ * Get the underlying database instance (for advanced operations)
670
+ */
671
+ getRawDatabase() {
672
+ return this.db;
673
+ }
674
+ ensureInitialized() {
675
+ if (!this.initialized) {
676
+ this.initialize();
677
+ }
678
+ }
679
+ }
680
+ // ============================================================================
681
+ // Database Manager (for multi-project support)
682
+ // ============================================================================
683
+ export class DatabaseManager {
684
+ databases = new Map();
685
+ basePath;
686
+ constructor(basePath = ":memory:") {
687
+ this.basePath = basePath;
688
+ }
689
+ /**
690
+ * Get or create a database for a project/environment
691
+ */
692
+ getDatabase(project, env = "production") {
693
+ const key = `${env}/${project}`;
694
+ if (!this.databases.has(key)) {
695
+ const path = this.basePath === ":memory:"
696
+ ? ":memory:"
697
+ : `${this.basePath}/${env}/${project}.db`;
698
+ const db = new GraphDatabase(path);
699
+ db.initialize();
700
+ this.databases.set(key, db);
701
+ }
702
+ return this.databases.get(key);
703
+ }
704
+ /**
705
+ * Close all database connections
706
+ */
707
+ closeAll() {
708
+ for (const db of this.databases.values()) {
709
+ db.close();
710
+ }
711
+ this.databases.clear();
712
+ }
713
+ /**
714
+ * List all open databases
715
+ */
716
+ listDatabases() {
717
+ return Array.from(this.databases.keys());
718
+ }
719
+ }
720
+ //# sourceMappingURL=db.js.map