@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.
- package/README.md +355 -78
- package/dist/backend-DuvHGgK1.d.cts +1897 -0
- package/dist/backend-DuvHGgK1.d.ts +1897 -0
- package/dist/backend.cjs +365 -5
- package/dist/backend.cjs.map +1 -1
- package/dist/backend.d.cts +25 -5
- package/dist/backend.d.ts +25 -5
- package/dist/backend.js +209 -7
- package/dist/backend.js.map +1 -1
- package/dist/chunk-2DHMNTV6.js +16 -0
- package/dist/chunk-2DHMNTV6.js.map +1 -0
- package/dist/chunk-4MMQ5W74.js +288 -0
- package/dist/chunk-4MMQ5W74.js.map +1 -0
- package/dist/{chunk-5753Y42M.js → chunk-C2QMD7RY.js} +6 -10
- package/dist/chunk-C2QMD7RY.js.map +1 -0
- package/dist/chunk-D4J7Z4FE.js +67 -0
- package/dist/chunk-D4J7Z4FE.js.map +1 -0
- package/dist/chunk-EQJUUVFG.js +14 -0
- package/dist/chunk-EQJUUVFG.js.map +1 -0
- package/dist/chunk-N5HFDWQX.js +23 -0
- package/dist/chunk-N5HFDWQX.js.map +1 -0
- package/dist/chunk-PAD7WFFU.js +573 -0
- package/dist/chunk-PAD7WFFU.js.map +1 -0
- package/dist/chunk-TK64DNVK.js +256 -0
- package/dist/chunk-TK64DNVK.js.map +1 -0
- package/dist/{chunk-NJSOD64C.js → chunk-WRTFC5NG.js} +438 -30
- package/dist/chunk-WRTFC5NG.js.map +1 -0
- package/dist/client-BKi3vk0Q.d.ts +34 -0
- package/dist/client-BrsaXtDV.d.cts +34 -0
- package/dist/cloudflare/index.cjs +1386 -74
- package/dist/cloudflare/index.cjs.map +1 -1
- package/dist/cloudflare/index.d.cts +217 -13
- package/dist/cloudflare/index.d.ts +217 -13
- package/dist/cloudflare/index.js +639 -180
- package/dist/cloudflare/index.js.map +1 -1
- package/dist/codegen/index.d.cts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/errors-BRc3I_eH.d.cts +73 -0
- package/dist/errors-BRc3I_eH.d.ts +73 -0
- package/dist/firestore-enterprise/index.cjs +3877 -0
- package/dist/firestore-enterprise/index.cjs.map +1 -0
- package/dist/firestore-enterprise/index.d.cts +141 -0
- package/dist/firestore-enterprise/index.d.ts +141 -0
- package/dist/firestore-enterprise/index.js +985 -0
- package/dist/firestore-enterprise/index.js.map +1 -0
- package/dist/firestore-standard/index.cjs +3117 -0
- package/dist/firestore-standard/index.cjs.map +1 -0
- package/dist/firestore-standard/index.d.cts +49 -0
- package/dist/firestore-standard/index.d.ts +49 -0
- package/dist/firestore-standard/index.js +283 -0
- package/dist/firestore-standard/index.js.map +1 -0
- package/dist/index.cjs +809 -534
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -100
- package/dist/index.d.ts +24 -100
- package/dist/index.js +184 -531
- package/dist/index.js.map +1 -1
- package/dist/registry-Bc7h6WTM.d.cts +64 -0
- package/dist/registry-C2KUPVZj.d.ts +64 -0
- package/dist/{scope-path-B1G3YiA7.d.ts → scope-path-CROFZGr9.d.cts} +1 -56
- package/dist/{scope-path-B1G3YiA7.d.cts → scope-path-CROFZGr9.d.ts} +1 -56
- package/dist/{serialization-ZZ7RSDRX.js → serialization-OE2PFZMY.js} +6 -4
- package/dist/sqlite/index.cjs +3631 -0
- package/dist/sqlite/index.cjs.map +1 -0
- package/dist/sqlite/index.d.cts +111 -0
- package/dist/sqlite/index.d.ts +111 -0
- package/dist/sqlite/index.js +1164 -0
- package/dist/sqlite/index.js.map +1 -0
- package/package.json +33 -3
- package/dist/backend-U-MLShlg.d.ts +0 -97
- package/dist/backend-np4gEVhB.d.cts +0 -97
- package/dist/chunk-5753Y42M.js.map +0 -1
- package/dist/chunk-NJSOD64C.js.map +0 -1
- package/dist/chunk-R7CRGYY4.js +0 -94
- package/dist/chunk-R7CRGYY4.js.map +0 -1
- package/dist/types-BGWxcpI_.d.cts +0 -736
- package/dist/types-BGWxcpI_.d.ts +0 -736
- /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-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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,
|