@typicalday/firegraph 0.11.2 → 0.13.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 (78) hide show
  1. package/README.md +355 -78
  2. package/dist/backend-DuvHGgK1.d.cts +1897 -0
  3. package/dist/backend-DuvHGgK1.d.ts +1897 -0
  4. package/dist/backend.cjs +365 -5
  5. package/dist/backend.cjs.map +1 -1
  6. package/dist/backend.d.cts +25 -5
  7. package/dist/backend.d.ts +25 -5
  8. package/dist/backend.js +209 -7
  9. package/dist/backend.js.map +1 -1
  10. package/dist/chunk-2DHMNTV6.js +16 -0
  11. package/dist/chunk-2DHMNTV6.js.map +1 -0
  12. package/dist/chunk-4MMQ5W74.js +288 -0
  13. package/dist/chunk-4MMQ5W74.js.map +1 -0
  14. package/dist/{chunk-5753Y42M.js → chunk-C2QMD7RY.js} +6 -10
  15. package/dist/chunk-C2QMD7RY.js.map +1 -0
  16. package/dist/chunk-D4J7Z4FE.js +67 -0
  17. package/dist/chunk-D4J7Z4FE.js.map +1 -0
  18. package/dist/chunk-EQJUUVFG.js +14 -0
  19. package/dist/chunk-EQJUUVFG.js.map +1 -0
  20. package/dist/chunk-N5HFDWQX.js +23 -0
  21. package/dist/chunk-N5HFDWQX.js.map +1 -0
  22. package/dist/chunk-PAD7WFFU.js +573 -0
  23. package/dist/chunk-PAD7WFFU.js.map +1 -0
  24. package/dist/chunk-TK64DNVK.js +256 -0
  25. package/dist/chunk-TK64DNVK.js.map +1 -0
  26. package/dist/{chunk-NJSOD64C.js → chunk-WRTFC5NG.js} +438 -30
  27. package/dist/chunk-WRTFC5NG.js.map +1 -0
  28. package/dist/client-BKi3vk0Q.d.ts +34 -0
  29. package/dist/client-BrsaXtDV.d.cts +34 -0
  30. package/dist/cloudflare/index.cjs +1386 -74
  31. package/dist/cloudflare/index.cjs.map +1 -1
  32. package/dist/cloudflare/index.d.cts +217 -13
  33. package/dist/cloudflare/index.d.ts +217 -13
  34. package/dist/cloudflare/index.js +639 -180
  35. package/dist/cloudflare/index.js.map +1 -1
  36. package/dist/codegen/index.d.cts +1 -1
  37. package/dist/codegen/index.d.ts +1 -1
  38. package/dist/errors-BRc3I_eH.d.cts +73 -0
  39. package/dist/errors-BRc3I_eH.d.ts +73 -0
  40. package/dist/firestore-enterprise/index.cjs +3877 -0
  41. package/dist/firestore-enterprise/index.cjs.map +1 -0
  42. package/dist/firestore-enterprise/index.d.cts +141 -0
  43. package/dist/firestore-enterprise/index.d.ts +141 -0
  44. package/dist/firestore-enterprise/index.js +985 -0
  45. package/dist/firestore-enterprise/index.js.map +1 -0
  46. package/dist/firestore-standard/index.cjs +3117 -0
  47. package/dist/firestore-standard/index.cjs.map +1 -0
  48. package/dist/firestore-standard/index.d.cts +49 -0
  49. package/dist/firestore-standard/index.d.ts +49 -0
  50. package/dist/firestore-standard/index.js +283 -0
  51. package/dist/firestore-standard/index.js.map +1 -0
  52. package/dist/index.cjs +809 -534
  53. package/dist/index.cjs.map +1 -1
  54. package/dist/index.d.cts +24 -100
  55. package/dist/index.d.ts +24 -100
  56. package/dist/index.js +184 -531
  57. package/dist/index.js.map +1 -1
  58. package/dist/registry-Bc7h6WTM.d.cts +64 -0
  59. package/dist/registry-C2KUPVZj.d.ts +64 -0
  60. package/dist/{scope-path-B1G3YiA7.d.ts → scope-path-CROFZGr9.d.cts} +1 -56
  61. package/dist/{scope-path-B1G3YiA7.d.cts → scope-path-CROFZGr9.d.ts} +1 -56
  62. package/dist/{serialization-ZZ7RSDRX.js → serialization-OE2PFZMY.js} +6 -4
  63. package/dist/sqlite/index.cjs +3631 -0
  64. package/dist/sqlite/index.cjs.map +1 -0
  65. package/dist/sqlite/index.d.cts +111 -0
  66. package/dist/sqlite/index.d.ts +111 -0
  67. package/dist/sqlite/index.js +1164 -0
  68. package/dist/sqlite/index.js.map +1 -0
  69. package/package.json +33 -3
  70. package/dist/backend-U-MLShlg.d.ts +0 -97
  71. package/dist/backend-np4gEVhB.d.cts +0 -97
  72. package/dist/chunk-5753Y42M.js.map +0 -1
  73. package/dist/chunk-NJSOD64C.js.map +0 -1
  74. package/dist/chunk-R7CRGYY4.js +0 -94
  75. package/dist/chunk-R7CRGYY4.js.map +0 -1
  76. package/dist/types-BGWxcpI_.d.cts +0 -736
  77. package/dist/types-BGWxcpI_.d.ts +0 -736
  78. /package/dist/{serialization-ZZ7RSDRX.js.map → serialization-OE2PFZMY.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,18 +1,32 @@
1
+ import {
2
+ QueryClient,
3
+ QueryClientError
4
+ } from "./chunk-EEKWRX5E.js";
1
5
  import {
2
6
  appendStorageScope,
3
7
  isAncestorScopeUid,
4
8
  parseStorageScope,
5
9
  resolveAncestorScope
6
10
  } from "./chunk-TYYPRVIE.js";
11
+ import {
12
+ DEFAULT_CORE_INDEXES
13
+ } from "./chunk-2DHMNTV6.js";
14
+ import {
15
+ generateTypes
16
+ } from "./chunk-GJVVRTQT.js";
17
+ import {
18
+ compileEngineTraversal
19
+ } from "./chunk-D4J7Z4FE.js";
20
+ import {
21
+ deserializeFirestoreTypes,
22
+ serializeFirestoreTypes
23
+ } from "./chunk-C2QMD7RY.js";
7
24
  import {
8
25
  BOOTSTRAP_ENTRIES,
9
- DEFAULT_CORE_INDEXES,
10
26
  DEFAULT_QUERY_LIMIT,
11
27
  EDGE_TYPE_SCHEMA,
12
- GraphClientImpl,
13
28
  META_EDGE_TYPE,
14
29
  META_NODE_TYPE,
15
- NODE_RELATION,
16
30
  NODE_TYPE_SCHEMA,
17
31
  analyzeQuerySafety,
18
32
  applyMigrationChain,
@@ -24,6 +38,7 @@ import {
24
38
  computeEdgeDocId,
25
39
  computeNodeDocId,
26
40
  createBootstrapRegistry,
41
+ createGraphClient,
27
42
  createGraphClientFromBackend,
28
43
  createMergedRegistry,
29
44
  createRegistry,
@@ -31,6 +46,7 @@ import {
31
46
  defaultExecutor,
32
47
  destroySandboxWorker,
33
48
  generateDeterministicUid,
49
+ generateId,
34
50
  jsonSchemaToFieldMeta,
35
51
  matchScope,
36
52
  matchScopeAny,
@@ -38,8 +54,9 @@ import {
38
54
  migrateRecords,
39
55
  precompileSource,
40
56
  validateMigrationChain
41
- } from "./chunk-NJSOD64C.js";
57
+ } from "./chunk-WRTFC5NG.js";
42
58
  import {
59
+ CapabilityNotSupportedError,
43
60
  CrossBackendTransactionError,
44
61
  DynamicRegistryError,
45
62
  EdgeNotFoundError,
@@ -51,21 +68,13 @@ import {
51
68
  RegistryScopeError,
52
69
  RegistryViolationError,
53
70
  TraversalError,
54
- ValidationError
55
- } from "./chunk-R7CRGYY4.js";
56
- import {
57
- generateTypes
58
- } from "./chunk-GJVVRTQT.js";
59
- import {
60
- QueryClient,
61
- QueryClientError
62
- } from "./chunk-EEKWRX5E.js";
71
+ ValidationError,
72
+ deleteField
73
+ } from "./chunk-TK64DNVK.js";
63
74
  import {
64
75
  SERIALIZATION_TAG,
65
- deserializeFirestoreTypes,
66
- isTaggedValue,
67
- serializeFirestoreTypes
68
- } from "./chunk-5753Y42M.js";
76
+ isTaggedValue
77
+ } from "./chunk-EQJUUVFG.js";
69
78
 
70
79
  // src/config.ts
71
80
  function defineConfig(config) {
@@ -302,489 +311,6 @@ function discoverEntities(entitiesDir) {
302
311
  };
303
312
  }
304
313
 
305
- // src/internal/firestore-backend.ts
306
- import { FieldValue } from "@google-cloud/firestore";
307
-
308
- // src/bulk.ts
309
- var MAX_BATCH_SIZE = 500;
310
- var DEFAULT_MAX_RETRIES = 3;
311
- var BASE_DELAY_MS = 200;
312
- function sleep(ms) {
313
- return new Promise((resolve2) => setTimeout(resolve2, ms));
314
- }
315
- function chunk(arr, size) {
316
- const chunks = [];
317
- for (let i = 0; i < arr.length; i += size) {
318
- chunks.push(arr.slice(i, i + size));
319
- }
320
- return chunks;
321
- }
322
- async function bulkDeleteDocIds(db, collectionPath, docIds, options) {
323
- if (docIds.length === 0) {
324
- return { deleted: 0, batches: 0, errors: [] };
325
- }
326
- const batchSize = Math.min(options?.batchSize ?? MAX_BATCH_SIZE, MAX_BATCH_SIZE);
327
- const maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES;
328
- const onProgress = options?.onProgress;
329
- const chunks = chunk(docIds, batchSize);
330
- const errors = [];
331
- let deleted = 0;
332
- let completedBatches = 0;
333
- for (let i = 0; i < chunks.length; i++) {
334
- const ids = chunks[i];
335
- let committed = false;
336
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
337
- try {
338
- const batch = db.batch();
339
- const collectionRef = db.collection(collectionPath);
340
- for (const id of ids) {
341
- batch.delete(collectionRef.doc(id));
342
- }
343
- await batch.commit();
344
- committed = true;
345
- deleted += ids.length;
346
- break;
347
- } catch (err) {
348
- if (attempt < maxRetries) {
349
- const delay = BASE_DELAY_MS * Math.pow(2, attempt);
350
- await sleep(delay);
351
- } else {
352
- errors.push({
353
- batchIndex: i,
354
- error: err instanceof Error ? err : new Error(String(err)),
355
- operationCount: ids.length
356
- });
357
- }
358
- }
359
- }
360
- if (committed) {
361
- completedBatches++;
362
- }
363
- if (onProgress) {
364
- onProgress({
365
- completedBatches,
366
- totalBatches: chunks.length,
367
- deletedSoFar: deleted
368
- });
369
- }
370
- }
371
- return { deleted, batches: completedBatches, errors };
372
- }
373
- async function bulkRemoveEdges(db, collectionPath, reader, params, options) {
374
- const effectiveParams = params.limit !== void 0 ? { ...params, allowCollectionScan: params.allowCollectionScan ?? true } : { ...params, limit: 0, allowCollectionScan: params.allowCollectionScan ?? true };
375
- const edges = await reader.findEdges(effectiveParams);
376
- const docIds = edges.map((e) => computeEdgeDocId(e.aUid, e.axbType, e.bUid));
377
- return bulkDeleteDocIds(db, collectionPath, docIds, options);
378
- }
379
- async function deleteSubcollectionsRecursive(db, collectionPath, docId, options) {
380
- const docRef = db.collection(collectionPath).doc(docId);
381
- const subcollections = await docRef.listCollections();
382
- if (subcollections.length === 0) return { deleted: 0, errors: [] };
383
- let totalDeleted = 0;
384
- const allErrors = [];
385
- const subOptions = options ? { batchSize: options.batchSize, maxRetries: options.maxRetries } : void 0;
386
- for (const subCollRef of subcollections) {
387
- const subCollPath = subCollRef.path;
388
- const snapshot = await subCollRef.select().get();
389
- const subDocIds = snapshot.docs.map((d) => d.id);
390
- for (const subDocId of subDocIds) {
391
- const subResult = await deleteSubcollectionsRecursive(db, subCollPath, subDocId, subOptions);
392
- totalDeleted += subResult.deleted;
393
- allErrors.push(...subResult.errors);
394
- }
395
- if (subDocIds.length > 0) {
396
- const result = await bulkDeleteDocIds(db, subCollPath, subDocIds, subOptions);
397
- totalDeleted += result.deleted;
398
- allErrors.push(...result.errors);
399
- }
400
- }
401
- return { deleted: totalDeleted, errors: allErrors };
402
- }
403
- async function removeNodeCascade(db, collectionPath, reader, uid, options) {
404
- const [outgoingRaw, incomingRaw] = await Promise.all([
405
- reader.findEdges({ aUid: uid, allowCollectionScan: true, limit: 0 }),
406
- reader.findEdges({ bUid: uid, allowCollectionScan: true, limit: 0 })
407
- ]);
408
- const outgoing = outgoingRaw.filter((e) => e.axbType !== NODE_RELATION);
409
- const incoming = incomingRaw.filter((e) => e.axbType !== NODE_RELATION);
410
- const edgeDocIdSet = /* @__PURE__ */ new Set();
411
- const allEdges = [];
412
- for (const edge of [...outgoing, ...incoming]) {
413
- const docId = computeEdgeDocId(edge.aUid, edge.axbType, edge.bUid);
414
- if (!edgeDocIdSet.has(docId)) {
415
- edgeDocIdSet.add(docId);
416
- allEdges.push(edge);
417
- }
418
- }
419
- const shouldDeleteSubcollections = options?.deleteSubcollections !== false;
420
- const nodeDocId = computeNodeDocId(uid);
421
- let subcollectionResult = { deleted: 0, errors: [] };
422
- if (shouldDeleteSubcollections) {
423
- subcollectionResult = await deleteSubcollectionsRecursive(
424
- db,
425
- collectionPath,
426
- nodeDocId,
427
- options
428
- );
429
- }
430
- const edgeDocIds = allEdges.map((e) => computeEdgeDocId(e.aUid, e.axbType, e.bUid));
431
- const allDocIds = [...edgeDocIds, nodeDocId];
432
- const batchSize = Math.min(options?.batchSize ?? MAX_BATCH_SIZE, MAX_BATCH_SIZE);
433
- const result = await bulkDeleteDocIds(db, collectionPath, allDocIds, {
434
- ...options,
435
- batchSize
436
- });
437
- const totalChunks = Math.ceil(allDocIds.length / batchSize);
438
- const nodeChunkIndex = totalChunks - 1;
439
- const nodeDeleted = !result.errors.some((e) => e.batchIndex === nodeChunkIndex);
440
- const topLevelEdgesDeleted = nodeDeleted ? result.deleted - 1 : result.deleted;
441
- return {
442
- deleted: result.deleted + subcollectionResult.deleted,
443
- batches: result.batches,
444
- errors: [...result.errors, ...subcollectionResult.errors],
445
- edgesDeleted: topLevelEdgesDeleted,
446
- nodeDeleted
447
- };
448
- }
449
-
450
- // src/internal/firestore-adapter.ts
451
- function createFirestoreAdapter(db, collectionPath) {
452
- const collectionRef = db.collection(collectionPath);
453
- return {
454
- collectionPath,
455
- async getDoc(docId) {
456
- const snap = await collectionRef.doc(docId).get();
457
- if (!snap.exists) return null;
458
- return snap.data();
459
- },
460
- async setDoc(docId, data) {
461
- await collectionRef.doc(docId).set(data);
462
- },
463
- async updateDoc(docId, data) {
464
- await collectionRef.doc(docId).update(data);
465
- },
466
- async deleteDoc(docId) {
467
- await collectionRef.doc(docId).delete();
468
- },
469
- async query(filters, options) {
470
- let q = collectionRef;
471
- for (const f of filters) {
472
- q = q.where(f.field, f.op, f.value);
473
- }
474
- if (options?.orderBy) {
475
- q = q.orderBy(options.orderBy.field, options.orderBy.direction ?? "asc");
476
- }
477
- if (options?.limit !== void 0) {
478
- q = q.limit(options.limit);
479
- }
480
- const snap = await q.get();
481
- return snap.docs.map((doc) => doc.data());
482
- }
483
- };
484
- }
485
- function createTransactionAdapter(db, collectionPath, tx) {
486
- const collectionRef = db.collection(collectionPath);
487
- return {
488
- async getDoc(docId) {
489
- const snap = await tx.get(collectionRef.doc(docId));
490
- if (!snap.exists) return null;
491
- return snap.data();
492
- },
493
- setDoc(docId, data) {
494
- tx.set(collectionRef.doc(docId), data);
495
- },
496
- updateDoc(docId, data) {
497
- tx.update(collectionRef.doc(docId), data);
498
- },
499
- deleteDoc(docId) {
500
- tx.delete(collectionRef.doc(docId));
501
- },
502
- async query(filters, options) {
503
- let q = collectionRef;
504
- for (const f of filters) {
505
- q = q.where(f.field, f.op, f.value);
506
- }
507
- if (options?.orderBy) {
508
- q = q.orderBy(options.orderBy.field, options.orderBy.direction ?? "asc");
509
- }
510
- if (options?.limit !== void 0) {
511
- q = q.limit(options.limit);
512
- }
513
- const snap = await tx.get(q);
514
- return snap.docs.map((doc) => doc.data());
515
- }
516
- };
517
- }
518
- function createBatchAdapter(db, collectionPath) {
519
- const collectionRef = db.collection(collectionPath);
520
- const batch = db.batch();
521
- return {
522
- setDoc(docId, data) {
523
- batch.set(collectionRef.doc(docId), data);
524
- },
525
- updateDoc(docId, data) {
526
- batch.update(collectionRef.doc(docId), data);
527
- },
528
- deleteDoc(docId) {
529
- batch.delete(collectionRef.doc(docId));
530
- },
531
- async commit() {
532
- await batch.commit();
533
- }
534
- };
535
- }
536
-
537
- // src/internal/pipeline-adapter.ts
538
- var _Pipelines = null;
539
- async function getPipelines() {
540
- if (!_Pipelines) {
541
- const mod = await import("@google-cloud/firestore");
542
- _Pipelines = mod.Pipelines;
543
- }
544
- return _Pipelines;
545
- }
546
- function buildFilterExpression(P, filter) {
547
- const { field: fieldName, op, value } = filter;
548
- switch (op) {
549
- case "==":
550
- return P.equal(fieldName, value);
551
- case "!=":
552
- return P.notEqual(fieldName, value);
553
- case "<":
554
- return P.lessThan(fieldName, value);
555
- case "<=":
556
- return P.lessThanOrEqual(fieldName, value);
557
- case ">":
558
- return P.greaterThan(fieldName, value);
559
- case ">=":
560
- return P.greaterThanOrEqual(fieldName, value);
561
- case "in":
562
- return P.equalAny(fieldName, value);
563
- case "not-in":
564
- return P.notEqualAny(fieldName, value);
565
- case "array-contains":
566
- return P.arrayContains(fieldName, value);
567
- case "array-contains-any":
568
- return P.arrayContainsAny(fieldName, value);
569
- default:
570
- throw new Error(`Unsupported filter op for pipeline mode: ${op}`);
571
- }
572
- }
573
- function createPipelineQueryAdapter(db, collectionPath) {
574
- return {
575
- async query(filters, options) {
576
- const P = await getPipelines();
577
- let pipeline = db.pipeline().collection(collectionPath);
578
- if (filters.length === 1) {
579
- pipeline = pipeline.where(buildFilterExpression(P, filters[0]));
580
- } else if (filters.length > 1) {
581
- const [first, second, ...rest] = filters.map((f) => buildFilterExpression(P, f));
582
- pipeline = pipeline.where(P.and(first, second, ...rest));
583
- }
584
- if (options?.orderBy) {
585
- const f = P.field(options.orderBy.field);
586
- const ordering = options.orderBy.direction === "desc" ? f.descending() : f.ascending();
587
- pipeline = pipeline.sort(ordering);
588
- }
589
- if (options?.limit !== void 0) {
590
- pipeline = pipeline.limit(options.limit);
591
- }
592
- const snap = await pipeline.execute();
593
- return snap.results.map((r) => r.data());
594
- }
595
- };
596
- }
597
-
598
- // src/internal/firestore-backend.ts
599
- function buildFirestoreUpdate(update, db) {
600
- const out = {
601
- updatedAt: FieldValue.serverTimestamp()
602
- };
603
- if (update.replaceData) {
604
- out.data = deserializeFirestoreTypes(update.replaceData, db);
605
- }
606
- if (update.dataFields) {
607
- for (const [k, v] of Object.entries(update.dataFields)) {
608
- out[`data.${k}`] = v;
609
- }
610
- }
611
- if (update.v !== void 0) {
612
- out.v = update.v;
613
- }
614
- return out;
615
- }
616
- function stampWritableRecord(record) {
617
- const now = FieldValue.serverTimestamp();
618
- const out = {
619
- aType: record.aType,
620
- aUid: record.aUid,
621
- axbType: record.axbType,
622
- bType: record.bType,
623
- bUid: record.bUid,
624
- data: record.data,
625
- createdAt: now,
626
- updatedAt: now
627
- };
628
- if (record.v !== void 0) out.v = record.v;
629
- return out;
630
- }
631
- var FirestoreTransactionBackend = class {
632
- constructor(adapter, db) {
633
- this.adapter = adapter;
634
- this.db = db;
635
- }
636
- getDoc(docId) {
637
- return this.adapter.getDoc(docId);
638
- }
639
- query(filters, options) {
640
- return this.adapter.query(filters, options);
641
- }
642
- async setDoc(docId, record) {
643
- this.adapter.setDoc(docId, stampWritableRecord(record));
644
- }
645
- async updateDoc(docId, update) {
646
- this.adapter.updateDoc(docId, buildFirestoreUpdate(update, this.db));
647
- }
648
- async deleteDoc(docId) {
649
- this.adapter.deleteDoc(docId);
650
- }
651
- };
652
- var FirestoreBatchBackend = class {
653
- constructor(adapter, db) {
654
- this.adapter = adapter;
655
- this.db = db;
656
- }
657
- setDoc(docId, record) {
658
- this.adapter.setDoc(docId, stampWritableRecord(record));
659
- }
660
- updateDoc(docId, update) {
661
- this.adapter.updateDoc(docId, buildFirestoreUpdate(update, this.db));
662
- }
663
- deleteDoc(docId) {
664
- this.adapter.deleteDoc(docId);
665
- }
666
- commit() {
667
- return this.adapter.commit();
668
- }
669
- };
670
- var FirestoreBackendImpl = class _FirestoreBackendImpl {
671
- constructor(db, collectionPath, queryMode, scopePath) {
672
- this.db = db;
673
- this.queryMode = queryMode;
674
- this.collectionPath = collectionPath;
675
- this.scopePath = scopePath;
676
- this.adapter = createFirestoreAdapter(db, collectionPath);
677
- if (queryMode === "pipeline") {
678
- this.pipelineAdapter = createPipelineQueryAdapter(db, collectionPath);
679
- }
680
- }
681
- collectionPath;
682
- scopePath;
683
- adapter;
684
- pipelineAdapter;
685
- // --- Reads ---
686
- getDoc(docId) {
687
- return this.adapter.getDoc(docId);
688
- }
689
- query(filters, options) {
690
- if (this.pipelineAdapter) {
691
- return this.pipelineAdapter.query(filters, options);
692
- }
693
- return this.adapter.query(filters, options);
694
- }
695
- // --- Writes ---
696
- setDoc(docId, record) {
697
- return this.adapter.setDoc(docId, stampWritableRecord(record));
698
- }
699
- updateDoc(docId, update) {
700
- return this.adapter.updateDoc(docId, buildFirestoreUpdate(update, this.db));
701
- }
702
- deleteDoc(docId) {
703
- return this.adapter.deleteDoc(docId);
704
- }
705
- // --- Transactions / Batches ---
706
- runTransaction(fn) {
707
- return this.db.runTransaction(async (firestoreTx) => {
708
- const txAdapter = createTransactionAdapter(this.db, this.collectionPath, firestoreTx);
709
- return fn(new FirestoreTransactionBackend(txAdapter, this.db));
710
- });
711
- }
712
- createBatch() {
713
- const batchAdapter = createBatchAdapter(this.db, this.collectionPath);
714
- return new FirestoreBatchBackend(batchAdapter, this.db);
715
- }
716
- // --- Subgraphs ---
717
- subgraph(parentNodeUid, name) {
718
- const subPath = `${this.collectionPath}/${parentNodeUid}/${name}`;
719
- const newScope = this.scopePath ? `${this.scopePath}/${name}` : name;
720
- return new _FirestoreBackendImpl(this.db, subPath, this.queryMode, newScope);
721
- }
722
- // --- Cascade & bulk ---
723
- removeNodeCascade(uid, reader, options) {
724
- return removeNodeCascade(this.db, this.collectionPath, reader, uid, options);
725
- }
726
- bulkRemoveEdges(params, reader, options) {
727
- return bulkRemoveEdges(this.db, this.collectionPath, reader, params, options);
728
- }
729
- // --- Cross-collection ---
730
- async findEdgesGlobal(params, collectionName) {
731
- const name = collectionName ?? this.collectionPath.split("/").pop();
732
- const plan = buildEdgeQueryPlan(params);
733
- if (plan.strategy === "get") {
734
- throw new FiregraphError(
735
- "findEdgesGlobal() requires a query, not a direct document lookup. Omit one of aUid/axbType/bUid to force a query strategy.",
736
- "INVALID_QUERY"
737
- );
738
- }
739
- const collectionGroupRef = this.db.collectionGroup(name);
740
- let q = collectionGroupRef;
741
- for (const f of plan.filters) {
742
- q = q.where(f.field, f.op, f.value);
743
- }
744
- if (plan.options?.orderBy) {
745
- q = q.orderBy(plan.options.orderBy.field, plan.options.orderBy.direction ?? "asc");
746
- }
747
- if (plan.options?.limit !== void 0) {
748
- q = q.limit(plan.options.limit);
749
- }
750
- const snap = await q.get();
751
- return snap.docs.map((doc) => doc.data());
752
- }
753
- };
754
- function createFirestoreBackend(db, collectionPath, options = {}) {
755
- const queryMode = options.queryMode ?? "pipeline";
756
- const scopePath = options.scopePath ?? "";
757
- return new FirestoreBackendImpl(db, collectionPath, queryMode, scopePath);
758
- }
759
-
760
- // src/firestore.ts
761
- var _standardModeWarned = false;
762
- function createGraphClient(db, collectionPath, options) {
763
- const requestedMode = options?.queryMode ?? "pipeline";
764
- const isEmulator = !!process.env.FIRESTORE_EMULATOR_HOST;
765
- const effectiveMode = isEmulator ? "standard" : requestedMode;
766
- if (effectiveMode === "standard" && !isEmulator && requestedMode === "standard" && !_standardModeWarned) {
767
- _standardModeWarned = true;
768
- console.warn(
769
- "[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"
770
- );
771
- }
772
- const backend = createFirestoreBackend(db, collectionPath, { queryMode: effectiveMode });
773
- let metaBackend;
774
- if (options?.registryMode?.collection && options.registryMode.collection !== collectionPath) {
775
- metaBackend = createFirestoreBackend(db, options.registryMode.collection, {
776
- queryMode: effectiveMode
777
- });
778
- }
779
- return new GraphClientImpl(backend, options, metaBackend);
780
- }
781
-
782
- // src/id.ts
783
- import { nanoid } from "nanoid";
784
- function generateId() {
785
- return nanoid();
786
- }
787
-
788
314
  // src/indexes.ts
789
315
  function normalizeField(f) {
790
316
  return typeof f === "string" ? { path: f, desc: false } : { path: f.path, desc: !!f.desc };
@@ -859,35 +385,6 @@ function generateIndexConfig(collection, options = {}) {
859
385
  return { indexes, fieldOverrides: [] };
860
386
  }
861
387
 
862
- // src/record.ts
863
- import { FieldValue as FieldValue2 } from "@google-cloud/firestore";
864
- function buildNodeRecord(aType, uid, data) {
865
- const now = FieldValue2.serverTimestamp();
866
- return {
867
- aType,
868
- aUid: uid,
869
- axbType: NODE_RELATION,
870
- bType: aType,
871
- bUid: uid,
872
- data,
873
- createdAt: now,
874
- updatedAt: now
875
- };
876
- }
877
- function buildEdgeRecord(aType, aUid, axbType, bType, bUid, data) {
878
- const now = FieldValue2.serverTimestamp();
879
- return {
880
- aType,
881
- aUid,
882
- axbType,
883
- bType,
884
- bUid,
885
- data,
886
- createdAt: now,
887
- updatedAt: now
888
- };
889
- }
890
-
891
388
  // src/traverse.ts
892
389
  var DEFAULT_LIMIT = 10;
893
390
  var DEFAULT_MAX_READS = 100;
@@ -896,6 +393,16 @@ var _crossGraphWarned = false;
896
393
  function isGraphClient(reader) {
897
394
  return "subgraph" in reader && typeof reader.subgraph === "function";
898
395
  }
396
+ function readerSupportsExpand(reader) {
397
+ if (!isGraphClient(reader)) return false;
398
+ const client = reader;
399
+ return "capabilities" in client && typeof client.capabilities?.has === "function" && client.capabilities.has("query.join") && typeof client.expand === "function";
400
+ }
401
+ function readerSupportsEngineTraversal(reader) {
402
+ if (!isGraphClient(reader)) return false;
403
+ const client = reader;
404
+ return "capabilities" in client && typeof client.capabilities?.has === "function" && client.capabilities.has("traversal.serverSide") && typeof client.runEngineTraversal === "function";
405
+ }
899
406
  var Semaphore = class {
900
407
  constructor(slots) {
901
408
  this.slots = slots;
@@ -938,7 +445,15 @@ var TraversalBuilderImpl = class {
938
445
  const maxReads = options?.maxReads ?? DEFAULT_MAX_READS;
939
446
  const concurrency = options?.concurrency ?? DEFAULT_CONCURRENCY;
940
447
  const returnIntermediates = options?.returnIntermediates ?? false;
448
+ const engineMode = options?.engineTraversal ?? "auto";
941
449
  const semaphore = new Semaphore(concurrency);
450
+ if (engineMode !== "off") {
451
+ const engineResult = await this.tryEngineTraversal({
452
+ engineMode,
453
+ returnIntermediates
454
+ });
455
+ if (engineResult) return engineResult;
456
+ }
942
457
  let totalReads = 0;
943
458
  let truncated = false;
944
459
  let sources = [
@@ -963,6 +478,62 @@ var TraversalBuilderImpl = class {
963
478
  const resolvedTargetGraph = this.resolveTargetGraph(hop);
964
479
  const direction = hop.direction ?? "forward";
965
480
  const isCrossGraph = direction === "forward" && !!resolvedTargetGraph;
481
+ const sharedReader = sources.every((s) => s.reader === sources[0].reader) ? sources[0].reader : null;
482
+ const canFastPath = !isCrossGraph && sharedReader && readerSupportsExpand(sharedReader);
483
+ if (canFastPath && sharedReader) {
484
+ if (totalReads >= maxReads) {
485
+ hopTruncated = true;
486
+ } else {
487
+ totalReads++;
488
+ const limit = hop.limit ?? DEFAULT_LIMIT;
489
+ const expandParams = {
490
+ sources: sources.map((s) => s.uid),
491
+ axbType: hop.axbType,
492
+ direction
493
+ };
494
+ if (hop.aType) expandParams.aType = hop.aType;
495
+ if (hop.bType) expandParams.bType = hop.bType;
496
+ if (hop.orderBy) expandParams.orderBy = hop.orderBy;
497
+ if (!hop.filter) {
498
+ expandParams.limitPerSource = limit;
499
+ }
500
+ const result = await sharedReader.expand(expandParams);
501
+ let edges2 = result.edges;
502
+ if (hop.filter) {
503
+ edges2 = edges2.filter(hop.filter);
504
+ const counts = /* @__PURE__ */ new Map();
505
+ const kept = [];
506
+ for (const e of edges2) {
507
+ const sourceUid = direction === "forward" ? e.aUid : e.bUid;
508
+ const c = counts.get(sourceUid) ?? 0;
509
+ if (c < limit) {
510
+ counts.set(sourceUid, c + 1);
511
+ kept.push(e);
512
+ }
513
+ }
514
+ edges2 = kept;
515
+ }
516
+ for (const edge of edges2) {
517
+ hopEdges.push({ edge, reader: sharedReader });
518
+ }
519
+ }
520
+ const fastEdges = hopEdges.map((h) => h.edge);
521
+ hopResults.push({
522
+ axbType: hop.axbType,
523
+ depth,
524
+ edges: returnIntermediates ? [...fastEdges] : fastEdges,
525
+ sourceCount,
526
+ truncated: hopTruncated
527
+ });
528
+ if (hopTruncated) truncated = true;
529
+ const seen2 = /* @__PURE__ */ new Map();
530
+ for (const { edge, reader: edgeReader } of hopEdges) {
531
+ const nextUid = direction === "forward" ? edge.bUid : edge.aUid;
532
+ if (!seen2.has(nextUid)) seen2.set(nextUid, edgeReader);
533
+ }
534
+ sources = [...seen2.entries()].map(([uid, reader]) => ({ uid, reader }));
535
+ continue;
536
+ }
966
537
  const tasks = sources.map(({ uid, reader: sourceReader }) => async () => {
967
538
  if (totalReads >= maxReads) {
968
539
  hopTruncated = true;
@@ -1057,6 +628,88 @@ var TraversalBuilderImpl = class {
1057
628
  truncated
1058
629
  };
1059
630
  }
631
+ /**
632
+ * Try to dispatch the entire hop chain as one engine-traversal call.
633
+ * Returns a `TraversalResult` on success, or `undefined` if the spec is
634
+ * ineligible and the caller should fall through to the per-hop loop.
635
+ *
636
+ * `'force'` mode throws on any ineligibility instead of returning
637
+ * `undefined` — the caller intentionally opted out of fallback.
638
+ */
639
+ async tryEngineTraversal(args) {
640
+ const { engineMode, returnIntermediates } = args;
641
+ const refuse = (reason) => {
642
+ if (engineMode === "force") {
643
+ throw new FiregraphError(`engineTraversal: 'force' but ${reason}`, "UNSUPPORTED_OPERATION");
644
+ }
645
+ return void 0;
646
+ };
647
+ if (!readerSupportsEngineTraversal(this.reader)) {
648
+ return refuse("reader does not declare traversal.serverSide capability");
649
+ }
650
+ const client = this.reader;
651
+ const engineHops = [];
652
+ for (let i = 0; i < this.hops.length; i++) {
653
+ const hop = this.hops[i];
654
+ if (hop.filter) {
655
+ return refuse(`hop ${i} (${hop.axbType}) carries a JS filter callback`);
656
+ }
657
+ const targetGraph = this.resolveTargetGraph(hop);
658
+ const direction = hop.direction ?? "forward";
659
+ if (targetGraph) {
660
+ return refuse(`hop ${i} (${hop.axbType}) is cross-graph (targetGraph=${targetGraph})`);
661
+ }
662
+ const limit = hop.limit ?? DEFAULT_LIMIT;
663
+ const engineHop = {
664
+ axbType: hop.axbType,
665
+ direction,
666
+ limitPerSource: limit
667
+ };
668
+ if (hop.aType) engineHop.aType = hop.aType;
669
+ if (hop.bType) engineHop.bType = hop.bType;
670
+ if (hop.orderBy) engineHop.orderBy = hop.orderBy;
671
+ engineHops.push(engineHop);
672
+ }
673
+ const params = {
674
+ sources: [this.startUid],
675
+ hops: engineHops
676
+ };
677
+ const compiled = compileEngineTraversal(params);
678
+ if (!compiled.eligible) {
679
+ return refuse(compiled.reason);
680
+ }
681
+ let engineResult;
682
+ try {
683
+ engineResult = await client.runEngineTraversal(params);
684
+ } catch (err) {
685
+ if (engineMode === "force") throw err;
686
+ return void 0;
687
+ }
688
+ const hopResults = [];
689
+ for (let i = 0; i < this.hops.length; i++) {
690
+ const definedHop = this.hops[i];
691
+ const engineHopResult = engineResult.hops[i] ?? { edges: [], sourceCount: 0 };
692
+ const edges = engineHopResult.edges;
693
+ const hopTruncated = edges.length >= engineHops[i].limitPerSource;
694
+ hopResults.push({
695
+ axbType: definedHop.axbType,
696
+ depth: i,
697
+ edges: returnIntermediates ? [...edges] : edges,
698
+ sourceCount: engineHopResult.sourceCount,
699
+ truncated: hopTruncated
700
+ });
701
+ }
702
+ const lastHop = hopResults[hopResults.length - 1];
703
+ return {
704
+ nodes: lastHop.edges,
705
+ hops: hopResults,
706
+ // One server-side round trip — same accounting as the `expand()`
707
+ // fast path. The tree response can carry up to `estimatedReads`
708
+ // docs total, but the budget is in round trips, not docs.
709
+ totalReads: 1,
710
+ truncated: hopResults.some((h) => h.truncated)
711
+ };
712
+ }
1060
713
  /**
1061
714
  * Resolve the targetGraph for a hop. Priority:
1062
715
  * 1. Explicit `hop.targetGraph` (user override)
@@ -1178,6 +831,7 @@ function defineViews(input) {
1178
831
  }
1179
832
  export {
1180
833
  BOOTSTRAP_ENTRIES,
834
+ CapabilityNotSupportedError,
1181
835
  CrossBackendTransactionError,
1182
836
  DEFAULT_CORE_INDEXES,
1183
837
  DEFAULT_QUERY_LIMIT,
@@ -1204,9 +858,7 @@ export {
1204
858
  appendStorageScope,
1205
859
  applyMigrationChain,
1206
860
  buildEdgeQueryPlan,
1207
- buildEdgeRecord,
1208
861
  buildNodeQueryPlan,
1209
- buildNodeRecord,
1210
862
  compileMigrationFn,
1211
863
  compileMigrations,
1212
864
  compileSchema,
@@ -1222,6 +874,7 @@ export {
1222
874
  defaultExecutor,
1223
875
  defineConfig,
1224
876
  defineViews,
877
+ deleteField,
1225
878
  deserializeFirestoreTypes,
1226
879
  destroySandboxWorker,
1227
880
  discoverEntities,