@typicalday/firegraph 0.1.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 (48) hide show
  1. package/LICENSE +27 -0
  2. package/README.md +527 -0
  3. package/bin/firegraph.mjs +129 -0
  4. package/dist/chunk-KFA7G37W.js +443 -0
  5. package/dist/chunk-KFA7G37W.js.map +1 -0
  6. package/dist/chunk-YLGXLEUE.js +47 -0
  7. package/dist/chunk-YLGXLEUE.js.map +1 -0
  8. package/dist/client-Bk2Cm6xv.d.cts +131 -0
  9. package/dist/client-Bk2Cm6xv.d.ts +131 -0
  10. package/dist/codegen/index.cjs +81 -0
  11. package/dist/codegen/index.cjs.map +1 -0
  12. package/dist/codegen/index.d.cts +2 -0
  13. package/dist/codegen/index.d.ts +2 -0
  14. package/dist/codegen/index.js +7 -0
  15. package/dist/codegen/index.js.map +1 -0
  16. package/dist/editor/client/assets/index-DJJ_b0jI.js +411 -0
  17. package/dist/editor/client/assets/index-Q0QBYrMV.css +1 -0
  18. package/dist/editor/client/index.html +16 -0
  19. package/dist/editor/server/index.mjs +49597 -0
  20. package/dist/index-CG3R68Hu.d.cts +414 -0
  21. package/dist/index-CG3R68Hu.d.ts +414 -0
  22. package/dist/index.cjs +1953 -0
  23. package/dist/index.cjs.map +1 -0
  24. package/dist/index.d.cts +186 -0
  25. package/dist/index.d.ts +186 -0
  26. package/dist/index.js +1569 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/query-client/index.cjs +484 -0
  29. package/dist/query-client/index.cjs.map +1 -0
  30. package/dist/query-client/index.d.cts +15 -0
  31. package/dist/query-client/index.d.ts +15 -0
  32. package/dist/query-client/index.js +17 -0
  33. package/dist/query-client/index.js.map +1 -0
  34. package/dist/react.cjs +85 -0
  35. package/dist/react.cjs.map +1 -0
  36. package/dist/react.d.cts +44 -0
  37. package/dist/react.d.ts +44 -0
  38. package/dist/react.js +60 -0
  39. package/dist/react.js.map +1 -0
  40. package/dist/svelte.cjs +90 -0
  41. package/dist/svelte.cjs.map +1 -0
  42. package/dist/svelte.d.cts +46 -0
  43. package/dist/svelte.d.ts +46 -0
  44. package/dist/svelte.js +65 -0
  45. package/dist/svelte.js.map +1 -0
  46. package/dist/views-DL60k0cf.d.cts +91 -0
  47. package/dist/views-DL60k0cf.d.ts +91 -0
  48. package/package.json +122 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,1953 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ BOOTSTRAP_ENTRIES: () => BOOTSTRAP_ENTRIES,
34
+ DiscoveryError: () => DiscoveryError,
35
+ DynamicRegistryError: () => DynamicRegistryError,
36
+ EDGE_TYPE_SCHEMA: () => EDGE_TYPE_SCHEMA,
37
+ EdgeNotFoundError: () => EdgeNotFoundError,
38
+ FiregraphError: () => FiregraphError,
39
+ InvalidQueryError: () => InvalidQueryError,
40
+ META_EDGE_TYPE: () => META_EDGE_TYPE,
41
+ META_NODE_TYPE: () => META_NODE_TYPE,
42
+ NODE_TYPE_SCHEMA: () => NODE_TYPE_SCHEMA,
43
+ NodeNotFoundError: () => NodeNotFoundError,
44
+ QueryClient: () => QueryClient,
45
+ QueryClientError: () => QueryClientError,
46
+ RegistryViolationError: () => RegistryViolationError,
47
+ TraversalError: () => TraversalError,
48
+ ValidationError: () => ValidationError,
49
+ buildEdgeQueryPlan: () => buildEdgeQueryPlan,
50
+ buildEdgeRecord: () => buildEdgeRecord,
51
+ buildNodeQueryPlan: () => buildNodeQueryPlan,
52
+ buildNodeRecord: () => buildNodeRecord,
53
+ compileSchema: () => compileSchema,
54
+ computeEdgeDocId: () => computeEdgeDocId,
55
+ computeNodeDocId: () => computeNodeDocId,
56
+ createBootstrapRegistry: () => createBootstrapRegistry,
57
+ createGraphClient: () => createGraphClient,
58
+ createRegistry: () => createRegistry,
59
+ createRegistryFromGraph: () => createRegistryFromGraph,
60
+ createTraversal: () => createTraversal,
61
+ defineConfig: () => defineConfig,
62
+ defineViews: () => defineViews,
63
+ discoverEntities: () => discoverEntities,
64
+ generateDeterministicUid: () => generateDeterministicUid,
65
+ generateId: () => generateId,
66
+ generateTypes: () => generateTypes,
67
+ jsonSchemaToFieldMeta: () => jsonSchemaToFieldMeta,
68
+ resolveView: () => resolveView
69
+ });
70
+ module.exports = __toCommonJS(index_exports);
71
+
72
+ // src/client.ts
73
+ var import_firestore4 = require("@google-cloud/firestore");
74
+
75
+ // src/docid.ts
76
+ var import_node_crypto = require("crypto");
77
+
78
+ // src/internal/constants.ts
79
+ var NODE_RELATION = "is";
80
+ var SHARD_SEPARATOR = ":";
81
+
82
+ // src/docid.ts
83
+ function computeNodeDocId(uid) {
84
+ return uid;
85
+ }
86
+ function computeEdgeDocId(aUid, axbType, bUid) {
87
+ const composite = `${aUid}${SHARD_SEPARATOR}${axbType}${SHARD_SEPARATOR}${bUid}`;
88
+ const hash = (0, import_node_crypto.createHash)("sha256").update(composite).digest("hex");
89
+ const shard = hash[0];
90
+ return `${shard}${SHARD_SEPARATOR}${aUid}${SHARD_SEPARATOR}${axbType}${SHARD_SEPARATOR}${bUid}`;
91
+ }
92
+
93
+ // src/record.ts
94
+ var import_firestore = require("@google-cloud/firestore");
95
+ function buildNodeRecord(aType, uid, data) {
96
+ const now = import_firestore.FieldValue.serverTimestamp();
97
+ return {
98
+ aType,
99
+ aUid: uid,
100
+ axbType: NODE_RELATION,
101
+ bType: aType,
102
+ bUid: uid,
103
+ data,
104
+ createdAt: now,
105
+ updatedAt: now
106
+ };
107
+ }
108
+ function buildEdgeRecord(aType, aUid, axbType, bType, bUid, data) {
109
+ const now = import_firestore.FieldValue.serverTimestamp();
110
+ return {
111
+ aType,
112
+ aUid,
113
+ axbType,
114
+ bType,
115
+ bUid,
116
+ data,
117
+ createdAt: now,
118
+ updatedAt: now
119
+ };
120
+ }
121
+
122
+ // src/errors.ts
123
+ var FiregraphError = class extends Error {
124
+ constructor(message, code) {
125
+ super(message);
126
+ this.code = code;
127
+ this.name = "FiregraphError";
128
+ }
129
+ };
130
+ var NodeNotFoundError = class extends FiregraphError {
131
+ constructor(uid) {
132
+ super(`Node not found: ${uid}`, "NODE_NOT_FOUND");
133
+ this.name = "NodeNotFoundError";
134
+ }
135
+ };
136
+ var EdgeNotFoundError = class extends FiregraphError {
137
+ constructor(aUid, axbType, bUid) {
138
+ super(`Edge not found: ${aUid} -[${axbType}]-> ${bUid}`, "EDGE_NOT_FOUND");
139
+ this.name = "EdgeNotFoundError";
140
+ }
141
+ };
142
+ var ValidationError = class extends FiregraphError {
143
+ constructor(message, details) {
144
+ super(message, "VALIDATION_ERROR");
145
+ this.details = details;
146
+ this.name = "ValidationError";
147
+ }
148
+ };
149
+ var RegistryViolationError = class extends FiregraphError {
150
+ constructor(aType, axbType, bType) {
151
+ super(
152
+ `Unregistered triple: (${aType}) -[${axbType}]-> (${bType})`,
153
+ "REGISTRY_VIOLATION"
154
+ );
155
+ this.name = "RegistryViolationError";
156
+ }
157
+ };
158
+ var InvalidQueryError = class extends FiregraphError {
159
+ constructor(message) {
160
+ super(message, "INVALID_QUERY");
161
+ this.name = "InvalidQueryError";
162
+ }
163
+ };
164
+ var TraversalError = class extends FiregraphError {
165
+ constructor(message) {
166
+ super(message, "TRAVERSAL_ERROR");
167
+ this.name = "TraversalError";
168
+ }
169
+ };
170
+ var DynamicRegistryError = class extends FiregraphError {
171
+ constructor(message) {
172
+ super(message, "DYNAMIC_REGISTRY_ERROR");
173
+ this.name = "DynamicRegistryError";
174
+ }
175
+ };
176
+
177
+ // src/query.ts
178
+ function buildEdgeQueryPlan(params) {
179
+ const { aType, aUid, axbType, bType, bUid, limit, orderBy } = params;
180
+ if (aUid && axbType && bUid && !params.where?.length) {
181
+ return { strategy: "get", docId: computeEdgeDocId(aUid, axbType, bUid) };
182
+ }
183
+ const filters = [];
184
+ if (aType) filters.push({ field: "aType", op: "==", value: aType });
185
+ if (aUid) filters.push({ field: "aUid", op: "==", value: aUid });
186
+ if (axbType) filters.push({ field: "axbType", op: "==", value: axbType });
187
+ if (bType) filters.push({ field: "bType", op: "==", value: bType });
188
+ if (bUid) filters.push({ field: "bUid", op: "==", value: bUid });
189
+ const builtinFields = ["aType", "aUid", "axbType", "bType", "bUid", "createdAt", "updatedAt"];
190
+ if (params.where) {
191
+ for (const clause of params.where) {
192
+ const field = builtinFields.includes(clause.field) ? clause.field : clause.field.startsWith("data.") ? clause.field : `data.${clause.field}`;
193
+ filters.push({ field, op: clause.op, value: clause.value });
194
+ }
195
+ }
196
+ if (filters.length === 0) {
197
+ throw new InvalidQueryError("findEdges requires at least one filter parameter");
198
+ }
199
+ const options = limit !== void 0 || orderBy ? { limit, orderBy } : void 0;
200
+ return { strategy: "query", filters, options };
201
+ }
202
+ function buildNodeQueryPlan(params) {
203
+ return {
204
+ strategy: "query",
205
+ filters: [
206
+ { field: "aType", op: "==", value: params.aType },
207
+ { field: "axbType", op: "==", value: NODE_RELATION }
208
+ ]
209
+ };
210
+ }
211
+
212
+ // src/internal/firestore-adapter.ts
213
+ function createFirestoreAdapter(db, collectionPath) {
214
+ const collectionRef = db.collection(collectionPath);
215
+ return {
216
+ collectionPath,
217
+ async getDoc(docId) {
218
+ const snap = await collectionRef.doc(docId).get();
219
+ if (!snap.exists) return null;
220
+ return snap.data();
221
+ },
222
+ async setDoc(docId, data) {
223
+ await collectionRef.doc(docId).set(data);
224
+ },
225
+ async updateDoc(docId, data) {
226
+ await collectionRef.doc(docId).update(data);
227
+ },
228
+ async deleteDoc(docId) {
229
+ await collectionRef.doc(docId).delete();
230
+ },
231
+ async query(filters, options) {
232
+ let q = collectionRef;
233
+ for (const f of filters) {
234
+ q = q.where(f.field, f.op, f.value);
235
+ }
236
+ if (options?.orderBy) {
237
+ q = q.orderBy(options.orderBy.field, options.orderBy.direction ?? "asc");
238
+ }
239
+ if (options?.limit !== void 0) {
240
+ q = q.limit(options.limit);
241
+ }
242
+ const snap = await q.get();
243
+ return snap.docs.map((doc) => doc.data());
244
+ }
245
+ };
246
+ }
247
+ function createTransactionAdapter(db, collectionPath, tx) {
248
+ const collectionRef = db.collection(collectionPath);
249
+ return {
250
+ async getDoc(docId) {
251
+ const snap = await tx.get(collectionRef.doc(docId));
252
+ if (!snap.exists) return null;
253
+ return snap.data();
254
+ },
255
+ setDoc(docId, data) {
256
+ tx.set(collectionRef.doc(docId), data);
257
+ },
258
+ updateDoc(docId, data) {
259
+ tx.update(collectionRef.doc(docId), data);
260
+ },
261
+ deleteDoc(docId) {
262
+ tx.delete(collectionRef.doc(docId));
263
+ },
264
+ async query(filters, options) {
265
+ let q = collectionRef;
266
+ for (const f of filters) {
267
+ q = q.where(f.field, f.op, f.value);
268
+ }
269
+ if (options?.orderBy) {
270
+ q = q.orderBy(options.orderBy.field, options.orderBy.direction ?? "asc");
271
+ }
272
+ if (options?.limit !== void 0) {
273
+ q = q.limit(options.limit);
274
+ }
275
+ const snap = await tx.get(q);
276
+ return snap.docs.map((doc) => doc.data());
277
+ }
278
+ };
279
+ }
280
+ function createBatchAdapter(db, collectionPath) {
281
+ const collectionRef = db.collection(collectionPath);
282
+ const batch = db.batch();
283
+ return {
284
+ setDoc(docId, data) {
285
+ batch.set(collectionRef.doc(docId), data);
286
+ },
287
+ updateDoc(docId, data) {
288
+ batch.update(collectionRef.doc(docId), data);
289
+ },
290
+ deleteDoc(docId) {
291
+ batch.delete(collectionRef.doc(docId));
292
+ },
293
+ async commit() {
294
+ await batch.commit();
295
+ }
296
+ };
297
+ }
298
+
299
+ // src/internal/pipeline-adapter.ts
300
+ var _Pipelines = null;
301
+ async function getPipelines() {
302
+ if (!_Pipelines) {
303
+ const mod = await import("@google-cloud/firestore");
304
+ _Pipelines = mod.Pipelines;
305
+ }
306
+ return _Pipelines;
307
+ }
308
+ function buildFilterExpression(P, filter) {
309
+ const { field: fieldName, op, value } = filter;
310
+ switch (op) {
311
+ case "==":
312
+ return P.equal(fieldName, value);
313
+ case "!=":
314
+ return P.notEqual(fieldName, value);
315
+ case "<":
316
+ return P.lessThan(fieldName, value);
317
+ case "<=":
318
+ return P.lessThanOrEqual(fieldName, value);
319
+ case ">":
320
+ return P.greaterThan(fieldName, value);
321
+ case ">=":
322
+ return P.greaterThanOrEqual(fieldName, value);
323
+ case "in":
324
+ return P.equalAny(fieldName, value);
325
+ case "not-in":
326
+ return P.notEqualAny(fieldName, value);
327
+ case "array-contains":
328
+ return P.arrayContains(fieldName, value);
329
+ case "array-contains-any":
330
+ return P.arrayContainsAny(fieldName, value);
331
+ default:
332
+ throw new Error(`Unsupported filter op for pipeline mode: ${op}`);
333
+ }
334
+ }
335
+ function createPipelineQueryAdapter(db, collectionPath) {
336
+ return {
337
+ async query(filters, options) {
338
+ const P = await getPipelines();
339
+ let pipeline = db.pipeline().collection(collectionPath);
340
+ if (filters.length === 1) {
341
+ pipeline = pipeline.where(buildFilterExpression(P, filters[0]));
342
+ } else if (filters.length > 1) {
343
+ const [first, second, ...rest] = filters.map((f) => buildFilterExpression(P, f));
344
+ pipeline = pipeline.where(P.and(first, second, ...rest));
345
+ }
346
+ if (options?.orderBy) {
347
+ const f = P.field(options.orderBy.field);
348
+ const ordering = options.orderBy.direction === "desc" ? f.descending() : f.ascending();
349
+ pipeline = pipeline.sort(ordering);
350
+ }
351
+ if (options?.limit !== void 0) {
352
+ pipeline = pipeline.limit(options.limit);
353
+ }
354
+ const snap = await pipeline.execute();
355
+ return snap.results.map((r) => r.data());
356
+ }
357
+ };
358
+ }
359
+
360
+ // src/transaction.ts
361
+ var import_firestore2 = require("@google-cloud/firestore");
362
+ var GraphTransactionImpl = class {
363
+ constructor(adapter, registry) {
364
+ this.adapter = adapter;
365
+ this.registry = registry;
366
+ }
367
+ async getNode(uid) {
368
+ const docId = computeNodeDocId(uid);
369
+ return this.adapter.getDoc(docId);
370
+ }
371
+ async getEdge(aUid, axbType, bUid) {
372
+ const docId = computeEdgeDocId(aUid, axbType, bUid);
373
+ return this.adapter.getDoc(docId);
374
+ }
375
+ async edgeExists(aUid, axbType, bUid) {
376
+ const record = await this.getEdge(aUid, axbType, bUid);
377
+ return record !== null;
378
+ }
379
+ async findEdges(params) {
380
+ const plan = buildEdgeQueryPlan(params);
381
+ if (plan.strategy === "get") {
382
+ const record = await this.adapter.getDoc(plan.docId);
383
+ return record ? [record] : [];
384
+ }
385
+ return this.adapter.query(plan.filters, plan.options);
386
+ }
387
+ async findNodes(params) {
388
+ const plan = buildNodeQueryPlan(params);
389
+ if (plan.strategy === "get") {
390
+ const record = await this.adapter.getDoc(plan.docId);
391
+ return record ? [record] : [];
392
+ }
393
+ return this.adapter.query(plan.filters, plan.options);
394
+ }
395
+ async putNode(aType, uid, data) {
396
+ if (this.registry) {
397
+ this.registry.validate(aType, NODE_RELATION, aType, data);
398
+ }
399
+ const docId = computeNodeDocId(uid);
400
+ const record = buildNodeRecord(aType, uid, data);
401
+ this.adapter.setDoc(docId, record);
402
+ }
403
+ async putEdge(aType, aUid, axbType, bType, bUid, data) {
404
+ if (this.registry) {
405
+ this.registry.validate(aType, axbType, bType, data);
406
+ }
407
+ const docId = computeEdgeDocId(aUid, axbType, bUid);
408
+ const record = buildEdgeRecord(aType, aUid, axbType, bType, bUid, data);
409
+ this.adapter.setDoc(docId, record);
410
+ }
411
+ async updateNode(uid, data) {
412
+ const docId = computeNodeDocId(uid);
413
+ this.adapter.updateDoc(docId, {
414
+ ...data,
415
+ updatedAt: import_firestore2.FieldValue.serverTimestamp()
416
+ });
417
+ }
418
+ async removeNode(uid) {
419
+ const docId = computeNodeDocId(uid);
420
+ this.adapter.deleteDoc(docId);
421
+ }
422
+ async removeEdge(aUid, axbType, bUid) {
423
+ const docId = computeEdgeDocId(aUid, axbType, bUid);
424
+ this.adapter.deleteDoc(docId);
425
+ }
426
+ };
427
+
428
+ // src/batch.ts
429
+ var import_firestore3 = require("@google-cloud/firestore");
430
+ var GraphBatchImpl = class {
431
+ constructor(adapter, registry) {
432
+ this.adapter = adapter;
433
+ this.registry = registry;
434
+ }
435
+ async putNode(aType, uid, data) {
436
+ if (this.registry) {
437
+ this.registry.validate(aType, NODE_RELATION, aType, data);
438
+ }
439
+ const docId = computeNodeDocId(uid);
440
+ const record = buildNodeRecord(aType, uid, data);
441
+ this.adapter.setDoc(docId, record);
442
+ }
443
+ async putEdge(aType, aUid, axbType, bType, bUid, data) {
444
+ if (this.registry) {
445
+ this.registry.validate(aType, axbType, bType, data);
446
+ }
447
+ const docId = computeEdgeDocId(aUid, axbType, bUid);
448
+ const record = buildEdgeRecord(aType, aUid, axbType, bType, bUid, data);
449
+ this.adapter.setDoc(docId, record);
450
+ }
451
+ async updateNode(uid, data) {
452
+ const docId = computeNodeDocId(uid);
453
+ this.adapter.updateDoc(docId, {
454
+ ...data,
455
+ updatedAt: import_firestore3.FieldValue.serverTimestamp()
456
+ });
457
+ }
458
+ async removeNode(uid) {
459
+ const docId = computeNodeDocId(uid);
460
+ this.adapter.deleteDoc(docId);
461
+ }
462
+ async removeEdge(aUid, axbType, bUid) {
463
+ const docId = computeEdgeDocId(aUid, axbType, bUid);
464
+ this.adapter.deleteDoc(docId);
465
+ }
466
+ async commit() {
467
+ await this.adapter.commit();
468
+ }
469
+ };
470
+
471
+ // src/bulk.ts
472
+ var MAX_BATCH_SIZE = 500;
473
+ var DEFAULT_MAX_RETRIES = 3;
474
+ var BASE_DELAY_MS = 200;
475
+ function sleep(ms) {
476
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
477
+ }
478
+ function chunk(arr, size) {
479
+ const chunks = [];
480
+ for (let i = 0; i < arr.length; i += size) {
481
+ chunks.push(arr.slice(i, i + size));
482
+ }
483
+ return chunks;
484
+ }
485
+ async function bulkDeleteDocIds(db, collectionPath, docIds, options) {
486
+ if (docIds.length === 0) {
487
+ return { deleted: 0, batches: 0, errors: [] };
488
+ }
489
+ const batchSize = Math.min(options?.batchSize ?? MAX_BATCH_SIZE, MAX_BATCH_SIZE);
490
+ const maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES;
491
+ const onProgress = options?.onProgress;
492
+ const chunks = chunk(docIds, batchSize);
493
+ const errors = [];
494
+ let deleted = 0;
495
+ let completedBatches = 0;
496
+ for (let i = 0; i < chunks.length; i++) {
497
+ const ids = chunks[i];
498
+ let committed = false;
499
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
500
+ try {
501
+ const batch = db.batch();
502
+ const collectionRef = db.collection(collectionPath);
503
+ for (const id of ids) {
504
+ batch.delete(collectionRef.doc(id));
505
+ }
506
+ await batch.commit();
507
+ committed = true;
508
+ deleted += ids.length;
509
+ break;
510
+ } catch (err) {
511
+ if (attempt < maxRetries) {
512
+ const delay = BASE_DELAY_MS * Math.pow(2, attempt);
513
+ await sleep(delay);
514
+ } else {
515
+ errors.push({
516
+ batchIndex: i,
517
+ error: err instanceof Error ? err : new Error(String(err)),
518
+ operationCount: ids.length
519
+ });
520
+ }
521
+ }
522
+ }
523
+ if (committed) {
524
+ completedBatches++;
525
+ }
526
+ if (onProgress) {
527
+ onProgress({
528
+ completedBatches,
529
+ totalBatches: chunks.length,
530
+ deletedSoFar: deleted
531
+ });
532
+ }
533
+ }
534
+ return { deleted, batches: completedBatches, errors };
535
+ }
536
+ async function bulkRemoveEdges(db, collectionPath, reader, params, options) {
537
+ const edges = await reader.findEdges(params);
538
+ const docIds = edges.map((e) => computeEdgeDocId(e.aUid, e.axbType, e.bUid));
539
+ return bulkDeleteDocIds(db, collectionPath, docIds, options);
540
+ }
541
+ async function removeNodeCascade(db, collectionPath, reader, uid, options) {
542
+ const [outgoingRaw, incomingRaw] = await Promise.all([
543
+ reader.findEdges({ aUid: uid }),
544
+ reader.findEdges({ bUid: uid })
545
+ ]);
546
+ const outgoing = outgoingRaw.filter((e) => e.axbType !== NODE_RELATION);
547
+ const incoming = incomingRaw.filter((e) => e.axbType !== NODE_RELATION);
548
+ const edgeDocIdSet = /* @__PURE__ */ new Set();
549
+ const allEdges = [];
550
+ for (const edge of [...outgoing, ...incoming]) {
551
+ const docId = computeEdgeDocId(edge.aUid, edge.axbType, edge.bUid);
552
+ if (!edgeDocIdSet.has(docId)) {
553
+ edgeDocIdSet.add(docId);
554
+ allEdges.push(edge);
555
+ }
556
+ }
557
+ const edgeDocIds = allEdges.map((e) => computeEdgeDocId(e.aUid, e.axbType, e.bUid));
558
+ const nodeDocId = computeNodeDocId(uid);
559
+ const allDocIds = [...edgeDocIds, nodeDocId];
560
+ const batchSize = Math.min(options?.batchSize ?? MAX_BATCH_SIZE, MAX_BATCH_SIZE);
561
+ const result = await bulkDeleteDocIds(db, collectionPath, allDocIds, {
562
+ ...options,
563
+ batchSize
564
+ });
565
+ const totalChunks = Math.ceil(allDocIds.length / batchSize);
566
+ const nodeChunkIndex = totalChunks - 1;
567
+ const nodeDeleted = !result.errors.some((e) => e.batchIndex === nodeChunkIndex);
568
+ return {
569
+ ...result,
570
+ edgesDeleted: nodeDeleted ? result.deleted - 1 : result.deleted,
571
+ nodeDeleted
572
+ };
573
+ }
574
+
575
+ // src/dynamic-registry.ts
576
+ var import_node_crypto2 = require("crypto");
577
+
578
+ // src/json-schema.ts
579
+ var import_ajv = __toESM(require("ajv"), 1);
580
+ var ajv = new import_ajv.default({ allErrors: true, strict: false });
581
+ function compileSchema(schema, label) {
582
+ const validate = ajv.compile(schema);
583
+ return (data) => {
584
+ if (!validate(data)) {
585
+ const errors = validate.errors ?? [];
586
+ const messages = errors.map((err) => `${err.instancePath || "/"}${err.message ? ": " + err.message : ""}`).join("; ");
587
+ throw new ValidationError(
588
+ `Data validation failed${label ? " for " + label : ""}: ${messages}`,
589
+ errors
590
+ );
591
+ }
592
+ };
593
+ }
594
+ function jsonSchemaToFieldMeta(schema) {
595
+ if (!schema || schema.type !== "object" || !schema.properties) return [];
596
+ const requiredSet = new Set(
597
+ Array.isArray(schema.required) ? schema.required : []
598
+ );
599
+ return Object.entries(schema.properties).map(
600
+ ([name, prop]) => propertyToFieldMeta(name, prop, requiredSet.has(name))
601
+ );
602
+ }
603
+ function propertyToFieldMeta(name, prop, required) {
604
+ if (!prop) return { name, type: "unknown", required };
605
+ if (Array.isArray(prop.enum)) {
606
+ return {
607
+ name,
608
+ type: "enum",
609
+ required,
610
+ enumValues: prop.enum,
611
+ description: prop.description
612
+ };
613
+ }
614
+ if (Array.isArray(prop.oneOf) || Array.isArray(prop.anyOf)) {
615
+ const variants = prop.oneOf ?? prop.anyOf;
616
+ const nonNull = variants.filter((v) => v.type !== "null");
617
+ if (nonNull.length === 1) {
618
+ return propertyToFieldMeta(name, nonNull[0], false);
619
+ }
620
+ return { name, type: "unknown", required, description: prop.description };
621
+ }
622
+ const type = prop.type;
623
+ if (type === "string") {
624
+ return {
625
+ name,
626
+ type: "string",
627
+ required,
628
+ minLength: prop.minLength,
629
+ maxLength: prop.maxLength,
630
+ pattern: prop.pattern,
631
+ description: prop.description
632
+ };
633
+ }
634
+ if (type === "number" || type === "integer") {
635
+ return {
636
+ name,
637
+ type: "number",
638
+ required,
639
+ min: prop.minimum,
640
+ max: prop.maximum,
641
+ isInt: type === "integer" ? true : void 0,
642
+ description: prop.description
643
+ };
644
+ }
645
+ if (type === "boolean") {
646
+ return { name, type: "boolean", required, description: prop.description };
647
+ }
648
+ if (type === "array") {
649
+ const itemMeta = prop.items ? propertyToFieldMeta("item", prop.items, true) : void 0;
650
+ return {
651
+ name,
652
+ type: "array",
653
+ required,
654
+ itemMeta,
655
+ description: prop.description
656
+ };
657
+ }
658
+ if (type === "object") {
659
+ return {
660
+ name,
661
+ type: "object",
662
+ required,
663
+ fields: jsonSchemaToFieldMeta(prop),
664
+ description: prop.description
665
+ };
666
+ }
667
+ return { name, type: "unknown", required, description: prop.description };
668
+ }
669
+
670
+ // src/registry.ts
671
+ function tripleKey(aType, axbType, bType) {
672
+ return `${aType}:${axbType}:${bType}`;
673
+ }
674
+ function createRegistry(input) {
675
+ const map = /* @__PURE__ */ new Map();
676
+ let entries;
677
+ if (Array.isArray(input)) {
678
+ entries = input;
679
+ } else {
680
+ entries = discoveryToEntries(input);
681
+ }
682
+ const entryList = Object.freeze([...entries]);
683
+ for (const entry of entries) {
684
+ const key = tripleKey(entry.aType, entry.axbType, entry.bType);
685
+ const validator = entry.jsonSchema ? compileSchema(entry.jsonSchema, `(${entry.aType}) -[${entry.axbType}]-> (${entry.bType})`) : void 0;
686
+ map.set(key, { entry, validate: validator });
687
+ }
688
+ return {
689
+ lookup(aType, axbType, bType) {
690
+ return map.get(tripleKey(aType, axbType, bType))?.entry;
691
+ },
692
+ validate(aType, axbType, bType, data) {
693
+ const rec = map.get(tripleKey(aType, axbType, bType));
694
+ if (!rec) {
695
+ throw new RegistryViolationError(aType, axbType, bType);
696
+ }
697
+ if (rec.validate) {
698
+ try {
699
+ rec.validate(data);
700
+ } catch (err) {
701
+ if (err instanceof ValidationError) throw err;
702
+ throw new ValidationError(
703
+ `Data validation failed for (${aType}) -[${axbType}]-> (${bType})`,
704
+ err
705
+ );
706
+ }
707
+ }
708
+ },
709
+ entries() {
710
+ return entryList;
711
+ }
712
+ };
713
+ }
714
+ function discoveryToEntries(discovery) {
715
+ const entries = [];
716
+ for (const [name, entity] of discovery.nodes) {
717
+ entries.push({
718
+ aType: name,
719
+ axbType: NODE_RELATION,
720
+ bType: name,
721
+ jsonSchema: entity.schema,
722
+ description: entity.description,
723
+ titleField: entity.titleField,
724
+ subtitleField: entity.subtitleField
725
+ });
726
+ }
727
+ for (const [axbType, entity] of discovery.edges) {
728
+ const topology = entity.topology;
729
+ if (!topology) continue;
730
+ const fromTypes = Array.isArray(topology.from) ? topology.from : [topology.from];
731
+ const toTypes = Array.isArray(topology.to) ? topology.to : [topology.to];
732
+ for (const aType of fromTypes) {
733
+ for (const bType of toTypes) {
734
+ entries.push({
735
+ aType,
736
+ axbType,
737
+ bType,
738
+ jsonSchema: entity.schema,
739
+ description: entity.description,
740
+ inverseLabel: topology.inverseLabel,
741
+ titleField: entity.titleField,
742
+ subtitleField: entity.subtitleField
743
+ });
744
+ }
745
+ }
746
+ }
747
+ return entries;
748
+ }
749
+
750
+ // src/dynamic-registry.ts
751
+ var META_NODE_TYPE = "nodeType";
752
+ var META_EDGE_TYPE = "edgeType";
753
+ var NODE_TYPE_SCHEMA = {
754
+ type: "object",
755
+ required: ["name", "jsonSchema"],
756
+ properties: {
757
+ name: { type: "string", minLength: 1 },
758
+ jsonSchema: { type: "object" },
759
+ description: { type: "string" },
760
+ titleField: { type: "string" },
761
+ subtitleField: { type: "string" },
762
+ viewTemplate: { type: "string" },
763
+ viewCss: { type: "string" }
764
+ },
765
+ additionalProperties: false
766
+ };
767
+ var EDGE_TYPE_SCHEMA = {
768
+ type: "object",
769
+ required: ["name", "from", "to"],
770
+ properties: {
771
+ name: { type: "string", minLength: 1 },
772
+ from: {
773
+ oneOf: [
774
+ { type: "string", minLength: 1 },
775
+ { type: "array", items: { type: "string", minLength: 1 }, minItems: 1 }
776
+ ]
777
+ },
778
+ to: {
779
+ oneOf: [
780
+ { type: "string", minLength: 1 },
781
+ { type: "array", items: { type: "string", minLength: 1 }, minItems: 1 }
782
+ ]
783
+ },
784
+ jsonSchema: { type: "object" },
785
+ inverseLabel: { type: "string" },
786
+ description: { type: "string" },
787
+ titleField: { type: "string" },
788
+ subtitleField: { type: "string" },
789
+ viewTemplate: { type: "string" },
790
+ viewCss: { type: "string" }
791
+ },
792
+ additionalProperties: false
793
+ };
794
+ var BOOTSTRAP_ENTRIES = [
795
+ {
796
+ aType: META_NODE_TYPE,
797
+ axbType: NODE_RELATION,
798
+ bType: META_NODE_TYPE,
799
+ jsonSchema: NODE_TYPE_SCHEMA,
800
+ description: "Meta-type: defines a node type"
801
+ },
802
+ {
803
+ aType: META_EDGE_TYPE,
804
+ axbType: NODE_RELATION,
805
+ bType: META_EDGE_TYPE,
806
+ jsonSchema: EDGE_TYPE_SCHEMA,
807
+ description: "Meta-type: defines an edge type"
808
+ }
809
+ ];
810
+ function createBootstrapRegistry() {
811
+ return createRegistry([...BOOTSTRAP_ENTRIES]);
812
+ }
813
+ function generateDeterministicUid(metaType, name) {
814
+ const hash = (0, import_node_crypto2.createHash)("sha256").update(`${metaType}:${name}`).digest("base64url");
815
+ return hash.slice(0, 21);
816
+ }
817
+ async function createRegistryFromGraph(reader) {
818
+ const [nodeTypes, edgeTypes] = await Promise.all([
819
+ reader.findNodes({ aType: META_NODE_TYPE }),
820
+ reader.findNodes({ aType: META_EDGE_TYPE })
821
+ ]);
822
+ const entries = [...BOOTSTRAP_ENTRIES];
823
+ for (const record of nodeTypes) {
824
+ const data = record.data;
825
+ entries.push({
826
+ aType: data.name,
827
+ axbType: NODE_RELATION,
828
+ bType: data.name,
829
+ jsonSchema: data.jsonSchema,
830
+ description: data.description,
831
+ titleField: data.titleField,
832
+ subtitleField: data.subtitleField
833
+ });
834
+ }
835
+ for (const record of edgeTypes) {
836
+ const data = record.data;
837
+ const fromTypes = Array.isArray(data.from) ? data.from : [data.from];
838
+ const toTypes = Array.isArray(data.to) ? data.to : [data.to];
839
+ for (const aType of fromTypes) {
840
+ for (const bType of toTypes) {
841
+ entries.push({
842
+ aType,
843
+ axbType: data.name,
844
+ bType,
845
+ jsonSchema: data.jsonSchema,
846
+ description: data.description,
847
+ inverseLabel: data.inverseLabel,
848
+ titleField: data.titleField,
849
+ subtitleField: data.subtitleField
850
+ });
851
+ }
852
+ }
853
+ }
854
+ return createRegistry(entries);
855
+ }
856
+
857
+ // src/client.ts
858
+ var _standardModeWarned = false;
859
+ var RESERVED_TYPE_NAMES = /* @__PURE__ */ new Set([META_NODE_TYPE, META_EDGE_TYPE]);
860
+ var GraphClientImpl = class {
861
+ constructor(db, collectionPath, options) {
862
+ this.db = db;
863
+ this.adapter = createFirestoreAdapter(db, collectionPath);
864
+ if (options?.registry && options?.registryMode) {
865
+ throw new DynamicRegistryError(
866
+ 'Cannot provide both "registry" and "registryMode". Use "registry" for static mode or "registryMode" for dynamic mode.'
867
+ );
868
+ }
869
+ if (options?.registryMode) {
870
+ this.dynamicConfig = options.registryMode;
871
+ this.bootstrapRegistry = createBootstrapRegistry();
872
+ const metaCollectionPath = options.registryMode.collection;
873
+ if (metaCollectionPath && metaCollectionPath !== collectionPath) {
874
+ this.metaAdapter = createFirestoreAdapter(db, metaCollectionPath);
875
+ }
876
+ } else {
877
+ this.staticRegistry = options?.registry;
878
+ }
879
+ const requestedMode = options?.queryMode ?? "pipeline";
880
+ const isEmulator = !!process.env.FIRESTORE_EMULATOR_HOST;
881
+ if (isEmulator) {
882
+ this.queryMode = "standard";
883
+ } else {
884
+ this.queryMode = requestedMode;
885
+ }
886
+ if (this.queryMode === "standard" && !isEmulator && requestedMode === "standard" && !_standardModeWarned) {
887
+ _standardModeWarned = true;
888
+ console.warn(
889
+ "[firegraph] Standard query mode enabled. This is NOT recommended for production:\n - Enterprise Firestore: data.* filters cause full collection scans (high billing)\n - Standard Firestore: data.* filters without composite indexes will fail\n See: https://github.com/typicalday/firegraph#query-modes"
890
+ );
891
+ }
892
+ if (this.queryMode === "pipeline") {
893
+ this.pipelineAdapter = createPipelineQueryAdapter(db, collectionPath);
894
+ if (this.metaAdapter) {
895
+ this.metaPipelineAdapter = createPipelineQueryAdapter(
896
+ db,
897
+ options.registryMode.collection
898
+ );
899
+ }
900
+ }
901
+ }
902
+ adapter;
903
+ pipelineAdapter;
904
+ queryMode;
905
+ // Static mode
906
+ staticRegistry;
907
+ // Dynamic mode
908
+ dynamicConfig;
909
+ bootstrapRegistry;
910
+ dynamicRegistry;
911
+ metaAdapter;
912
+ metaPipelineAdapter;
913
+ // ---------------------------------------------------------------------------
914
+ // Registry routing
915
+ // ---------------------------------------------------------------------------
916
+ /**
917
+ * Get the appropriate registry for validating a write to the given type.
918
+ *
919
+ * - Static mode: returns staticRegistry (or undefined if none set)
920
+ * - Dynamic mode:
921
+ * - Meta-types (nodeType, edgeType): validated against bootstrapRegistry
922
+ * - Domain types: validated against dynamicRegistry (falls back to
923
+ * bootstrapRegistry which rejects unknown types)
924
+ */
925
+ getRegistryForType(aType) {
926
+ if (!this.dynamicConfig) return this.staticRegistry;
927
+ if (aType === META_NODE_TYPE || aType === META_EDGE_TYPE) {
928
+ return this.bootstrapRegistry;
929
+ }
930
+ return this.dynamicRegistry ?? this.bootstrapRegistry;
931
+ }
932
+ /**
933
+ * Get the Firestore adapter for writing the given type.
934
+ * Meta-types route to metaAdapter if a separate collection is configured.
935
+ */
936
+ getAdapterForType(aType) {
937
+ if (this.metaAdapter && (aType === META_NODE_TYPE || aType === META_EDGE_TYPE)) {
938
+ return this.metaAdapter;
939
+ }
940
+ return this.adapter;
941
+ }
942
+ /**
943
+ * Get the combined registry for transaction/batch context.
944
+ * In static mode, returns staticRegistry.
945
+ * In dynamic mode, returns dynamicRegistry (which includes bootstrap entries)
946
+ * or bootstrapRegistry if not yet reloaded.
947
+ */
948
+ getCombinedRegistry() {
949
+ if (!this.dynamicConfig) return this.staticRegistry;
950
+ return this.dynamicRegistry ?? this.bootstrapRegistry;
951
+ }
952
+ // ---------------------------------------------------------------------------
953
+ // Query dispatch
954
+ // ---------------------------------------------------------------------------
955
+ /**
956
+ * Dispatch a query to the appropriate adapter based on queryMode.
957
+ * Pipeline queries use the PipelineQueryAdapter; standard queries
958
+ * use the FirestoreAdapter.
959
+ */
960
+ executeQuery(filters, options) {
961
+ if (this.pipelineAdapter) {
962
+ return this.pipelineAdapter.query(filters, options);
963
+ }
964
+ return this.adapter.query(filters, options);
965
+ }
966
+ // ---------------------------------------------------------------------------
967
+ // GraphReader
968
+ // ---------------------------------------------------------------------------
969
+ async getNode(uid) {
970
+ const docId = computeNodeDocId(uid);
971
+ return this.adapter.getDoc(docId);
972
+ }
973
+ async getEdge(aUid, axbType, bUid) {
974
+ const docId = computeEdgeDocId(aUid, axbType, bUid);
975
+ return this.adapter.getDoc(docId);
976
+ }
977
+ async edgeExists(aUid, axbType, bUid) {
978
+ const record = await this.getEdge(aUid, axbType, bUid);
979
+ return record !== null;
980
+ }
981
+ async findEdges(params) {
982
+ const plan = buildEdgeQueryPlan(params);
983
+ if (plan.strategy === "get") {
984
+ const record = await this.adapter.getDoc(plan.docId);
985
+ return record ? [record] : [];
986
+ }
987
+ return this.executeQuery(plan.filters, plan.options);
988
+ }
989
+ async findNodes(params) {
990
+ const plan = buildNodeQueryPlan(params);
991
+ if (plan.strategy === "get") {
992
+ const record = await this.adapter.getDoc(plan.docId);
993
+ return record ? [record] : [];
994
+ }
995
+ return this.executeQuery(plan.filters, plan.options);
996
+ }
997
+ // ---------------------------------------------------------------------------
998
+ // GraphWriter
999
+ // ---------------------------------------------------------------------------
1000
+ async putNode(aType, uid, data) {
1001
+ const registry = this.getRegistryForType(aType);
1002
+ if (registry) {
1003
+ registry.validate(aType, NODE_RELATION, aType, data);
1004
+ }
1005
+ const adapter = this.getAdapterForType(aType);
1006
+ const docId = computeNodeDocId(uid);
1007
+ const record = buildNodeRecord(aType, uid, data);
1008
+ await adapter.setDoc(docId, record);
1009
+ }
1010
+ async putEdge(aType, aUid, axbType, bType, bUid, data) {
1011
+ const registry = this.getRegistryForType(aType);
1012
+ if (registry) {
1013
+ registry.validate(aType, axbType, bType, data);
1014
+ }
1015
+ const adapter = this.getAdapterForType(aType);
1016
+ const docId = computeEdgeDocId(aUid, axbType, bUid);
1017
+ const record = buildEdgeRecord(aType, aUid, axbType, bType, bUid, data);
1018
+ await adapter.setDoc(docId, record);
1019
+ }
1020
+ async updateNode(uid, data) {
1021
+ const docId = computeNodeDocId(uid);
1022
+ await this.adapter.updateDoc(docId, {
1023
+ ...data,
1024
+ updatedAt: import_firestore4.FieldValue.serverTimestamp()
1025
+ });
1026
+ }
1027
+ async removeNode(uid) {
1028
+ const docId = computeNodeDocId(uid);
1029
+ await this.adapter.deleteDoc(docId);
1030
+ }
1031
+ async removeEdge(aUid, axbType, bUid) {
1032
+ const docId = computeEdgeDocId(aUid, axbType, bUid);
1033
+ await this.adapter.deleteDoc(docId);
1034
+ }
1035
+ // ---------------------------------------------------------------------------
1036
+ // Transactions & Batches
1037
+ // ---------------------------------------------------------------------------
1038
+ async runTransaction(fn) {
1039
+ return this.db.runTransaction(async (firestoreTx) => {
1040
+ const adapter = createTransactionAdapter(
1041
+ this.db,
1042
+ this.adapter.collectionPath,
1043
+ firestoreTx
1044
+ );
1045
+ const graphTx = new GraphTransactionImpl(adapter, this.getCombinedRegistry());
1046
+ return fn(graphTx);
1047
+ });
1048
+ }
1049
+ batch() {
1050
+ const adapter = createBatchAdapter(this.db, this.adapter.collectionPath);
1051
+ return new GraphBatchImpl(adapter, this.getCombinedRegistry());
1052
+ }
1053
+ // ---------------------------------------------------------------------------
1054
+ // Bulk operations
1055
+ // ---------------------------------------------------------------------------
1056
+ async removeNodeCascade(uid, options) {
1057
+ return removeNodeCascade(this.db, this.adapter.collectionPath, this, uid, options);
1058
+ }
1059
+ async bulkRemoveEdges(params, options) {
1060
+ return bulkRemoveEdges(this.db, this.adapter.collectionPath, this, params, options);
1061
+ }
1062
+ // ---------------------------------------------------------------------------
1063
+ // Dynamic registry methods
1064
+ // ---------------------------------------------------------------------------
1065
+ async defineNodeType(name, jsonSchema, description, options) {
1066
+ if (!this.dynamicConfig) {
1067
+ throw new DynamicRegistryError(
1068
+ 'defineNodeType() is only available in dynamic registry mode. Pass registryMode: { mode: "dynamic" } to createGraphClient().'
1069
+ );
1070
+ }
1071
+ if (RESERVED_TYPE_NAMES.has(name)) {
1072
+ throw new DynamicRegistryError(
1073
+ `Cannot define type "${name}": this name is reserved for the meta-registry.`
1074
+ );
1075
+ }
1076
+ const uid = generateDeterministicUid(META_NODE_TYPE, name);
1077
+ const data = { name, jsonSchema };
1078
+ if (description !== void 0) data.description = description;
1079
+ if (options?.titleField !== void 0) data.titleField = options.titleField;
1080
+ if (options?.subtitleField !== void 0) data.subtitleField = options.subtitleField;
1081
+ if (options?.viewTemplate !== void 0) data.viewTemplate = options.viewTemplate;
1082
+ if (options?.viewCss !== void 0) data.viewCss = options.viewCss;
1083
+ await this.putNode(META_NODE_TYPE, uid, data);
1084
+ }
1085
+ async defineEdgeType(name, topology, jsonSchema, description, options) {
1086
+ if (!this.dynamicConfig) {
1087
+ throw new DynamicRegistryError(
1088
+ 'defineEdgeType() is only available in dynamic registry mode. Pass registryMode: { mode: "dynamic" } to createGraphClient().'
1089
+ );
1090
+ }
1091
+ if (RESERVED_TYPE_NAMES.has(name)) {
1092
+ throw new DynamicRegistryError(
1093
+ `Cannot define type "${name}": this name is reserved for the meta-registry.`
1094
+ );
1095
+ }
1096
+ const uid = generateDeterministicUid(META_EDGE_TYPE, name);
1097
+ const data = {
1098
+ name,
1099
+ from: topology.from,
1100
+ to: topology.to
1101
+ };
1102
+ if (jsonSchema !== void 0) data.jsonSchema = jsonSchema;
1103
+ if (topology.inverseLabel !== void 0) data.inverseLabel = topology.inverseLabel;
1104
+ if (description !== void 0) data.description = description;
1105
+ if (options?.titleField !== void 0) data.titleField = options.titleField;
1106
+ if (options?.subtitleField !== void 0) data.subtitleField = options.subtitleField;
1107
+ if (options?.viewTemplate !== void 0) data.viewTemplate = options.viewTemplate;
1108
+ if (options?.viewCss !== void 0) data.viewCss = options.viewCss;
1109
+ await this.putNode(META_EDGE_TYPE, uid, data);
1110
+ }
1111
+ async reloadRegistry() {
1112
+ if (!this.dynamicConfig) {
1113
+ throw new DynamicRegistryError(
1114
+ 'reloadRegistry() is only available in dynamic registry mode. Pass registryMode: { mode: "dynamic" } to createGraphClient().'
1115
+ );
1116
+ }
1117
+ const reader = this.createMetaReader();
1118
+ this.dynamicRegistry = await createRegistryFromGraph(reader);
1119
+ }
1120
+ /**
1121
+ * Create a GraphReader for the meta-collection.
1122
+ * If meta-collection is the same as main collection, returns `this`.
1123
+ * If separate, creates a lightweight reader wrapping the meta adapter.
1124
+ */
1125
+ createMetaReader() {
1126
+ if (!this.metaAdapter) return this;
1127
+ const adapter = this.metaAdapter;
1128
+ const pipelineAdapter = this.metaPipelineAdapter;
1129
+ const executeMetaQuery = (filters, options) => {
1130
+ if (pipelineAdapter) return pipelineAdapter.query(filters, options);
1131
+ return adapter.query(filters, options);
1132
+ };
1133
+ return {
1134
+ async getNode(uid) {
1135
+ return adapter.getDoc(computeNodeDocId(uid));
1136
+ },
1137
+ async getEdge(aUid, axbType, bUid) {
1138
+ return adapter.getDoc(computeEdgeDocId(aUid, axbType, bUid));
1139
+ },
1140
+ async edgeExists(aUid, axbType, bUid) {
1141
+ const record = await adapter.getDoc(computeEdgeDocId(aUid, axbType, bUid));
1142
+ return record !== null;
1143
+ },
1144
+ async findEdges(params) {
1145
+ const plan = buildEdgeQueryPlan(params);
1146
+ if (plan.strategy === "get") {
1147
+ const record = await adapter.getDoc(plan.docId);
1148
+ return record ? [record] : [];
1149
+ }
1150
+ return executeMetaQuery(plan.filters, plan.options);
1151
+ },
1152
+ async findNodes(params) {
1153
+ const plan = buildNodeQueryPlan(params);
1154
+ if (plan.strategy === "get") {
1155
+ const record = await adapter.getDoc(plan.docId);
1156
+ return record ? [record] : [];
1157
+ }
1158
+ return executeMetaQuery(plan.filters, plan.options);
1159
+ }
1160
+ };
1161
+ }
1162
+ };
1163
+ function createGraphClient(db, collectionPath, options) {
1164
+ return new GraphClientImpl(db, collectionPath, options);
1165
+ }
1166
+
1167
+ // src/id.ts
1168
+ var import_nanoid = require("nanoid");
1169
+ function generateId() {
1170
+ return (0, import_nanoid.nanoid)();
1171
+ }
1172
+
1173
+ // src/traverse.ts
1174
+ var DEFAULT_LIMIT = 10;
1175
+ var DEFAULT_MAX_READS = 100;
1176
+ var DEFAULT_CONCURRENCY = 5;
1177
+ var Semaphore = class {
1178
+ constructor(slots) {
1179
+ this.slots = slots;
1180
+ }
1181
+ queue = [];
1182
+ active = 0;
1183
+ async acquire() {
1184
+ if (this.active < this.slots) {
1185
+ this.active++;
1186
+ return;
1187
+ }
1188
+ return new Promise((resolve2) => {
1189
+ this.queue.push(resolve2);
1190
+ });
1191
+ }
1192
+ release() {
1193
+ this.active--;
1194
+ const next = this.queue.shift();
1195
+ if (next) {
1196
+ this.active++;
1197
+ next();
1198
+ }
1199
+ }
1200
+ };
1201
+ var TraversalBuilderImpl = class {
1202
+ constructor(reader, startUid) {
1203
+ this.reader = reader;
1204
+ this.startUid = startUid;
1205
+ }
1206
+ hops = [];
1207
+ follow(axbType, options) {
1208
+ this.hops.push({ axbType, ...options });
1209
+ return this;
1210
+ }
1211
+ async run(options) {
1212
+ if (this.hops.length === 0) {
1213
+ throw new TraversalError("Traversal requires at least one follow() hop");
1214
+ }
1215
+ const maxReads = options?.maxReads ?? DEFAULT_MAX_READS;
1216
+ const concurrency = options?.concurrency ?? DEFAULT_CONCURRENCY;
1217
+ const returnIntermediates = options?.returnIntermediates ?? false;
1218
+ const semaphore = new Semaphore(concurrency);
1219
+ let totalReads = 0;
1220
+ let truncated = false;
1221
+ let sourceUids = [this.startUid];
1222
+ const hopResults = [];
1223
+ for (let depth = 0; depth < this.hops.length; depth++) {
1224
+ const hop = this.hops[depth];
1225
+ if (sourceUids.length === 0) {
1226
+ hopResults.push({
1227
+ axbType: hop.axbType,
1228
+ depth,
1229
+ edges: [],
1230
+ sourceCount: 0,
1231
+ truncated: false
1232
+ });
1233
+ continue;
1234
+ }
1235
+ const hopEdges = [];
1236
+ const sourceCount = sourceUids.length;
1237
+ let hopTruncated = false;
1238
+ const tasks = sourceUids.map((uid) => async () => {
1239
+ if (totalReads >= maxReads) {
1240
+ hopTruncated = true;
1241
+ return;
1242
+ }
1243
+ await semaphore.acquire();
1244
+ try {
1245
+ if (totalReads >= maxReads) {
1246
+ hopTruncated = true;
1247
+ return;
1248
+ }
1249
+ totalReads++;
1250
+ const direction2 = hop.direction ?? "forward";
1251
+ const params = { axbType: hop.axbType };
1252
+ if (direction2 === "forward") {
1253
+ params.aUid = uid;
1254
+ if (hop.bType) params.bType = hop.bType;
1255
+ } else {
1256
+ params.bUid = uid;
1257
+ if (hop.aType) params.aType = hop.aType;
1258
+ }
1259
+ if (direction2 === "forward" && hop.aType) {
1260
+ params.aType = hop.aType;
1261
+ }
1262
+ if (direction2 === "reverse" && hop.bType) {
1263
+ params.bType = hop.bType;
1264
+ }
1265
+ if (hop.orderBy) params.orderBy = hop.orderBy;
1266
+ const limit = hop.limit ?? DEFAULT_LIMIT;
1267
+ if (!hop.filter) {
1268
+ params.limit = limit;
1269
+ }
1270
+ let edges = await this.reader.findEdges(params);
1271
+ if (hop.filter) {
1272
+ edges = edges.filter(hop.filter);
1273
+ edges = edges.slice(0, limit);
1274
+ }
1275
+ hopEdges.push(...edges);
1276
+ } finally {
1277
+ semaphore.release();
1278
+ }
1279
+ });
1280
+ await Promise.all(tasks.map((task) => task()));
1281
+ hopResults.push({
1282
+ axbType: hop.axbType,
1283
+ depth,
1284
+ edges: returnIntermediates ? [...hopEdges] : hopEdges,
1285
+ sourceCount,
1286
+ truncated: hopTruncated
1287
+ });
1288
+ if (hopTruncated) {
1289
+ truncated = true;
1290
+ }
1291
+ const direction = hop.direction ?? "forward";
1292
+ sourceUids = [...new Set(
1293
+ hopEdges.map((e) => direction === "forward" ? e.bUid : e.aUid)
1294
+ )];
1295
+ }
1296
+ const lastHop = hopResults[hopResults.length - 1];
1297
+ return {
1298
+ nodes: lastHop.edges,
1299
+ hops: hopResults,
1300
+ totalReads,
1301
+ truncated
1302
+ };
1303
+ }
1304
+ };
1305
+ function createTraversal(reader, startUid) {
1306
+ return new TraversalBuilderImpl(reader, startUid);
1307
+ }
1308
+
1309
+ // src/views.ts
1310
+ function sanitizeTagPart(s) {
1311
+ return s.toLowerCase().replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
1312
+ }
1313
+ function getCustomElements() {
1314
+ const g = globalThis;
1315
+ if (g.customElements && typeof g.customElements.define === "function") {
1316
+ return g.customElements;
1317
+ }
1318
+ return null;
1319
+ }
1320
+ function resilientView(ViewClass, tagName) {
1321
+ const g = globalThis;
1322
+ if (!g.HTMLElement) return ViewClass;
1323
+ const Base = g.HTMLElement;
1324
+ const Wrapped = class extends ViewClass {
1325
+ connectedCallback() {
1326
+ try {
1327
+ super.connectedCallback?.();
1328
+ } catch (err) {
1329
+ console.warn(`[firegraph] <${tagName}> connectedCallback error:`, err);
1330
+ this._showError(err);
1331
+ }
1332
+ }
1333
+ disconnectedCallback() {
1334
+ try {
1335
+ super.disconnectedCallback?.();
1336
+ } catch (err) {
1337
+ console.warn(`[firegraph] <${tagName}> disconnectedCallback error:`, err);
1338
+ }
1339
+ }
1340
+ set data(v) {
1341
+ try {
1342
+ super.data = v;
1343
+ } catch (err) {
1344
+ console.warn(`[firegraph] <${tagName}> data setter error:`, err);
1345
+ this._showError(err);
1346
+ }
1347
+ }
1348
+ get data() {
1349
+ try {
1350
+ return super.data;
1351
+ } catch {
1352
+ return {};
1353
+ }
1354
+ }
1355
+ _showError(err) {
1356
+ try {
1357
+ this.innerHTML = `<div style="padding:6px;color:#f87171;font-size:11px;font-family:monospace;">View error in &lt;${tagName}&gt;: ${err instanceof Error ? err.message : String(err)}</div>`;
1358
+ } catch {
1359
+ }
1360
+ }
1361
+ };
1362
+ Wrapped.viewName = ViewClass.viewName;
1363
+ Wrapped.description = ViewClass.description;
1364
+ return Wrapped;
1365
+ }
1366
+ function defineViews(input) {
1367
+ const nodes = {};
1368
+ const edges = {};
1369
+ const registry = getCustomElements();
1370
+ for (const [entityType, config] of Object.entries(input.nodes ?? {})) {
1371
+ const viewMetas = [];
1372
+ for (const ViewClass of config.views) {
1373
+ const tagName = `fg-${sanitizeTagPart(entityType)}-${sanitizeTagPart(ViewClass.viewName)}`;
1374
+ viewMetas.push({
1375
+ tagName,
1376
+ viewName: ViewClass.viewName,
1377
+ description: ViewClass.description
1378
+ });
1379
+ if (registry && !registry.get(tagName)) {
1380
+ registry.define(tagName, resilientView(ViewClass, tagName));
1381
+ }
1382
+ }
1383
+ nodes[entityType] = {
1384
+ views: viewMetas,
1385
+ sampleData: config.sampleData
1386
+ };
1387
+ }
1388
+ for (const [axbType, config] of Object.entries(input.edges ?? {})) {
1389
+ const viewMetas = [];
1390
+ for (const ViewClass of config.views) {
1391
+ const tagName = `fg-edge-${sanitizeTagPart(axbType)}-${sanitizeTagPart(ViewClass.viewName)}`;
1392
+ viewMetas.push({
1393
+ tagName,
1394
+ viewName: ViewClass.viewName,
1395
+ description: ViewClass.description
1396
+ });
1397
+ if (registry && !registry.get(tagName)) {
1398
+ registry.define(tagName, resilientView(ViewClass, tagName));
1399
+ }
1400
+ }
1401
+ edges[axbType] = {
1402
+ views: viewMetas,
1403
+ sampleData: config.sampleData
1404
+ };
1405
+ }
1406
+ return { nodes, edges };
1407
+ }
1408
+
1409
+ // src/config.ts
1410
+ function defineConfig(config) {
1411
+ return config;
1412
+ }
1413
+ function resolveView(resolverConfig, availableViewNames, context) {
1414
+ if (!resolverConfig) return "json";
1415
+ const available = new Set(availableViewNames);
1416
+ if (context) {
1417
+ const contextDefault = resolverConfig[context];
1418
+ if (contextDefault && available.has(contextDefault)) {
1419
+ return contextDefault;
1420
+ }
1421
+ }
1422
+ if (resolverConfig.default && available.has(resolverConfig.default)) {
1423
+ return resolverConfig.default;
1424
+ }
1425
+ return "json";
1426
+ }
1427
+
1428
+ // src/discover.ts
1429
+ var import_node_fs = require("fs");
1430
+ var import_node_module = require("module");
1431
+ var import_node_path = require("path");
1432
+ var import_meta = {};
1433
+ var DiscoveryError = class extends FiregraphError {
1434
+ constructor(message) {
1435
+ super(message, "DISCOVERY_ERROR");
1436
+ this.name = "DiscoveryError";
1437
+ }
1438
+ };
1439
+ function readJson(filePath) {
1440
+ try {
1441
+ const raw = (0, import_node_fs.readFileSync)(filePath, "utf-8");
1442
+ return JSON.parse(raw);
1443
+ } catch (err) {
1444
+ const msg = err instanceof SyntaxError ? `Invalid JSON in ${filePath}: ${err.message}` : `Cannot read ${filePath}: ${err.message}`;
1445
+ throw new DiscoveryError(msg);
1446
+ }
1447
+ }
1448
+ function readJsonIfExists(filePath) {
1449
+ if (!(0, import_node_fs.existsSync)(filePath)) return void 0;
1450
+ return readJson(filePath);
1451
+ }
1452
+ var SCHEMA_SCRIPT_EXTENSIONS = [".ts", ".js", ".mts", ".mjs"];
1453
+ function loadSchema(dir, entityLabel) {
1454
+ for (const ext of SCHEMA_SCRIPT_EXTENSIONS) {
1455
+ const candidate = (0, import_node_path.join)(dir, `schema${ext}`);
1456
+ if ((0, import_node_fs.existsSync)(candidate)) {
1457
+ return loadSchemaModule(candidate, entityLabel);
1458
+ }
1459
+ }
1460
+ const jsonPath = (0, import_node_path.join)(dir, "schema.json");
1461
+ if ((0, import_node_fs.existsSync)(jsonPath)) {
1462
+ return readJson(jsonPath);
1463
+ }
1464
+ throw new DiscoveryError(
1465
+ `Missing schema for ${entityLabel} in ${dir}. Provide a schema.ts (or .js/.mts/.mjs) or schema.json file.`
1466
+ );
1467
+ }
1468
+ var _jiti;
1469
+ function getJiti() {
1470
+ if (!_jiti) {
1471
+ const base = typeof __filename !== "undefined" ? __filename : import_meta.url;
1472
+ const esmRequire = (0, import_node_module.createRequire)(base);
1473
+ const { createJiti } = esmRequire("jiti");
1474
+ _jiti = createJiti(base, { interopDefault: true });
1475
+ }
1476
+ return _jiti;
1477
+ }
1478
+ function loadSchemaModule(filePath, entityLabel) {
1479
+ try {
1480
+ const jiti = getJiti();
1481
+ const mod = jiti(filePath);
1482
+ const schema = mod && typeof mod === "object" && "default" in mod ? mod.default : mod;
1483
+ if (!schema || typeof schema !== "object") {
1484
+ throw new DiscoveryError(
1485
+ `Schema file ${filePath} for ${entityLabel} must default-export a JSON Schema object.`
1486
+ );
1487
+ }
1488
+ return schema;
1489
+ } catch (err) {
1490
+ if (err instanceof DiscoveryError) throw err;
1491
+ throw new DiscoveryError(
1492
+ `Failed to load schema module ${filePath} for ${entityLabel}: ${err.message}`
1493
+ );
1494
+ }
1495
+ }
1496
+ var VIEW_EXTENSIONS = [".ts", ".js", ".mts", ".mjs"];
1497
+ function findViewsFile(dir) {
1498
+ for (const ext of VIEW_EXTENSIONS) {
1499
+ const candidate = (0, import_node_path.join)(dir, `views${ext}`);
1500
+ if ((0, import_node_fs.existsSync)(candidate)) return candidate;
1501
+ }
1502
+ return void 0;
1503
+ }
1504
+ function loadNodeEntity(dir, name) {
1505
+ const schema = loadSchema(dir, `node type "${name}"`);
1506
+ const meta = readJsonIfExists((0, import_node_path.join)(dir, "meta.json"));
1507
+ const sampleData = readJsonIfExists((0, import_node_path.join)(dir, "sample.json"));
1508
+ const viewsPath = findViewsFile(dir);
1509
+ return {
1510
+ kind: "node",
1511
+ name,
1512
+ schema,
1513
+ description: meta?.description,
1514
+ titleField: meta?.titleField,
1515
+ subtitleField: meta?.subtitleField,
1516
+ viewDefaults: meta?.viewDefaults,
1517
+ viewsPath,
1518
+ sampleData
1519
+ };
1520
+ }
1521
+ function loadEdgeEntity(dir, name) {
1522
+ const schema = loadSchema(dir, `edge type "${name}"`);
1523
+ const edgePath = (0, import_node_path.join)(dir, "edge.json");
1524
+ if (!(0, import_node_fs.existsSync)(edgePath)) {
1525
+ throw new DiscoveryError(
1526
+ `Missing edge.json for edge type "${name}" in ${dir}. Edge entities must declare topology (from/to node types).`
1527
+ );
1528
+ }
1529
+ const topology = readJson(edgePath);
1530
+ if (!topology.from) {
1531
+ throw new DiscoveryError(
1532
+ `edge.json for "${name}" is missing required "from" field`
1533
+ );
1534
+ }
1535
+ if (!topology.to) {
1536
+ throw new DiscoveryError(
1537
+ `edge.json for "${name}" is missing required "to" field`
1538
+ );
1539
+ }
1540
+ const meta = readJsonIfExists((0, import_node_path.join)(dir, "meta.json"));
1541
+ const sampleData = readJsonIfExists((0, import_node_path.join)(dir, "sample.json"));
1542
+ const viewsPath = findViewsFile(dir);
1543
+ return {
1544
+ kind: "edge",
1545
+ name,
1546
+ schema,
1547
+ topology,
1548
+ description: meta?.description,
1549
+ titleField: meta?.titleField,
1550
+ subtitleField: meta?.subtitleField,
1551
+ viewDefaults: meta?.viewDefaults,
1552
+ viewsPath,
1553
+ sampleData
1554
+ };
1555
+ }
1556
+ function getSubdirectories(dir) {
1557
+ if (!(0, import_node_fs.existsSync)(dir)) return [];
1558
+ return (0, import_node_fs.readdirSync)(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
1559
+ }
1560
+ function discoverEntities(entitiesDir) {
1561
+ const absDir = (0, import_node_path.resolve)(entitiesDir);
1562
+ if (!(0, import_node_fs.existsSync)(absDir) || !(0, import_node_fs.statSync)(absDir).isDirectory()) {
1563
+ throw new DiscoveryError(`Entities directory not found: ${entitiesDir}`);
1564
+ }
1565
+ const nodes = /* @__PURE__ */ new Map();
1566
+ const edges = /* @__PURE__ */ new Map();
1567
+ const warnings = [];
1568
+ const nodesDir = (0, import_node_path.join)(absDir, "nodes");
1569
+ for (const name of getSubdirectories(nodesDir)) {
1570
+ nodes.set(name, loadNodeEntity((0, import_node_path.join)(nodesDir, name), name));
1571
+ }
1572
+ const edgesDir = (0, import_node_path.join)(absDir, "edges");
1573
+ for (const name of getSubdirectories(edgesDir)) {
1574
+ edges.set(name, loadEdgeEntity((0, import_node_path.join)(edgesDir, name), name));
1575
+ }
1576
+ const nodeNames = new Set(nodes.keys());
1577
+ for (const [axbType, entity] of edges) {
1578
+ const topology = entity.topology;
1579
+ const fromTypes = Array.isArray(topology.from) ? topology.from : [topology.from];
1580
+ const toTypes = Array.isArray(topology.to) ? topology.to : [topology.to];
1581
+ for (const ref of [...fromTypes, ...toTypes]) {
1582
+ if (!nodeNames.has(ref)) {
1583
+ warnings.push({
1584
+ code: "DANGLING_TOPOLOGY_REF",
1585
+ message: `Edge "${axbType}" references node type "${ref}" which was not found in the nodes directory`
1586
+ });
1587
+ }
1588
+ }
1589
+ }
1590
+ return {
1591
+ result: { nodes, edges },
1592
+ warnings
1593
+ };
1594
+ }
1595
+
1596
+ // src/codegen/index.ts
1597
+ function pascalCase(s) {
1598
+ return s.replace(
1599
+ /(^|[^a-zA-Z0-9])([a-zA-Z])/g,
1600
+ (_, _sep, ch) => ch.toUpperCase()
1601
+ );
1602
+ }
1603
+ async function generateTypes(discovery, options = {}) {
1604
+ const { compile } = await import("json-schema-to-typescript");
1605
+ const { banner = true } = options;
1606
+ const chunks = [];
1607
+ if (banner) {
1608
+ chunks.push(
1609
+ "// Auto-generated by firegraph codegen \u2014 do not edit manually\n"
1610
+ );
1611
+ }
1612
+ const sortedNodes = [...discovery.nodes.entries()].sort(
1613
+ ([a], [b]) => a.localeCompare(b)
1614
+ );
1615
+ const sortedEdges = [...discovery.edges.entries()].sort(
1616
+ ([a], [b]) => a.localeCompare(b)
1617
+ );
1618
+ for (const [name, entity] of sortedNodes) {
1619
+ const typeName = `${pascalCase(name)}Data`;
1620
+ const ts = await compile(entity.schema, typeName, {
1621
+ bannerComment: "",
1622
+ additionalProperties: false
1623
+ });
1624
+ chunks.push(ts.trim());
1625
+ chunks.push("");
1626
+ }
1627
+ for (const [name, entity] of sortedEdges) {
1628
+ const typeName = `${pascalCase(name)}EdgeData`;
1629
+ const ts = await compile(entity.schema, typeName, {
1630
+ bannerComment: "",
1631
+ additionalProperties: false
1632
+ });
1633
+ chunks.push(ts.trim());
1634
+ chunks.push("");
1635
+ }
1636
+ return chunks.join("\n").trimEnd() + "\n";
1637
+ }
1638
+
1639
+ // src/query-client/client.ts
1640
+ var import_node_http = __toESM(require("http"), 1);
1641
+
1642
+ // src/query-client/shaping.ts
1643
+ function summarizeRecord(r) {
1644
+ if (!r) return null;
1645
+ const out = { type: r.aType, uid: r.aUid };
1646
+ const data = r.data;
1647
+ if (data && typeof data === "object" && Object.keys(data).length > 0) {
1648
+ out.data = data;
1649
+ }
1650
+ return out;
1651
+ }
1652
+ function summarizeEdge(r) {
1653
+ if (!r) return null;
1654
+ const out = {
1655
+ fromType: r.aType,
1656
+ fromUid: r.aUid,
1657
+ relation: r.axbType,
1658
+ toType: r.bType,
1659
+ toUid: r.bUid
1660
+ };
1661
+ const data = r.data;
1662
+ if (data && typeof data === "object" && Object.keys(data).length > 0) {
1663
+ out.data = data;
1664
+ }
1665
+ return out;
1666
+ }
1667
+
1668
+ // src/query-client/config.ts
1669
+ var import_node_fs2 = require("fs");
1670
+ var import_node_path2 = require("path");
1671
+ var CONFIG_FILES = ["firegraph.config.ts", "firegraph.config.js", "firegraph.config.mjs"];
1672
+ var DEFAULT_PORT = 3884;
1673
+ function readEditorPort(cwd) {
1674
+ const dir = cwd ?? process.cwd();
1675
+ for (const name of CONFIG_FILES) {
1676
+ try {
1677
+ const content = (0, import_node_fs2.readFileSync)((0, import_node_path2.join)(dir, name), "utf8");
1678
+ const editorBlock = content.match(/editor\s*:\s*\{[^}]*\}/s)?.[0] ?? "";
1679
+ const portMatch = editorBlock.match(/port\s*:\s*(\d+)/);
1680
+ if (portMatch) return parseInt(portMatch[1], 10);
1681
+ } catch {
1682
+ continue;
1683
+ }
1684
+ }
1685
+ return DEFAULT_PORT;
1686
+ }
1687
+
1688
+ // src/query-client/client.ts
1689
+ var QueryClientError = class extends Error {
1690
+ constructor(message, code) {
1691
+ super(message);
1692
+ this.code = code;
1693
+ this.name = "QueryClientError";
1694
+ }
1695
+ };
1696
+ function requireString(value, name) {
1697
+ if (typeof value !== "string" || value.length === 0) {
1698
+ throw new QueryClientError(`${name} must be a non-empty string`, "VALIDATION_ERROR");
1699
+ }
1700
+ }
1701
+ function clampInt(value, min, max, fallback) {
1702
+ if (value == null) return fallback;
1703
+ if (!Number.isInteger(value)) {
1704
+ throw new QueryClientError(`limit must be an integer`, "VALIDATION_ERROR");
1705
+ }
1706
+ return Math.max(min, Math.min(max, value));
1707
+ }
1708
+ function validateSortDir(dir) {
1709
+ if (dir != null && dir !== "asc" && dir !== "desc") {
1710
+ throw new QueryClientError(`sortDir must be 'asc' or 'desc'`, "VALIDATION_ERROR");
1711
+ }
1712
+ }
1713
+ function httpGet(url) {
1714
+ return new Promise((resolve2, reject) => {
1715
+ import_node_http.default.get(url, (res) => {
1716
+ let body = "";
1717
+ res.on("data", (c) => body += c);
1718
+ res.on("end", () => resolve2(body));
1719
+ }).on("error", (err) => {
1720
+ reject(new QueryClientError(`Connection failed: ${err.message}`, "CONNECTION_FAILED"));
1721
+ });
1722
+ });
1723
+ }
1724
+ function httpPost(url, payload) {
1725
+ const parsed = new URL(url);
1726
+ return new Promise((resolve2, reject) => {
1727
+ const req = import_node_http.default.request(
1728
+ {
1729
+ hostname: parsed.hostname,
1730
+ port: parsed.port,
1731
+ path: parsed.pathname,
1732
+ method: "POST",
1733
+ headers: {
1734
+ "Content-Type": "application/json",
1735
+ "Content-Length": Buffer.byteLength(payload)
1736
+ }
1737
+ },
1738
+ (res) => {
1739
+ let body = "";
1740
+ res.on("data", (c) => body += c);
1741
+ res.on("end", () => resolve2(body));
1742
+ }
1743
+ );
1744
+ req.on("error", (err) => {
1745
+ reject(new QueryClientError(`Connection failed: ${err.message}`, "CONNECTION_FAILED"));
1746
+ });
1747
+ req.write(payload);
1748
+ req.end();
1749
+ });
1750
+ }
1751
+ function parseTrpcResponse(raw, procedure) {
1752
+ let parsed;
1753
+ try {
1754
+ parsed = JSON.parse(raw);
1755
+ } catch {
1756
+ throw new QueryClientError(
1757
+ `Invalid JSON from ${procedure}: ${raw.slice(0, 200)}`,
1758
+ "SERVER_ERROR"
1759
+ );
1760
+ }
1761
+ if (parsed.error) {
1762
+ const msg = typeof parsed.error === "object" && parsed.error !== null ? parsed.error.message ?? JSON.stringify(parsed.error) : String(parsed.error);
1763
+ throw new QueryClientError(`Server error from ${procedure}: ${msg}`, "SERVER_ERROR");
1764
+ }
1765
+ return parsed.result?.data ?? parsed;
1766
+ }
1767
+ var QueryClient = class {
1768
+ baseUrl;
1769
+ constructor(options) {
1770
+ const host = options?.host ?? "localhost";
1771
+ const port = options?.port ?? readEditorPort();
1772
+ this.baseUrl = `http://${host}:${port}/api/trpc`;
1773
+ }
1774
+ async query(procedure, input) {
1775
+ const qs = input != null ? `?input=${encodeURIComponent(JSON.stringify(input))}` : "";
1776
+ const url = `${this.baseUrl}/${procedure}${qs}`;
1777
+ const raw = await httpGet(url);
1778
+ return parseTrpcResponse(raw, procedure);
1779
+ }
1780
+ async mutate(procedure, input) {
1781
+ const url = `${this.baseUrl}/${procedure}`;
1782
+ const raw = await httpPost(url, JSON.stringify(input));
1783
+ return parseTrpcResponse(raw, procedure);
1784
+ }
1785
+ // --- Public API ---
1786
+ async getSchema() {
1787
+ const data = await this.query("getSchema");
1788
+ return {
1789
+ nodeTypes: (data.nodeTypes ?? []).map(
1790
+ (t) => typeof t === "object" && t !== null ? t.type : t
1791
+ ),
1792
+ edgeTypes: (data.edgeTypes ?? []).map((t) => {
1793
+ const e = t;
1794
+ return {
1795
+ relation: e.axbType,
1796
+ from: e.aType,
1797
+ to: e.bType,
1798
+ inverseLabel: e.inverseLabel ?? null
1799
+ };
1800
+ })
1801
+ };
1802
+ }
1803
+ async getNodeDetail(input) {
1804
+ requireString(input.uid, "uid");
1805
+ const data = await this.query("getNodeDetail", { uid: input.uid });
1806
+ return {
1807
+ node: summarizeRecord(data.node),
1808
+ outEdges: (data.outEdges ?? []).map(summarizeEdge).filter(Boolean),
1809
+ inEdges: (data.inEdges ?? []).map(summarizeEdge).filter(Boolean)
1810
+ };
1811
+ }
1812
+ async getNodes(input) {
1813
+ const limit = clampInt(input.limit, 1, 200, 25);
1814
+ validateSortDir(input.sortDir);
1815
+ const data = await this.query("getNodes", {
1816
+ type: input.type,
1817
+ limit,
1818
+ startAfter: input.startAfter,
1819
+ sortBy: input.sortBy,
1820
+ sortDir: input.sortDir,
1821
+ where: input.where
1822
+ });
1823
+ return {
1824
+ nodes: (data.nodes ?? []).map(summarizeRecord).filter(Boolean),
1825
+ hasMore: data.hasMore ?? false,
1826
+ nextCursor: data.nextCursor
1827
+ };
1828
+ }
1829
+ async getEdges(input) {
1830
+ const hasFilter = input.aType || input.aUid || input.axbType || input.bType || input.bUid || input.where && input.where.length > 0;
1831
+ if (!hasFilter) {
1832
+ throw new QueryClientError(
1833
+ "getEdges requires at least one filter field (aType, aUid, axbType, bType, bUid, or where)",
1834
+ "VALIDATION_ERROR"
1835
+ );
1836
+ }
1837
+ const limit = clampInt(input.limit, 1, 200, 25);
1838
+ validateSortDir(input.sortDir);
1839
+ const data = await this.query("getEdges", {
1840
+ aType: input.aType,
1841
+ aUid: input.aUid,
1842
+ axbType: input.axbType,
1843
+ bType: input.bType,
1844
+ bUid: input.bUid,
1845
+ limit,
1846
+ startAfter: input.startAfter,
1847
+ sortBy: input.sortBy,
1848
+ sortDir: input.sortDir,
1849
+ where: input.where
1850
+ });
1851
+ return {
1852
+ edges: (data.edges ?? []).map(summarizeEdge).filter(Boolean),
1853
+ hasMore: data.hasMore ?? false,
1854
+ nextCursor: data.nextCursor
1855
+ };
1856
+ }
1857
+ async traverse(input) {
1858
+ requireString(input.startUid, "startUid");
1859
+ if (!input.hops || input.hops.length === 0) {
1860
+ throw new QueryClientError("traverse requires at least one hop", "VALIDATION_ERROR");
1861
+ }
1862
+ for (let i = 0; i < input.hops.length; i++) {
1863
+ const hop = input.hops[i];
1864
+ requireString(hop.axbType, `hops[${i}].axbType`);
1865
+ if (hop.direction != null && hop.direction !== "forward" && hop.direction !== "reverse") {
1866
+ throw new QueryClientError(
1867
+ `hops[${i}].direction must be 'forward' or 'reverse'`,
1868
+ "VALIDATION_ERROR"
1869
+ );
1870
+ }
1871
+ if (hop.limit != null && (!Number.isInteger(hop.limit) || hop.limit < 1)) {
1872
+ throw new QueryClientError(
1873
+ `hops[${i}].limit must be a positive integer`,
1874
+ "VALIDATION_ERROR"
1875
+ );
1876
+ }
1877
+ }
1878
+ if (input.maxReads != null && (!Number.isInteger(input.maxReads) || input.maxReads < 1)) {
1879
+ throw new QueryClientError("maxReads must be a positive integer", "VALIDATION_ERROR");
1880
+ }
1881
+ if (input.concurrency != null && (!Number.isInteger(input.concurrency) || input.concurrency < 1)) {
1882
+ throw new QueryClientError("concurrency must be a positive integer", "VALIDATION_ERROR");
1883
+ }
1884
+ const data = await this.mutate("traverse", input);
1885
+ return {
1886
+ hops: (data.hops ?? []).map((h) => ({
1887
+ relation: h.axbType,
1888
+ direction: h.direction,
1889
+ depth: h.depth,
1890
+ edgeCount: (h.edges ?? []).length,
1891
+ edges: (h.edges ?? []).map(summarizeEdge).filter(Boolean),
1892
+ truncated: h.truncated ?? false
1893
+ })),
1894
+ totalReads: data.totalReads ?? 0,
1895
+ truncated: data.truncated ?? false
1896
+ };
1897
+ }
1898
+ async search(input) {
1899
+ requireString(input.q, "q");
1900
+ const limit = clampInt(input.limit, 1, 50, 20);
1901
+ const data = await this.query("search", { q: input.q, limit });
1902
+ return {
1903
+ results: (data.results ?? []).map((r) => {
1904
+ const base = summarizeRecord(r);
1905
+ if (!base) return null;
1906
+ return {
1907
+ ...base,
1908
+ matchType: r._matchType ?? null
1909
+ };
1910
+ }).filter(Boolean)
1911
+ };
1912
+ }
1913
+ };
1914
+ // Annotate the CommonJS export names for ESM import in node:
1915
+ 0 && (module.exports = {
1916
+ BOOTSTRAP_ENTRIES,
1917
+ DiscoveryError,
1918
+ DynamicRegistryError,
1919
+ EDGE_TYPE_SCHEMA,
1920
+ EdgeNotFoundError,
1921
+ FiregraphError,
1922
+ InvalidQueryError,
1923
+ META_EDGE_TYPE,
1924
+ META_NODE_TYPE,
1925
+ NODE_TYPE_SCHEMA,
1926
+ NodeNotFoundError,
1927
+ QueryClient,
1928
+ QueryClientError,
1929
+ RegistryViolationError,
1930
+ TraversalError,
1931
+ ValidationError,
1932
+ buildEdgeQueryPlan,
1933
+ buildEdgeRecord,
1934
+ buildNodeQueryPlan,
1935
+ buildNodeRecord,
1936
+ compileSchema,
1937
+ computeEdgeDocId,
1938
+ computeNodeDocId,
1939
+ createBootstrapRegistry,
1940
+ createGraphClient,
1941
+ createRegistry,
1942
+ createRegistryFromGraph,
1943
+ createTraversal,
1944
+ defineConfig,
1945
+ defineViews,
1946
+ discoverEntities,
1947
+ generateDeterministicUid,
1948
+ generateId,
1949
+ generateTypes,
1950
+ jsonSchemaToFieldMeta,
1951
+ resolveView
1952
+ });
1953
+ //# sourceMappingURL=index.cjs.map