@typicalday/firegraph 0.12.0 → 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 (70) hide show
  1. package/README.md +317 -73
  2. package/dist/backend-DuvHGgK1.d.cts +1897 -0
  3. package/dist/backend-DuvHGgK1.d.ts +1897 -0
  4. package/dist/backend.cjs +222 -3
  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 +197 -4
  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-D4J7Z4FE.js +67 -0
  15. package/dist/chunk-D4J7Z4FE.js.map +1 -0
  16. package/dist/chunk-N5HFDWQX.js +23 -0
  17. package/dist/chunk-N5HFDWQX.js.map +1 -0
  18. package/dist/chunk-PAD7WFFU.js +573 -0
  19. package/dist/chunk-PAD7WFFU.js.map +1 -0
  20. package/dist/{chunk-AWW4MUJ5.js → chunk-TK64DNVK.js} +12 -1
  21. package/dist/chunk-TK64DNVK.js.map +1 -0
  22. package/dist/{chunk-HONQY4HF.js → chunk-WRTFC5NG.js} +362 -17
  23. package/dist/chunk-WRTFC5NG.js.map +1 -0
  24. package/dist/client-BKi3vk0Q.d.ts +34 -0
  25. package/dist/client-BrsaXtDV.d.cts +34 -0
  26. package/dist/cloudflare/index.cjs +930 -3
  27. package/dist/cloudflare/index.cjs.map +1 -1
  28. package/dist/cloudflare/index.d.cts +213 -12
  29. package/dist/cloudflare/index.d.ts +213 -12
  30. package/dist/cloudflare/index.js +562 -281
  31. package/dist/cloudflare/index.js.map +1 -1
  32. package/dist/codegen/index.d.cts +1 -1
  33. package/dist/codegen/index.d.ts +1 -1
  34. package/dist/errors-BRc3I_eH.d.cts +73 -0
  35. package/dist/errors-BRc3I_eH.d.ts +73 -0
  36. package/dist/firestore-enterprise/index.cjs +3877 -0
  37. package/dist/firestore-enterprise/index.cjs.map +1 -0
  38. package/dist/firestore-enterprise/index.d.cts +141 -0
  39. package/dist/firestore-enterprise/index.d.ts +141 -0
  40. package/dist/firestore-enterprise/index.js +985 -0
  41. package/dist/firestore-enterprise/index.js.map +1 -0
  42. package/dist/firestore-standard/index.cjs +3117 -0
  43. package/dist/firestore-standard/index.cjs.map +1 -0
  44. package/dist/firestore-standard/index.d.cts +49 -0
  45. package/dist/firestore-standard/index.d.ts +49 -0
  46. package/dist/firestore-standard/index.js +283 -0
  47. package/dist/firestore-standard/index.js.map +1 -0
  48. package/dist/index.cjs +590 -550
  49. package/dist/index.cjs.map +1 -1
  50. package/dist/index.d.cts +9 -37
  51. package/dist/index.d.ts +9 -37
  52. package/dist/index.js +178 -555
  53. package/dist/index.js.map +1 -1
  54. package/dist/{registry-Fi074zVa.d.ts → registry-Bc7h6WTM.d.cts} +1 -1
  55. package/dist/{registry-B1qsVL0E.d.cts → registry-C2KUPVZj.d.ts} +1 -1
  56. package/dist/{scope-path-B1G3YiA7.d.cts → scope-path-CROFZGr9.d.cts} +1 -56
  57. package/dist/{scope-path-B1G3YiA7.d.ts → scope-path-CROFZGr9.d.ts} +1 -56
  58. package/dist/sqlite/index.cjs +3631 -0
  59. package/dist/sqlite/index.cjs.map +1 -0
  60. package/dist/sqlite/index.d.cts +111 -0
  61. package/dist/sqlite/index.d.ts +111 -0
  62. package/dist/sqlite/index.js +1164 -0
  63. package/dist/sqlite/index.js.map +1 -0
  64. package/package.json +33 -3
  65. package/dist/backend-BsR0lnFL.d.ts +0 -200
  66. package/dist/backend-Ct-fLlkG.d.cts +0 -200
  67. package/dist/chunk-AWW4MUJ5.js.map +0 -1
  68. package/dist/chunk-HONQY4HF.js.map +0 -1
  69. package/dist/types-DxYLy8Ol.d.cts +0 -770
  70. package/dist/types-DxYLy8Ol.d.ts +0 -770
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/firestore-enterprise/backend.ts","../../src/internal/firestore-bulk-dml.ts","../../src/internal/firestore-expand.ts","../../src/internal/firestore-fulltext.ts","../../src/internal/firestore-geo.ts","../../src/internal/firestore-traverse.ts","../../src/firestore-enterprise/pipeline-adapter.ts"],"sourcesContent":["/**\n * Firestore Enterprise edition `StorageBackend`.\n *\n * The Enterprise edition wires the classic Query API (transactions, single-\n * doc reads/writes, listeners) alongside the Pipelines query engine. Pipeline\n * mode is the default for `query()` outside the emulator; classic mode is\n * always used for transactions and doc-level operations because pipelines\n * have no transactional binding (per Firestore's GA notes — April 2026).\n *\n * Capability declarations target the full Enterprise surface that the\n * shipped `@google-cloud/firestore@8.5.0` SDK exposes typed APIs for:\n * core read/write/transactions/batch/subgraph, `query.aggregate`,\n * `query.select`, `query.join` (via Pipelines `equalAny(field, values)`\n * for single-statement multi-source fan-out), `query.dml` (gated by\n * the opt-in `previewDml` flag — Pipeline `delete()` / `update(...)`\n * stages are `@beta` in 8.5.0; see the per-capability rationale below),\n * `search.vector` (via the classic `findNearest` API for parity with\n * Standard), `search.fullText` (via Pipelines\n * `search({ query: documentMatches(...) })`), `search.geo` (via\n * Pipelines `search({ query: geoDistance(...).lessThanOrEqual(...) })`),\n * and `raw.firestore`. Capabilities that remain fundamentally absent on\n * 8.5.0 — `realtime.listen` — stay undeclared until the SDK exposes an\n * addressable feature. See the comment block above\n * `FirestoreEnterpriseCapability` for the per-capability rationale.\n */\n\nimport type { Firestore, Query, Transaction } from '@google-cloud/firestore';\nimport { FieldValue } from '@google-cloud/firestore';\n\nimport {\n bulkRemoveEdges as bulkRemoveEdgesImpl,\n removeNodeCascade as removeNodeCascadeImpl,\n} from '../bulk.js';\nimport { FiregraphError } from '../errors.js';\nimport type {\n BackendCapabilities,\n BatchBackend,\n StorageBackend,\n TransactionBackend,\n UpdatePayload,\n WritableRecord,\n WriteMode,\n} from '../internal/backend.js';\nimport { createCapabilities } from '../internal/backend.js';\nimport { runFirestoreAggregate } from '../internal/firestore-aggregate.js';\nimport {\n runFirestorePipelineDelete,\n runFirestorePipelineUpdate,\n} from '../internal/firestore-bulk-dml.js';\nimport type {\n BatchAdapter,\n FirestoreAdapter,\n TransactionAdapter,\n} from '../internal/firestore-classic-adapter.js';\nimport {\n createBatchAdapter,\n createFirestoreAdapter,\n createTransactionAdapter,\n} from '../internal/firestore-classic-adapter.js';\nimport { runFirestoreClassicExpand } from '../internal/firestore-classic-expand.js';\nimport { runFirestorePipelineExpand } from '../internal/firestore-expand.js';\nimport { runFirestoreFullTextSearch } from '../internal/firestore-fulltext.js';\nimport { runFirestoreGeoSearch } from '../internal/firestore-geo.js';\nimport { runFirestoreFindEdgesProjected } from '../internal/firestore-projection.js';\nimport { runFirestoreEngineTraversal } from '../internal/firestore-traverse.js';\nimport { runFirestoreFindNearest } from '../internal/firestore-vector.js';\nimport type { DataPathOp } from '../internal/write-plan.js';\nimport { assertSafePath, assertUpdatePayloadExclusive } from '../internal/write-plan.js';\nimport { buildEdgeQueryPlan } from '../query.js';\nimport { deserializeFirestoreTypes } from '../serialization.js';\nimport type {\n AggregateSpec,\n BulkOptions,\n BulkResult,\n BulkUpdatePatch,\n CascadeResult,\n EngineTraversalParams,\n EngineTraversalResult,\n ExpandParams,\n ExpandResult,\n FindEdgesParams,\n FindNearestParams,\n FullTextSearchParams,\n GeoSearchParams,\n GraphReader,\n QueryFilter,\n QueryOptions,\n StoredGraphRecord,\n} from '../types.js';\nimport type { PipelineQueryAdapter } from './pipeline-adapter.js';\nimport { createPipelineQueryAdapter } from './pipeline-adapter.js';\n\n/**\n * Capability union declared by the Firestore Enterprise backend.\n *\n * `core.transactions` is included because transactions are still supported\n * via the classic Query API (pipelines themselves are not transactionally\n * bound; the GA notes call this out explicitly). `search.vector` (Phase 8)\n * is implemented via the classic `Query.findNearest(...)` API for parity\n * with the Standard edition — see `findNearest()` below.\n *\n * `search.fullText` and `search.geo` (Phase 12) are implemented via the\n * Pipelines `search(...)` stage exposed in `@google-cloud/firestore@8.5.0`.\n * The 8.5.0 typed surface adds `documentMatches(...)`, `score()`, and\n * `geoDistance(...)` as first-class expressions (all `@beta` and gated to\n * the `Search` stage); the `Pipeline.search(options)` method itself is\n * also first-class. Standard does NOT declare these caps — full-text\n * search and geospatial queries are Enterprise-only product features\n * regardless of SDK shape, so the routing invariant (declared cap ⇒\n * method exists ⇒ index exists) demands that the cap stay edition-gated.\n *\n * Conservative declaration matters here: declaring a capability we don't\n * implement turns the type-level gate (Phase 3) into a lie that throws at\n * runtime instead of failing to compile. The inverse also matters:\n * implementing without declaring leaves the surface accessible only via\n * `as any` casts, which silently bypasses the capability gate.\n *\n * **`query.join` (Phase 13a) is implemented via Pipelines `equalAny`.**\n * Multi-source fan-out collapses to a single round trip:\n * `db.pipeline().collection(path).where(equalAny(sourceField, sources))\n * .execute()`. The shared helper lives at `src/internal/firestore-expand.ts`.\n * The classic Query API caps `'in'` at 30 elements per call, forcing\n * `ceil(N/30)` round trips; pipeline `equalAny(field, values)` accepts\n * an arbitrary list, so a 1k-source fan-out goes from ~34 round trips\n * to one. When `queryMode === 'classic'` (emulator or explicit override),\n * the classic chunked path in `firestore-classic-expand.ts` is used\n * instead — same observable contract, different round-trip profile.\n *\n * **`query.dml` (Phase 13b) is wired through `runFirestorePipelineDelete`\n * and `runFirestorePipelineUpdate` (`src/internal/firestore-bulk-dml.ts`)\n * but is gated by an opt-in `FirestoreEnterpriseOptions.previewDml`\n * flag.** The underlying `Pipeline.delete()` and\n * `Pipeline.update(transformedFields)` stages are `@beta` in\n * `@google-cloud/firestore@8.5.0` (`firestore.d.ts:12647` /\n * `firestore.d.ts:12662`). When `previewDml: true`, the cap is declared\n * and `bulkDelete` / `bulkUpdate` dispatch to single-statement Pipelines\n * stages — same observable contract as SQLite/DO, one round trip per\n * call, no fetch-then-write loop. A one-time `console.warn` fires on\n * backend construction so the `@beta` status is visible. When\n * `previewDml: false` (default), the cap is NOT declared; `bulk.ts`\n * cascade and `client.bulkDelete()` / `client.bulkUpdate()` route\n * through the existing `bulkRemoveEdges` fetch-then-write fallback.\n * SQLite and Cloudflare DO declare `query.dml` unconditionally because\n * their `DELETE … WHERE …` / `UPDATE … SET …` paths are GA, not preview.\n */\nexport type FirestoreEnterpriseCapability =\n | 'core.read'\n | 'core.write'\n | 'core.transactions'\n | 'core.batch'\n | 'core.subgraph'\n | 'query.aggregate'\n | 'query.select'\n | 'query.join'\n | 'query.dml'\n | 'traversal.serverSide'\n | 'search.vector'\n | 'search.fullText'\n | 'search.geo'\n | 'raw.firestore';\n\n/**\n * Base capability set declared by every Firestore Enterprise backend.\n * `query.dml` is conditionally added on construction when\n * `FirestoreEnterpriseOptions.previewDml === true`; see the\n * `query.dml` rationale comment above and the constructor below.\n */\nconst ENTERPRISE_BASE_CAPS: ReadonlySet<FirestoreEnterpriseCapability> =\n new Set<FirestoreEnterpriseCapability>([\n 'core.read',\n 'core.write',\n 'core.transactions',\n 'core.batch',\n 'core.subgraph',\n 'query.aggregate',\n 'query.select',\n 'query.join',\n 'traversal.serverSide',\n 'search.vector',\n 'search.fullText',\n 'search.geo',\n 'raw.firestore',\n ]);\n\nexport type FirestoreEnterpriseQueryMode = 'pipeline' | 'classic';\n\nexport interface FirestoreEnterpriseOptions {\n /**\n * Query execution mode for `findEdges` / `findNodes`. `'pipeline'` (the\n * default outside the emulator) routes through the Pipeline query engine;\n * `'classic'` falls back to the Query API. Pipeline-only capabilities\n * (search, aggregate, etc., once implemented) always use pipelines\n * regardless of this option.\n *\n * The emulator does not execute pipeline queries, so this option is\n * forced to `'classic'` whenever `FIRESTORE_EMULATOR_HOST` is set, with\n * a one-time `console.warn` if the caller explicitly asked for pipeline\n * mode.\n */\n defaultQueryMode?: FirestoreEnterpriseQueryMode;\n /**\n * Opt in to Pipelines DML stages (`@beta` in `@google-cloud/firestore@8.5.0`:\n * `Pipeline.delete()` at `firestore.d.ts:12647`,\n * `Pipeline.update(transformedFields)` at `firestore.d.ts:12662`).\n *\n * When `false` (default), this backend does NOT declare `query.dml` and\n * `client.bulkDelete()` / `client.bulkUpdate()` throw\n * `UNSUPPORTED_OPERATION` (or, via `bulk.ts`'s cascade path, fall back\n * to the read-then-write loop in `bulkRemoveEdges`).\n *\n * When `true`, the backend declares `query.dml` and dispatches both\n * methods to single-statement Pipeline stages via\n * `runFirestorePipelineDelete` / `runFirestorePipelineUpdate`. A\n * one-time `console.warn` fires on the first backend created with the\n * flag so the `@beta` status is visible without disrupting tests or\n * production traffic. The flag intentionally has no effect on\n * `defaultQueryMode: 'classic'` — the classic-API path has no DML stage\n * to fall back to, so opting into preview DML in classic mode is a\n * misconfiguration; we accept the flag silently rather than throw to\n * keep the option surface ergonomic across the dual-mode toggle. The\n * routing layer relies on `query.dml` being a structural cap, not a\n * runtime promise — so the cap is still declared even in classic mode,\n * and the methods still dispatch through Pipelines (Pipeline DML works\n * regardless of the read-path `queryMode`).\n */\n previewDml?: boolean;\n /** Internal: the logical scope path inherited from a parent subgraph. */\n scopePath?: string;\n}\n\nlet _emulatorFallbackWarned = false;\nlet _classicInProductionWarned = false;\nlet _previewDmlWarned = false;\n\n/** Build a `data.a.b.c` dotted path for Firestore's `update()` API. */\nfunction dottedDataPath(op: DataPathOp): string {\n assertSafePath(op.path);\n return `data.${op.path.join('.')}`;\n}\n\n/**\n * Build the patch payload Firestore expects from an `UpdatePayload`.\n *\n * - `replaceData` sets the whole `data` field at once (full replacement).\n * Tagged Firestore types from the migration sandbox are reconstructed\n * here. Cannot be combined with `dataOps`.\n * - `dataOps` becomes one Firestore field-update entry per terminal op,\n * keyed by `data.<dotted.path>`. Delete ops use `FieldValue.delete()`.\n * Sibling keys at every depth are preserved by Firestore's update\n * semantics for nested maps.\n * - `updatedAt` is always stamped with `FieldValue.serverTimestamp()`.\n * - `v` is stamped at the root when provided.\n */\nfunction buildFirestoreUpdate(update: UpdatePayload, db: Firestore): Record<string, unknown> {\n assertUpdatePayloadExclusive(update);\n const out: Record<string, unknown> = {\n updatedAt: FieldValue.serverTimestamp(),\n };\n if (update.replaceData) {\n out.data = deserializeFirestoreTypes(update.replaceData, db);\n } else if (update.dataOps) {\n for (const op of update.dataOps) {\n const key = dottedDataPath(op);\n out[key] = op.delete ? FieldValue.delete() : op.value;\n }\n }\n if (update.v !== undefined) {\n out.v = update.v;\n }\n return out;\n}\n\n/**\n * Stamp `createdAt`/`updatedAt` server-timestamp sentinels on a\n * timestampless record. Used for `setDoc`.\n */\nfunction stampWritableRecord(record: WritableRecord): Record<string, unknown> {\n const now = FieldValue.serverTimestamp();\n const out: Record<string, unknown> = {\n aType: record.aType,\n aUid: record.aUid,\n axbType: record.axbType,\n bType: record.bType,\n bUid: record.bUid,\n data: record.data,\n createdAt: now,\n updatedAt: now,\n };\n if (record.v !== undefined) out.v = record.v;\n return out;\n}\n\nclass FirestoreEnterpriseTransactionBackend implements TransactionBackend {\n constructor(\n private readonly adapter: TransactionAdapter,\n private readonly db: Firestore,\n ) {}\n\n getDoc(docId: string): Promise<StoredGraphRecord | null> {\n return this.adapter.getDoc(docId);\n }\n\n query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]> {\n return this.adapter.query(filters, options);\n }\n\n async setDoc(docId: string, record: WritableRecord, mode: WriteMode): Promise<void> {\n this.adapter.setDoc(\n docId,\n stampWritableRecord(record),\n mode === 'merge' ? { merge: true } : undefined,\n );\n }\n\n async updateDoc(docId: string, update: UpdatePayload): Promise<void> {\n this.adapter.updateDoc(docId, buildFirestoreUpdate(update, this.db));\n }\n\n async deleteDoc(docId: string): Promise<void> {\n this.adapter.deleteDoc(docId);\n }\n}\n\nclass FirestoreEnterpriseBatchBackend implements BatchBackend {\n constructor(\n private readonly adapter: BatchAdapter,\n private readonly db: Firestore,\n ) {}\n\n setDoc(docId: string, record: WritableRecord, mode: WriteMode): void {\n this.adapter.setDoc(\n docId,\n stampWritableRecord(record),\n mode === 'merge' ? { merge: true } : undefined,\n );\n }\n\n updateDoc(docId: string, update: UpdatePayload): void {\n this.adapter.updateDoc(docId, buildFirestoreUpdate(update, this.db));\n }\n\n deleteDoc(docId: string): void {\n this.adapter.deleteDoc(docId);\n }\n\n commit(): Promise<void> {\n return this.adapter.commit();\n }\n}\n\nclass FirestoreEnterpriseBackendImpl implements StorageBackend<FirestoreEnterpriseCapability> {\n readonly capabilities: BackendCapabilities<FirestoreEnterpriseCapability>;\n readonly collectionPath: string;\n readonly scopePath: string;\n private readonly adapter: FirestoreAdapter;\n private readonly pipelineAdapter?: PipelineQueryAdapter;\n\n constructor(\n private readonly db: Firestore,\n collectionPath: string,\n private readonly queryMode: FirestoreEnterpriseQueryMode,\n scopePath: string,\n private readonly previewDml: boolean,\n ) {\n this.collectionPath = collectionPath;\n this.scopePath = scopePath;\n this.adapter = createFirestoreAdapter(db, collectionPath);\n if (queryMode === 'pipeline') {\n this.pipelineAdapter = createPipelineQueryAdapter(db, collectionPath);\n }\n // `query.dml` is opt-in because the Pipeline `delete()` / `update(...)`\n // stages are `@beta` in 8.5.0; declaring it without the flag would\n // promise behaviour we'd then have to revert if the SDK shape shifts.\n const caps = previewDml\n ? new Set<FirestoreEnterpriseCapability>([...ENTERPRISE_BASE_CAPS, 'query.dml'])\n : ENTERPRISE_BASE_CAPS;\n this.capabilities = createCapabilities(caps);\n }\n\n // --- Reads ---\n\n getDoc(docId: string): Promise<StoredGraphRecord | null> {\n return this.adapter.getDoc(docId);\n }\n\n query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]> {\n if (this.pipelineAdapter) {\n return this.pipelineAdapter.query(filters, options);\n }\n return this.adapter.query(filters, options);\n }\n\n // --- Writes ---\n\n setDoc(docId: string, record: WritableRecord, mode: WriteMode): Promise<void> {\n return this.adapter.setDoc(\n docId,\n stampWritableRecord(record),\n mode === 'merge' ? { merge: true } : undefined,\n );\n }\n\n updateDoc(docId: string, update: UpdatePayload): Promise<void> {\n return this.adapter.updateDoc(docId, buildFirestoreUpdate(update, this.db));\n }\n\n deleteDoc(docId: string): Promise<void> {\n return this.adapter.deleteDoc(docId);\n }\n\n // --- Transactions / Batches ---\n\n runTransaction<T>(fn: (tx: TransactionBackend) => Promise<T>): Promise<T> {\n return this.db.runTransaction(async (firestoreTx: Transaction) => {\n const txAdapter = createTransactionAdapter(this.db, this.collectionPath, firestoreTx);\n return fn(new FirestoreEnterpriseTransactionBackend(txAdapter, this.db));\n });\n }\n\n createBatch(): BatchBackend {\n const batchAdapter = createBatchAdapter(this.db, this.collectionPath);\n return new FirestoreEnterpriseBatchBackend(batchAdapter, this.db);\n }\n\n // --- Subgraphs ---\n\n subgraph(parentNodeUid: string, name: string): StorageBackend {\n const subPath = `${this.collectionPath}/${parentNodeUid}/${name}`;\n const newScope = this.scopePath ? `${this.scopePath}/${name}` : name;\n // Inherit `previewDml` so subgraphs declare the same cap as the parent\n // — otherwise `client.subgraph(uid).bulkDelete(...)` would silently\n // route through the read-then-write fallback while the parent client\n // dispatched through Pipelines, breaking parity.\n return new FirestoreEnterpriseBackendImpl(\n this.db,\n subPath,\n this.queryMode,\n newScope,\n this.previewDml,\n );\n }\n\n // --- Cascade & bulk ---\n\n removeNodeCascade(\n uid: string,\n reader: GraphReader,\n options?: BulkOptions,\n ): Promise<CascadeResult> {\n return removeNodeCascadeImpl(this.db, this.collectionPath, reader, uid, options);\n }\n\n bulkRemoveEdges(\n params: FindEdgesParams,\n reader: GraphReader,\n options?: BulkOptions,\n ): Promise<BulkResult> {\n return bulkRemoveEdgesImpl(this.db, this.collectionPath, reader, params, options);\n }\n\n // --- Cross-collection ---\n\n async findEdgesGlobal(\n params: FindEdgesParams,\n collectionName?: string,\n ): Promise<StoredGraphRecord[]> {\n const name = collectionName ?? this.collectionPath.split('/').pop()!;\n const plan = buildEdgeQueryPlan(params);\n\n if (plan.strategy === 'get') {\n throw new FiregraphError(\n 'findEdgesGlobal() requires a query, not a direct document lookup. ' +\n 'Omit one of aUid/axbType/bUid to force a query strategy.',\n 'INVALID_QUERY',\n );\n }\n\n const collectionGroupRef = this.db.collectionGroup(name);\n let q: Query = collectionGroupRef;\n for (const f of plan.filters) {\n q = q.where(f.field, f.op, f.value);\n }\n if (plan.options?.orderBy) {\n q = q.orderBy(plan.options.orderBy.field, plan.options.orderBy.direction ?? 'asc');\n }\n if (plan.options?.limit !== undefined) {\n q = q.limit(plan.options.limit);\n }\n const snap = await q.get();\n return snap.docs.map((doc) => doc.data() as StoredGraphRecord);\n }\n\n // --- Aggregate ---\n\n /**\n * Aggregate via the classic `Query.aggregate()` API. Supports count/sum/avg\n * — min/max throws `UNSUPPORTED_AGGREGATE`. The Pipelines `aggregate()`\n * stage could in principle add min/max on Enterprise, but that is deferred\n * to a future phase; both editions currently route through the same\n * classic-API helper so capability semantics stay symmetric.\n */\n aggregate(spec: AggregateSpec, filters: QueryFilter[]): Promise<Record<string, number>> {\n return runFirestoreAggregate(this.db.collection(this.collectionPath), spec, filters, {\n edition: 'enterprise',\n });\n }\n\n // --- Server-side projection (capability: query.select) ---\n\n /**\n * Run a projecting query via the shared classic-API helper. Enterprise and\n * Standard delegate to the same implementation so the projection contract\n * stays consistent.\n *\n * Why classic and not the pipeline `select()` stage: the byte-savings\n * deliverable (the only reason `findEdgesProjected` exists) is achieved\n * by either path; the classic API works on both editions today and\n * sidesteps the pipeline-vs-emulator forking the rest of this backend has\n * to manage. When pipeline `select()` becomes preferable for some other\n * reason — e.g. composing with a future pipeline-only stage — swap the\n * implementation behind `runFirestoreFindEdgesProjected`; callers and\n * the capability declaration stay put.\n */\n findEdgesProjected(\n select: ReadonlyArray<string>,\n filters: QueryFilter[],\n options?: QueryOptions,\n ): Promise<Array<Record<string, unknown>>> {\n return runFirestoreFindEdgesProjected(\n this.db.collection(this.collectionPath),\n select,\n filters,\n options,\n );\n }\n\n // --- Native vector / nearest-neighbour search (capability: search.vector) ---\n\n /**\n * Run a vector / nearest-neighbour query via the shared classic-API\n * helper. Enterprise and Standard delegate to one implementation so the\n * field-path normalisation, validation surface, and result shape stay\n * consistent across editions.\n *\n * Why classic and not the pipeline `findNearest` stage: the deliverable\n * (top-K by similarity) is achieved by either path; the classic API\n * works identically on both editions today and sidesteps the\n * pipeline-vs-emulator forking the rest of this backend has to manage.\n * When pipeline `findNearest` becomes preferable for composing with\n * other pipeline stages, swap the implementation behind\n * `runFirestoreFindNearest`; callers and the capability declaration\n * stay put.\n *\n * Index requirements are identical to Standard — single-field vector\n * index on the indexed `vectorField`, plus a composite index whenever\n * additional `where` filters narrow the candidate set.\n */\n findNearest(params: FindNearestParams): Promise<StoredGraphRecord[]> {\n return runFirestoreFindNearest(this.db.collection(this.collectionPath), params);\n }\n\n // --- Native full-text search (capability: search.fullText) ---\n\n /**\n * Run a full-text search via Firestore Pipelines `search(...)` stage.\n * Translates `documentMatches(query)` against the indexed search\n * fields, sorts by relevance score (`score().descending()`), and\n * applies identifying filters as a follow-up `where(...)` stage\n * because `search` must be the first stage of a pipeline.\n *\n * Enterprise-only: full-text search is an Enterprise product feature.\n * Standard does not declare `search.fullText` regardless of SDK\n * surface, and the SQLite-shaped backends have no native FTS index.\n *\n * Index requirements: an FTS index on the indexed search fields\n * (configured in Firestore's index config). Without the index the\n * underlying `search` stage returns no rows; the firegraph layer\n * cannot detect that case ahead of time.\n */\n fullTextSearch(params: FullTextSearchParams): Promise<StoredGraphRecord[]> {\n return runFirestoreFullTextSearch(this.db, this.collectionPath, params);\n }\n\n // --- Native geospatial distance search (capability: search.geo) ---\n\n /**\n * Run a geospatial distance query via Firestore Pipelines\n * `search(...)` stage. The same `geoDistance(geoField, point)`\n * expression feeds the radius cap (`<= radiusMeters`) and the\n * nearest-first sort (when `orderByDistance` is true / unset).\n * Identifying filters apply as a follow-up `where(...)` stage.\n *\n * Enterprise-only: same Enterprise-product-feature gating as FTS.\n *\n * Index requirements: a geospatial index on the indexed `geoField`.\n * Same caveat as FTS — unindexed geo searches return no rows\n * server-side and the firegraph layer cannot pre-detect that.\n */\n geoSearch(params: GeoSearchParams): Promise<StoredGraphRecord[]> {\n return runFirestoreGeoSearch(this.db, this.collectionPath, params);\n }\n\n // --- Server-side multi-source fan-out (capability: query.join) ---\n\n /**\n * Fan out from `params.sources` over a single edge type in one server-\n * side round trip when running in pipeline mode, or via a chunked\n * classic-API fan-out when running in classic mode.\n *\n * Pipeline mode (default outside the emulator): one call to\n * `db.pipeline().collection(path).where(equalAny(sourceField, sources))\n * .execute()`. `equalAny` has no documented cap, so a 1k-source fan-\n * out is one round trip.\n *\n * Classic mode (emulator, or `defaultQueryMode: 'classic'`): chunks\n * `params.sources` into 30-element groups (the classic `'in'`\n * operator's documented cap), dispatches one query per chunk in\n * parallel, concats the results, and applies a cross-chunk re-sort +\n * total-limit slice. Same observable contract, `ceil(N/30)` round\n * trips. The chunked path is still a win over the per-source\n * `findEdges` loop in `traverse.ts` — 100 sources go from 100 round\n * trips to 4.\n */\n expand(params: ExpandParams): Promise<ExpandResult> {\n if (this.queryMode === 'pipeline') {\n return runFirestorePipelineExpand(this.db, this.collectionPath, params);\n }\n return runFirestoreClassicExpand(this.adapter, params);\n }\n\n // --- Server-side DML (capability: query.dml, gated by previewDml) ---\n\n /**\n * Single-statement bulk DELETE via Pipeline `delete()` stage. Wired only\n * when the backend was created with `previewDml: true`; otherwise the\n * cap isn't declared and `client.bulkDelete()` throws\n * `UNSUPPORTED_OPERATION` (or `bulk.ts` cascade falls back to\n * `bulkRemoveEdges`).\n *\n * Empty filter lists are rejected at the helper boundary —\n * see `runFirestorePipelineDelete`'s `assertNonEmptyFilters`.\n *\n * Subgraph isolation comes from `this.collectionPath`: the pipeline's\n * `collection(path)` source IS the subgraph, so there's no separate\n * `scope` predicate to enforce (unlike SQLite's leading-`scope`-`?`\n * filter).\n */\n bulkDelete(filters: QueryFilter[], options?: BulkOptions): Promise<BulkResult> {\n return runFirestorePipelineDelete(this.db, this.collectionPath, filters, options);\n }\n\n /**\n * Single-statement bulk UPDATE via Pipeline\n * `update(transformedFields)` stage. Same gating and scoping as\n * `bulkDelete` above. The patch is flattened into one\n * `AliasedExpression` per terminal leaf via the shared `flattenPatch`\n * pipeline; `deleteField()` sentinels are rejected at the helper\n * boundary (the typed `update(AliasedExpression[])` surface in 8.5.0\n * has no field-deletion transform — see `runFirestorePipelineUpdate`).\n */\n bulkUpdate(\n filters: QueryFilter[],\n patch: BulkUpdatePatch,\n options?: BulkOptions,\n ): Promise<BulkResult> {\n return runFirestorePipelineUpdate(this.db, this.collectionPath, filters, patch, options);\n }\n\n // --- Engine-level multi-hop traversal (capability: traversal.serverSide) ---\n\n /**\n * Compile a multi-hop traversal spec into one nested Pipeline and\n * dispatch a single round trip via `define` + `addFields(child\n * .toArrayExpression().as(...))`. The compiler in\n * `firestore-traverse-compiler.ts` validates spec eligibility (depth\n * ≤ `MAX_PIPELINE_DEPTH`, every hop has `limitPerSource`, response-\n * size product ≤ `maxReads`) and the executor in\n * `firestore-traverse.ts` builds + decodes the tree.\n *\n * Unlike `bulkDelete` / `bulkUpdate`, this method is GA-typed in 8.5.0\n * (no `@beta` annotation on `define`, `addFields`, `toArrayExpression`,\n * or `variable`), so it does NOT need a `previewDml`-style opt-in.\n *\n * `defaultQueryMode` is irrelevant here — engine traversal always\n * dispatches through Pipelines because the join-key binding (`define`\n * + `variable`) has no classic Query API equivalent. Specs that\n * arrive on a classic-mode backend still execute via Pipelines; the\n * `queryMode` toggle only affects the read query path\n * (`query()` / `expand()`).\n */\n runEngineTraversal(params: EngineTraversalParams): Promise<EngineTraversalResult> {\n return runFirestoreEngineTraversal(this.db, this.collectionPath, params);\n }\n}\n\n/**\n * Create a Firestore Enterprise-edition `StorageBackend`.\n *\n * Pipeline mode is the default. When `FIRESTORE_EMULATOR_HOST` is set the\n * effective mode is forced to `'classic'` because the emulator does not\n * execute pipelines; if the caller explicitly asked for pipeline mode in\n * that environment, a one-time `console.warn` surfaces the override so\n * the deployment mismatch is visible without breaking the test run.\n */\nexport function createFirestoreEnterpriseBackend(\n db: Firestore,\n collectionPath: string,\n options: FirestoreEnterpriseOptions = {},\n): StorageBackend<FirestoreEnterpriseCapability> {\n const requestedMode: FirestoreEnterpriseQueryMode = options.defaultQueryMode ?? 'pipeline';\n const isEmulator = !!process.env.FIRESTORE_EMULATOR_HOST;\n const effectiveMode: FirestoreEnterpriseQueryMode =\n isEmulator && requestedMode === 'pipeline' ? 'classic' : requestedMode;\n\n if (\n isEmulator &&\n requestedMode === 'pipeline' &&\n effectiveMode === 'classic' &&\n !_emulatorFallbackWarned\n ) {\n _emulatorFallbackWarned = true;\n console.warn(\n '[firegraph] Firestore Enterprise pipeline mode is unavailable in the emulator; ' +\n 'falling back to classic Query API for this run. Set ' +\n \"`defaultQueryMode: 'classic'` to silence this warning.\",\n );\n }\n\n if (!isEmulator && requestedMode === 'classic' && !_classicInProductionWarned) {\n _classicInProductionWarned = true;\n console.warn(\n \"[firegraph] Firestore Enterprise backend created with `defaultQueryMode: 'classic'`. \" +\n 'Classic-mode `query()` against Enterprise causes full collection scans for ' +\n '`data.*` filters (high billing). For production reads on Standard Firestore, ' +\n \"import from `'firegraph/firestore-standard'` instead.\",\n );\n }\n\n const previewDml = options.previewDml ?? false;\n if (previewDml && !_previewDmlWarned) {\n _previewDmlWarned = true;\n console.warn(\n '[firegraph] Firestore Enterprise backend created with `previewDml: true`. ' +\n 'bulkDelete()/bulkUpdate() will dispatch through Pipeline.delete() / ' +\n 'Pipeline.update(transformedFields), both `@beta` in @google-cloud/firestore@8.5.0. ' +\n 'The typed surface may shift before GA — pin your firestore SDK or be ready to ' +\n 'set `previewDml: false` and route through the read-then-write fallback if needed.',\n );\n }\n\n const scopePath = options.scopePath ?? '';\n return new FirestoreEnterpriseBackendImpl(\n db,\n collectionPath,\n effectiveMode,\n scopePath,\n previewDml,\n );\n}\n","/**\n * Pipelines-based DML for Firestore Enterprise (`query.dml`, Phase 13b).\n *\n * Translates `bulkDelete(filters)` / `bulkUpdate(filters, patch)` into a\n * single server-side pipeline:\n *\n * db.pipeline().collection(path).where(<filters>).delete().execute()\n * db.pipeline().collection(path).where(<filters>).update([…transforms]).execute()\n *\n * Both stages are `@beta` in `@google-cloud/firestore@8.5.0`\n * (`Pipeline.delete()` at `firestore.d.ts:12647`, `Pipeline.update(transformedFields)`\n * at `firestore.d.ts:12662`). The Enterprise backend gates this whole helper\n * behind an opt-in `previewDml: true` flag and emits a one-time `console.warn`\n * on first call — see `firestore-enterprise/backend.ts`.\n *\n * Why a separate helper rather than reusing `pipeline-adapter.ts`: the adapter\n * is read-only by design (it returns `StoredGraphRecord[]`). DML stages\n * mutate, return an affected-row count, and obey different empty-filter\n * defaults (an empty `where` would delete the whole collection — we reject\n * that at the boundary as defense-in-depth, mirroring `DORPCBackend`'s\n * empty-filter rejection at the wire).\n *\n * Result decoding: `Pipeline.delete()` and `Pipeline.update(...)` return a\n * `PipelineSnapshot` whose `results` array contains one entry per affected\n * document (the documents the stage acted on). We use `results.length` as\n * the affected-row count, matching `BulkResult.deleted` / parity with the\n * SQLite RETURNING-driven count. If the SDK changes the result shape when\n * the stages graduate from `@beta`, this is the only call site to update.\n */\n\nimport type { Firestore, Pipelines, Timestamp } from '@google-cloud/firestore';\n\nimport { FiregraphError } from '../errors.js';\nimport type { BulkOptions, BulkResult, BulkUpdatePatch, QueryFilter } from '../types.js';\nimport { flattenPatch } from './write-plan.js';\n\n/**\n * Lazily loaded Pipelines module + Timestamp class. Same dynamic-import\n * pattern as `pipeline-adapter.ts`, `firestore-fulltext.ts`, and\n * `firestore-expand.ts` — keeps the `@google-cloud/firestore` Pipelines\n * code out of the load graph for callers that never opt into preview DML.\n */\nlet _Pipelines: typeof Pipelines | null = null;\nlet _Timestamp: typeof Timestamp | null = null;\n\nasync function getFirestoreSurface(): Promise<{\n P: typeof Pipelines;\n Ts: typeof Timestamp;\n}> {\n if (!_Pipelines || !_Timestamp) {\n const mod = await import('@google-cloud/firestore');\n _Pipelines = mod.Pipelines;\n _Timestamp = mod.Timestamp;\n }\n return { P: _Pipelines, Ts: _Timestamp };\n}\n\n/**\n * Build the Pipelines `BooleanExpression` for one `QueryFilter`. Mirrors\n * `pipeline-adapter.ts`'s `buildFilterExpression` exactly — kept as a\n * private helper here so this module doesn't reach into the Enterprise\n * package's adapter module (helpers under `internal/` should not depend\n * on per-edition modules).\n */\nfunction buildFilterExpression(\n P: typeof Pipelines,\n filter: QueryFilter,\n): Pipelines.BooleanExpression {\n const { field: fieldName, op, value } = filter;\n switch (op) {\n case '==':\n return P.equal(fieldName, value);\n case '!=':\n return P.notEqual(fieldName, value);\n case '<':\n return P.lessThan(fieldName, value);\n case '<=':\n return P.lessThanOrEqual(fieldName, value);\n case '>':\n return P.greaterThan(fieldName, value);\n case '>=':\n return P.greaterThanOrEqual(fieldName, value);\n case 'in':\n return P.equalAny(fieldName, value as Array<unknown>);\n case 'not-in':\n return P.notEqualAny(fieldName, value as Array<unknown>);\n case 'array-contains':\n return P.arrayContains(fieldName, value);\n case 'array-contains-any':\n return P.arrayContainsAny(fieldName, value as Array<unknown>);\n default:\n throw new FiregraphError(\n `bulkDelete/bulkUpdate: unsupported filter op \"${op}\" for pipeline DML.`,\n 'INVALID_QUERY',\n );\n }\n}\n\n/**\n * Compose `where(and(... filters))`, normalising the 0/1/N-filter cases.\n * Empty `filters` is rejected by the caller; this helper assumes ≥ 1.\n */\nfunction applyWhere(\n P: typeof Pipelines,\n pipeline: Pipelines.Pipeline,\n filters: readonly QueryFilter[],\n): Pipelines.Pipeline {\n const exprs = filters.map((f) => buildFilterExpression(P, f));\n if (exprs.length === 1) {\n return pipeline.where(exprs[0]);\n }\n const [first, second, ...rest] = exprs;\n return pipeline.where(P.and(first, second, ...rest));\n}\n\n/**\n * Reject empty filter lists at the helper boundary. An unscoped\n * `pipeline().collection(path).delete()` would erase the whole graph\n * (or the whole subgraph), so we make the caller name a predicate.\n * `bulkDelete` / `bulkUpdate` callers always have at least the\n * scope-bounding filter (subgraph isolation is enforced by the parent\n * collection path, not by an explicit `scope` predicate the way SQLite\n * does it — Firestore's collection path IS the scope), but the public\n * `client.bulkDelete()` permits a no-filter call which should not\n * compose into a wholesale erase here.\n */\nfunction assertNonEmptyFilters(filters: readonly QueryFilter[], op: 'delete' | 'update'): void {\n if (filters.length === 0) {\n throw new FiregraphError(\n `bulk${op === 'delete' ? 'Delete' : 'Update'}() on Firestore requires at least one filter; ` +\n `an empty filter list would target the whole collection. To wipe a subgraph, use ` +\n `removeNodeCascade() on the parent node instead.`,\n 'INVALID_QUERY',\n );\n }\n}\n\n/**\n * Server-side bulk DELETE.\n *\n * Pipeline shape:\n *\n * db.pipeline().collection(path).where(...).delete().execute()\n *\n * Decode: `snap.results.length` ⇒ `BulkResult.deleted`. `batches: 1`\n * because Pipeline DML is single-statement; `errors: []` on success\n * (a thrown error propagates up — there is no per-batch retry surface\n * here, mirroring the SQLite `bulkDelete` which also throws rather than\n * returning a partial result).\n */\nexport async function runFirestorePipelineDelete(\n db: Firestore,\n collectionPath: string,\n filters: QueryFilter[],\n _options?: BulkOptions,\n): Promise<BulkResult> {\n assertNonEmptyFilters(filters, 'delete');\n const { P } = await getFirestoreSurface();\n let pipeline = db.pipeline().collection(collectionPath);\n pipeline = applyWhere(P, pipeline, filters);\n const snap = await pipeline.delete().execute();\n // @google-cloud/firestore@8.5.0 @beta: results contains one entry per\n // affected document. If this changes on graduation, this is the call site\n // to update (see module docstring).\n return {\n deleted: snap.results.length,\n batches: 1,\n errors: [],\n };\n}\n\n/**\n * Server-side bulk UPDATE.\n *\n * Translation:\n *\n * 1. `flattenPatch(patch.data)` → `DataPathOp[]`. Each terminal leaf\n * becomes one transform on the row. Path validation lives in\n * `flattenPatch` / `assertSafePath`.\n * 2. `delete: true` ops are rejected with `INVALID_QUERY`. The typed\n * `Pipeline.update(AliasedExpression[])` surface in 8.5.0 has no\n * sentinel for \"remove this field\"; emulating one would require a\n * read-modify-write loop, which defeats the single-statement DML\n * goal. Use the regular `replaceEdge` / `replaceNode` path or\n * `bulkRemoveEdges` for delete-leaning patches.\n * 3. Each set op becomes `constant(value).as('data.<dotted.path>')`.\n * The dotted alias relies on Firestore's standard nested-update\n * semantics; integration tests against real Enterprise pin this.\n * 4. `updatedAt` is stamped with `constant(Timestamp.now())`. This is\n * a client-side timestamp (Pipeline `update` doesn't accept a\n * `FieldValue.serverTimestamp()` sentinel — it only takes typed\n * `AliasedExpression`s), matching SQLite's `compileBulkUpdate(..., Date.now())`.\n *\n * Decode: same shape as delete — `snap.results.length` is the affected\n * row count surfaced as `BulkResult.deleted` (the field's name is a\n * legacy from cascade-delete; for an update, \"deleted\" is the\n * affected-row count by convention — same as SQLite `bulkUpdate`).\n */\nexport async function runFirestorePipelineUpdate(\n db: Firestore,\n collectionPath: string,\n filters: QueryFilter[],\n patch: BulkUpdatePatch,\n _options?: BulkOptions,\n): Promise<BulkResult> {\n assertNonEmptyFilters(filters, 'update');\n\n const ops = flattenPatch(patch.data);\n if (ops.length === 0) {\n throw new FiregraphError(\n 'bulkUpdate(): patch.data produced no field updates. An empty patch ' +\n 'would only stamp updatedAt, which is almost certainly a bug.',\n 'INVALID_QUERY',\n );\n }\n for (const op of ops) {\n if (op.delete) {\n throw new FiregraphError(\n `bulkUpdate(): preview Pipeline DML does not support deleteField() sentinels ` +\n `(no typed delete-transform on @google-cloud/firestore@8.5.0's ` +\n `Pipeline.update(AliasedExpression[])). Drop the delete from the patch ` +\n `or use replaceEdge/replaceNode for the affected rows.`,\n 'INVALID_QUERY',\n );\n }\n }\n\n const { P, Ts } = await getFirestoreSurface();\n const transforms: Pipelines.AliasedExpression[] = ops.map((op) => {\n const alias = `data.${op.path.join('.')}`;\n // `constant(value)` in the Pipelines surface accepts a fixed set of\n // primitive / Firestore-special types; we cast through `unknown` because\n // patch values are user-defined and the typed overloads don't expose a\n // catch-all. If the value type isn't supported by `constant(...)`, the\n // SDK rejects at execute time with a clear error.\n return P.constant(op.value as never).as(alias);\n });\n // Stamp updatedAt last so the SDK's `update` stage sees a single coherent\n // transform list. Matches the order in `buildFirestoreUpdate`.\n transforms.push(P.constant(Ts.now()).as('updatedAt'));\n\n let pipeline = db.pipeline().collection(collectionPath);\n pipeline = applyWhere(P, pipeline, filters);\n const snap = await pipeline.update(transforms).execute();\n // @google-cloud/firestore@8.5.0 @beta: results contains one entry per\n // affected document. If this changes on graduation, this is the call site\n // to update (see module docstring).\n return {\n deleted: snap.results.length,\n batches: 1,\n errors: [],\n };\n}\n","/**\n * Shared Pipelines-API multi-source fan-out for Firestore Enterprise.\n *\n * Translates an `expand({ sources, axbType, ... })` call into a single\n * `db.pipeline().collection(path).where(equalAny(sourceField, sources))\n * .sort(...).limit(...).execute()` pipeline and decodes the result.\n * Used only by `firestore-enterprise` when `queryMode === 'pipeline'`.\n * The classic-mode (and Standard) path uses the chunked-`'in'` helper in\n * `firestore-classic-expand.ts`.\n *\n * Why this matters: the classic Query API caps `'in'` operators at 30\n * elements, forcing a `ceil(N/30)` round-trip fan-out per call. Pipeline\n * `equalAny(field, values)` is one server-side stage with no documented\n * cap on the value list — `Pipelines` accepts an arbitrary array. With\n * 1,000 sources, classic does ~34 round trips; pipelines do one. This is\n * the engine-level collapse that makes single-hop fan-out tractable on\n * Enterprise without changing the public `expand()` contract.\n *\n * Hydration follows the same one-pipeline shape: a second pipeline that\n * fetches every node row whose `aUid` is in the deduped target set\n * (nodes are stored as self-loops `(uid, 'is', uid)`, so `aUid` and\n * `bUid` both equal the node UID by construction).\n *\n * Self-loop guard: when `params.axbType === NODE_RELATION`, edges where\n * `aUid === bUid` are nodes, not real hops. We mirror the\n * `firestore-classic-expand.ts` post-process filter rather than try to\n * express `aUid != bUid` as a typed pipeline expression — the typed\n * surface in 8.5.0 doesn't expose column-vs-column predicates, so a\n * post-pass is the clean path. The guard is defensive; `traverse.ts`\n * never sends `NODE_RELATION` as `axbType`.\n */\n\nimport type { Firestore, Pipelines } from '@google-cloud/firestore';\n\nimport { FiregraphError } from '../errors.js';\nimport type { ExpandParams, ExpandResult, StoredGraphRecord } from '../types.js';\nimport { NODE_RELATION } from './constants.js';\n\n/**\n * Lazily loaded Pipelines module. Same lazy-import pattern as\n * `pipeline-adapter.ts` and `firestore-fulltext.ts`: avoids pulling\n * pipeline-related code at module load for callers that never invoke\n * `expand()` on Enterprise.\n */\nlet _Pipelines: typeof Pipelines | null = null;\n\nasync function getPipelines(): Promise<typeof Pipelines> {\n if (!_Pipelines) {\n const mod = await import('@google-cloud/firestore');\n _Pipelines = mod.Pipelines;\n }\n return _Pipelines;\n}\n\n/**\n * Run a Pipelines-API `expand()` against a Firestore collection.\n * Returns the same `ExpandResult` shape as the SQL backends and the\n * classic-mode helper.\n *\n * Empty `params.sources` short-circuits without touching the SDK — the\n * pipeline would emit an empty `equalAny([])` which the SDK accepts but\n * the early return makes the contract explicit and matches the\n * `client.expand` wrapper.\n */\nexport async function runFirestorePipelineExpand(\n db: Firestore,\n collectionPath: string,\n params: ExpandParams,\n): Promise<ExpandResult> {\n if (params.sources.length === 0) {\n return params.hydrate ? { edges: [], targets: [] } : { edges: [] };\n }\n\n if (params.axbType.length === 0) {\n throw new FiregraphError('expand(): axbType must be a non-empty string.', 'INVALID_QUERY');\n }\n\n const direction = params.direction ?? 'forward';\n const sourceField = direction === 'forward' ? 'aUid' : 'bUid';\n\n const P = await getPipelines();\n\n // Build the AND predicate. `equalAny(field, values)` is the typed\n // pipeline equivalent of classic's `'in'` operator, but without the\n // 30-element cap.\n const exprs: Pipelines.BooleanExpression[] = [\n P.equal('axbType', params.axbType),\n P.equalAny(sourceField, params.sources as Array<unknown>),\n ];\n if (params.aType !== undefined) exprs.push(P.equal('aType', params.aType));\n if (params.bType !== undefined) exprs.push(P.equal('bType', params.bType));\n\n let pipeline = db.pipeline().collection(collectionPath);\n if (exprs.length === 1) {\n pipeline = pipeline.where(exprs[0]);\n } else {\n const [first, second, ...rest] = exprs;\n pipeline = pipeline.where(P.and(first, second, ...rest));\n }\n\n if (params.orderBy) {\n const f = P.field(params.orderBy.field);\n const ordering = params.orderBy.direction === 'desc' ? f.descending() : f.ascending();\n pipeline = pipeline.sort(ordering);\n }\n\n // Apply the global limit server-side. Unlike the chunked classic path\n // we don't need a per-chunk soft cap — there's only one round trip.\n // `limitPerSource * sources.length` is the documented contract for\n // the cross-chunk total cap; mirror it here.\n const totalLimit =\n params.limitPerSource !== undefined ? params.sources.length * params.limitPerSource : undefined;\n if (totalLimit !== undefined) {\n pipeline = pipeline.limit(totalLimit);\n }\n\n const snap = await pipeline.execute();\n let edges: StoredGraphRecord[] = snap.results.map((r) => r.data() as StoredGraphRecord);\n\n // Self-loop filter when the caller targeted the node-relation. See JSDoc.\n if (params.axbType === NODE_RELATION) {\n edges = edges.filter((e) => e.aUid !== e.bUid);\n }\n\n if (!params.hydrate) return { edges };\n\n // Hydration: one pipeline that fetches every target node by\n // `axbType == NODE_RELATION AND aUid equalAny <targets>`. Nodes are\n // self-loops so this picks up exactly one row per UID.\n const targetUids = edges.map((e) => (direction === 'forward' ? e.bUid : e.aUid));\n const uniqueTargets = [...new Set(targetUids)];\n if (uniqueTargets.length === 0) {\n return { edges, targets: [] };\n }\n\n const hydratePipeline = db\n .pipeline()\n .collection(collectionPath)\n .where(\n P.and(P.equal('axbType', NODE_RELATION), P.equalAny('aUid', uniqueTargets as Array<unknown>)),\n );\n\n const hydrateSnap = await hydratePipeline.execute();\n const byUid = new Map<string, StoredGraphRecord>();\n for (const r of hydrateSnap.results) {\n const row = r.data() as StoredGraphRecord;\n // `bUid === aUid === uid` for node rows by construction.\n byUid.set(row.bUid, row);\n }\n const targets = targetUids.map((uid) => byUid.get(uid) ?? null);\n return { edges, targets };\n}\n","/**\n * Shared Pipelines-API full-text search translation for Firestore Enterprise.\n *\n * Translates a `fullTextSearch({ ... })` call into a\n * `db.pipeline().collection(path).search({ query: documentMatches(...), addFields: [score().as('_score')] }).where(...).limit(N).execute()`\n * pipeline and decodes the result. Only the Enterprise backend wires this\n * helper today — Firestore Standard does not support the FTS index at all\n * (an Enterprise-only product feature), and the SQLite-shaped backends\n * have no native FTS index.\n *\n * Why pipelines (not classic): Firestore's classic Query API has no FTS\n * primitive. The 8.5.0 SDK exposes typed `documentMatches` /\n * `geoDistance` / `score` functions plus the `Pipeline.search(...)`\n * stage; we use them directly and avoid the `rawStage(...)` escape\n * hatch.\n *\n * The `search` stage **must be the first stage** of a pipeline (per the\n * `@beta` SDK docstring at `Pipeline.search`). Identifying filters\n * (`aType` / `axbType` / `bType`) therefore go into a follow-up\n * `where(...)` stage, not into the `search.query` expression — composing\n * with `documentMatches` via `and(...)` is unsupported on the search\n * query path. The trade-off: identifying filters narrow *after* the\n * index walk rather than constraining its scope. For per-edge-type\n * search, callers should rely on Firestore's collection-scoped indexes\n * (one FTS index per `aType` collection); the post-search `where` is a\n * safety net, not a primary scope mechanism.\n *\n * Migrations are not applied to the result. The contract on\n * `StorageBackend.fullTextSearch` documents the rationale: the FTS\n * index walked the raw stored shape, and rehydrating through the\n * migration pipeline would change the candidate set the index already\n * scored.\n */\n\nimport type { Firestore, Pipelines } from '@google-cloud/firestore';\n\nimport { FiregraphError } from '../errors.js';\nimport type { FullTextSearchParams, StoredGraphRecord } from '../types.js';\n\n/**\n * Built-in envelope fields that must NOT appear in `fields`. Search\n * targets live inside `data`; the envelope is reserved for firegraph\n * metadata and is not text-indexable. Mirrors the projection /\n * vector-field rejection list.\n */\nconst ENVELOPE_FIELDS: ReadonlySet<string> = new Set([\n 'aType',\n 'aUid',\n 'axbType',\n 'bType',\n 'bUid',\n 'createdAt',\n 'updatedAt',\n 'v',\n]);\n\n/**\n * Normalise a caller-supplied search-target field path. Bare names\n * rewrite to `data.<name>`; `'data'` and `'data.*'` pass through;\n * envelope fields are rejected.\n */\nexport function normalizeFullTextFieldPath(field: string): string {\n if (ENVELOPE_FIELDS.has(field)) {\n throw new FiregraphError(\n `fullTextSearch(): field '${field}' is a built-in envelope field — ` +\n `text-indexed fields must live under \\`data.*\\`. Use a path like ` +\n `'data.${field}' if you really meant a nested data field.`,\n 'INVALID_QUERY',\n );\n }\n if (field === 'data' || field.startsWith('data.')) return field;\n return `data.${field}`;\n}\n\n/**\n * Lazily loaded Pipelines module. Same lazy-import pattern as\n * `pipeline-adapter.ts`: avoids pulling pipeline-related code at module\n * load for callers that never invoke FTS.\n */\nlet _Pipelines: typeof Pipelines | null = null;\n\nasync function getPipelines(): Promise<typeof Pipelines> {\n if (!_Pipelines) {\n const mod = await import('@google-cloud/firestore');\n _Pipelines = mod.Pipelines;\n }\n return _Pipelines;\n}\n\n/**\n * Run a full-text search against a collection path. Returns the\n * matching records as `StoredGraphRecord[]`, ordered by relevance\n * (the search index's natural score order — highest-first).\n *\n * Validation surface:\n *\n * - `query` must be a non-empty string.\n * - Each entry in `fields` (if set) must not be an envelope field\n * and is rewritten to `data.<name>` for bare names.\n * - `limit` must be a positive integer.\n *\n * Note: scan-protection lives in the client wrapper (same as\n * `findNearest`). Backends never see whether the caller opted in via\n * `allowCollectionScan` — that flag is consumed before dispatch.\n */\nexport async function runFirestoreFullTextSearch(\n db: Firestore,\n collectionPath: string,\n params: FullTextSearchParams,\n): Promise<StoredGraphRecord[]> {\n if (typeof params.query !== 'string' || params.query.length === 0) {\n throw new FiregraphError(\n 'fullTextSearch(): query must be a non-empty string.',\n 'INVALID_QUERY',\n );\n }\n if (!Number.isInteger(params.limit) || params.limit <= 0) {\n throw new FiregraphError(\n `fullTextSearch(): limit must be a positive integer (got ${params.limit}).`,\n 'INVALID_QUERY',\n );\n }\n\n // Normalise field paths up front so any envelope-field misuse fails\n // before we hit the SDK.\n const normalizedFields = params.fields?.map((f) => normalizeFullTextFieldPath(f));\n\n const P = await getPipelines();\n\n // Per-field text predicates (`matches(field, query)`) are not yet typed\n // in @google-cloud/firestore@8.5.0. Reject `fields` now so callers get a\n // clear error instead of silently receiving full-document search results.\n // When the SDK exposes the typed predicate, replace this guard with\n // `and(matches(f1, query), matches(f2, query), ...)` using normalizedFields.\n if (normalizedFields !== undefined && normalizedFields.length > 0) {\n throw new FiregraphError(\n 'fullTextSearch(): the `fields` option is not yet supported — ' +\n 'per-field text predicates are not available in @google-cloud/firestore@8.5.0. ' +\n 'Omit `fields` to search all indexed fields.',\n 'INVALID_QUERY',\n );\n }\n\n const searchQuery = P.documentMatches(params.query);\n\n // Build the search stage. Sort by relevance score descending — that's\n // the standard FTS contract.\n let pipeline = db.pipeline().collection(collectionPath).search({\n query: searchQuery,\n sort: P.score().descending(),\n });\n\n // Identifying filters land *after* `search()` because the search\n // stage must be the first stage of a pipeline. The post-search\n // `where` doesn't shrink the index walk — it's a safety net. For\n // efficient per-aType search, rely on Firestore's per-collection\n // FTS indexes.\n const whereExprs: Pipelines.BooleanExpression[] = [];\n if (params.aType) whereExprs.push(P.equal('aType', params.aType));\n if (params.axbType) whereExprs.push(P.equal('axbType', params.axbType));\n if (params.bType) whereExprs.push(P.equal('bType', params.bType));\n if (whereExprs.length === 1) {\n pipeline = pipeline.where(whereExprs[0]);\n } else if (whereExprs.length > 1) {\n const [first, second, ...rest] = whereExprs;\n pipeline = pipeline.where(P.and(first, second, ...rest));\n }\n\n pipeline = pipeline.limit(params.limit);\n\n const snap = await pipeline.execute();\n return snap.results.map((r) => r.data() as StoredGraphRecord);\n}\n","/**\n * Shared Pipelines-API geospatial distance translation for Firestore Enterprise.\n *\n * Translates a `geoSearch({ ... })` call into a\n * `db.pipeline().collection(path).search({ query: geoDistance(field, point).lessThanOrEqual(radius), sort: geoDistance(...).ascending() }).where(...).limit(N).execute()`\n * pipeline and decodes the result. Only the Enterprise backend wires\n * this helper today — Firestore Standard does not support the\n * geospatial index (an Enterprise-only product feature), and the\n * SQLite-shaped backends have no native geo index.\n *\n * Why pipelines (not classic): Firestore's classic Query API has no\n * geospatial primitive. The 8.5.0 SDK exposes typed `geoDistance(...)`\n * inside the `Pipeline.search(...)` stage; we use it directly and\n * avoid the `rawStage(...)` escape hatch.\n *\n * The `search` stage **must be the first stage** of a pipeline. So\n * identifying filters (`aType` / `axbType` / `bType`) go into a\n * follow-up `where(...)` stage rather than `search.query`. The radius\n * cap stays inside `search.query` (where the geo index can apply it\n * efficiently); the same `geoDistance(...)` expression also feeds\n * `search.sort` for nearest-first ordering when `orderByDistance` is\n * true / unset.\n *\n * Migrations are not applied to the result — same rationale as\n * `findNearest` / `fullTextSearch`.\n */\n\nimport type { Firestore, Pipelines } from '@google-cloud/firestore';\nimport { GeoPoint } from '@google-cloud/firestore';\n\nimport { FiregraphError } from '../errors.js';\nimport type { GeoSearchParams, StoredGraphRecord } from '../types.js';\n\n/**\n * Built-in envelope fields that must NOT be passed as `geoField`. Geo\n * indexes live inside `data`; the envelope is reserved for firegraph\n * metadata. Mirrors the projection / vector / FTS rejection list.\n */\nconst ENVELOPE_FIELDS: ReadonlySet<string> = new Set([\n 'aType',\n 'aUid',\n 'axbType',\n 'bType',\n 'bUid',\n 'createdAt',\n 'updatedAt',\n 'v',\n]);\n\n/**\n * Normalise a caller-supplied geo-field path. Bare names rewrite to\n * `data.<name>`; `'data'` and `'data.*'` pass through; envelope fields\n * are rejected.\n */\nexport function normalizeGeoFieldPath(field: string): string {\n if (ENVELOPE_FIELDS.has(field)) {\n throw new FiregraphError(\n `geoSearch(): geoField '${field}' is a built-in envelope field — ` +\n `geo-indexed fields must live under \\`data.*\\`. Use a path like ` +\n `'data.${field}' if you really meant a nested data field.`,\n 'INVALID_QUERY',\n );\n }\n if (field === 'data' || field.startsWith('data.')) return field;\n return `data.${field}`;\n}\n\n/** Lat/lng range check. Mirrors Firestore's GeoPoint constructor validation. */\nfunction assertValidGeoPoint(lat: number, lng: number): void {\n if (!Number.isFinite(lat) || lat < -90 || lat > 90) {\n throw new FiregraphError(\n `geoSearch(): point.lat must be in [-90, 90] (got ${lat}).`,\n 'INVALID_QUERY',\n );\n }\n if (!Number.isFinite(lng) || lng < -180 || lng > 180) {\n throw new FiregraphError(\n `geoSearch(): point.lng must be in [-180, 180] (got ${lng}).`,\n 'INVALID_QUERY',\n );\n }\n}\n\nlet _Pipelines: typeof Pipelines | null = null;\n\nasync function getPipelines(): Promise<typeof Pipelines> {\n if (!_Pipelines) {\n const mod = await import('@google-cloud/firestore');\n _Pipelines = mod.Pipelines;\n }\n return _Pipelines;\n}\n\n/**\n * Run a geospatial distance search against a collection path. Returns\n * rows whose `geoField` is within `radiusMeters` of `point`, ordered\n * nearest-first by default.\n *\n * Validation surface:\n *\n * - `geoField` must not be an envelope field; bare names are rewritten\n * to `data.<name>`.\n * - `point.lat` must be in `[-90, 90]`, `point.lng` in `[-180, 180]`.\n * - `radiusMeters` must be a positive finite number.\n * - `limit` must be a positive integer.\n *\n * Scan-protection lives in the client wrapper (same as `findNearest`\n * and `fullTextSearch`).\n */\nexport async function runFirestoreGeoSearch(\n db: Firestore,\n collectionPath: string,\n params: GeoSearchParams,\n): Promise<StoredGraphRecord[]> {\n if (!Number.isFinite(params.radiusMeters) || params.radiusMeters <= 0) {\n throw new FiregraphError(\n `geoSearch(): radiusMeters must be a positive finite number (got ${params.radiusMeters}).`,\n 'INVALID_QUERY',\n );\n }\n if (!Number.isInteger(params.limit) || params.limit <= 0) {\n throw new FiregraphError(\n `geoSearch(): limit must be a positive integer (got ${params.limit}).`,\n 'INVALID_QUERY',\n );\n }\n assertValidGeoPoint(params.point.lat, params.point.lng);\n\n const geoFieldPath = normalizeGeoFieldPath(params.geoField);\n const center = new GeoPoint(params.point.lat, params.point.lng);\n\n const P = await getPipelines();\n\n // The geoDistance expression appears in two places: the search query\n // (as `<= radius`) and the search sort (as `.ascending()`). Build it\n // once and reuse — the SDK will treat the two calls as equivalent\n // server-side, but it's clearer to mirror the docstring shape.\n const distanceQuery = P.geoDistance(geoFieldPath, center).lessThanOrEqual(params.radiusMeters);\n\n // Build the search stage. `orderByDistance` defaults to true — that's\n // the contract on `GeoSearchParams`. The same `geoDistance(...)`\n // expression feeds the radius filter (`<= radius`) and the\n // ascending-distance sort.\n const orderByDistance = params.orderByDistance !== false;\n const opts: { query: Pipelines.BooleanExpression; sort?: Pipelines.Ordering } = {\n query: distanceQuery,\n };\n if (orderByDistance) {\n opts.sort = P.geoDistance(geoFieldPath, center).ascending();\n }\n\n let pipeline = db.pipeline().collection(collectionPath).search(opts);\n\n // Identifying filters land *after* `search()` (same constraint as FTS).\n const whereExprs: Pipelines.BooleanExpression[] = [];\n if (params.aType) whereExprs.push(P.equal('aType', params.aType));\n if (params.axbType) whereExprs.push(P.equal('axbType', params.axbType));\n if (params.bType) whereExprs.push(P.equal('bType', params.bType));\n if (whereExprs.length === 1) {\n pipeline = pipeline.where(whereExprs[0]);\n } else if (whereExprs.length > 1) {\n const [first, second, ...rest] = whereExprs;\n pipeline = pipeline.where(P.and(first, second, ...rest));\n }\n\n pipeline = pipeline.limit(params.limit);\n\n const snap = await pipeline.execute();\n return snap.results.map((r) => r.data() as StoredGraphRecord);\n}\n","/**\n * Engine-level multi-hop traversal executor for Firestore Enterprise.\n *\n * Compiles an `EngineTraversalParams` spec into one nested Pipeline\n * (`define` + `addFields(child.toArrayExpression().as(...))`) and\n * dispatches a single round trip. The single-call collapse is the\n * payoff that motivates the `traversal.serverSide` capability — a\n * 5-hop traversal with a 100-element fan-out at hop 1 stays at one\n * server-side call instead of fanning out 100 round trips per\n * subsequent hop.\n *\n * The executor is a thin shell over `compileEngineTraversal` (validation\n * lives in `firestore-traverse-compiler.ts`) and the typed Pipelines\n * surface in `@google-cloud/firestore@8.5.0`. Every primitive used\n * (`define`, `addFields`, `field`, `variable`, `equal`, `equalAny`,\n * `and`, `toArrayExpression`) is GA-typed in 8.5.0 — no `@beta`\n * annotation, so this capability does NOT need a `previewDml`-style\n * opt-in flag (unlike `query.dml` in Phase 13b).\n *\n * Pipeline shape (forward 2-hop):\n *\n * ```\n * db.pipeline().collection(graph)\n * .where(and(equal('axbType', 'e1'), equalAny('aUid', sources)))\n * .define(field('bUid').as('hop_0_join'))\n * .addFields(\n * db.pipeline().collection(graph)\n * .where(and(equal('axbType', 'e2'), equal('aUid', variable('hop_0_join'))))\n * .toArrayExpression()\n * .as('hop_0_children'))\n * .execute();\n * ```\n *\n * Each top-level result row is a hop-0 edge augmented with a `hop_0_children`\n * field — an array of hop-1 edges. For depth N+1 the executor wraps\n * the inner pipeline in another `define` + `addFields` layer before\n * `toArrayExpression()`, with each hop's sub-pipeline nested inside\n * the previous hop's `addFields(...)`.\n *\n * The reverse-direction mode swaps the join key — it uses\n * `equalAny('bUid', sources)` at the root and `field('aUid').as(...)`\n * for the variable bound at each depth. Mixed directions across hops\n * are honoured per-hop.\n *\n * Result decoding flattens the tree into per-depth `StoredGraphRecord[]`\n * arrays, deduping each depth on the target-side UID (`bUid` for\n * forward hops, `aUid` for reverse). The scaffolding fields\n * (`hop_0_children`, `hop_0_join`, `hop_1_children`, `hop_1_join`, …)\n * are stripped from each row before it lands in the returned `edges`\n * slot — they're scaffolding, not part of the edge\n * payload.\n *\n * NODE_RELATION self-loop guard mirrors `firestore-expand.ts`: if any\n * hop targets `axbType === 'is'`, post-pass-filter rows where\n * `aUid === bUid`. Defensive — `traverse.ts` never emits the\n * node-relation as a hop axbType — but matches the parity story for\n * `expand()`.\n */\n\nimport type { Firestore, Pipelines } from '@google-cloud/firestore';\n\nimport { FiregraphError } from '../errors.js';\nimport type { EngineTraversalParams, EngineTraversalResult, StoredGraphRecord } from '../types.js';\nimport { NODE_RELATION } from './constants.js';\nimport {\n compileEngineTraversal,\n type NormalizedEngineTraversal,\n} from './firestore-traverse-compiler.js';\n\n/**\n * Field name on each parent row holding the array of child-hop edges.\n * The depth index is appended (`hop_0_children`, `hop_1_children`, …)\n * so deeply-nested rows carry distinguishable scaffolding fields the\n * decoder can strip cleanly.\n */\nfunction childArrayKey(depth: number): string {\n return `hop_${depth}_children`;\n}\n\n/**\n * Variable name for the join key bound at a given depth via `define()`.\n * Forward hops bind `bUid`; reverse hops bind `aUid`. The depth index\n * appears in the variable name to prevent collisions when nested\n * pipelines need to refer to multiple ancestors.\n */\nfunction joinVarName(depth: number): string {\n return `hop_${depth}_join`;\n}\n\n/**\n * Lazily loaded Pipelines module. Same lazy-import pattern as\n * `firestore-expand.ts` and the FTS / geo helpers — avoids pulling\n * pipeline code into the module load for callers that never invoke\n * engine traversal.\n */\nlet _Pipelines: typeof Pipelines | null = null;\n\nasync function getPipelines(): Promise<typeof Pipelines> {\n if (!_Pipelines) {\n const mod = await import('@google-cloud/firestore');\n _Pipelines = mod.Pipelines;\n }\n return _Pipelines;\n}\n\n/**\n * Build a single `BooleanExpression` from the per-hop predicate set.\n * `aType` / `bType` filters are optional and only added when set.\n *\n * The first hop's source-side predicate uses `equalAny(field, sources)`\n * (multi-source fan-out); deeper hops use `equal(field, variable(...))`\n * to pin against the bound join key from the parent define stage.\n */\nfunction buildHopPredicates(\n P: typeof Pipelines,\n hop: NormalizedEngineTraversal['hops'][number],\n sourcePredicate: Pipelines.BooleanExpression,\n): Pipelines.BooleanExpression {\n const exprs: Pipelines.BooleanExpression[] = [P.equal('axbType', hop.axbType), sourcePredicate];\n if (hop.aType !== undefined) exprs.push(P.equal('aType', hop.aType));\n if (hop.bType !== undefined) exprs.push(P.equal('bType', hop.bType));\n\n const [first, second, ...rest] = exprs;\n return P.and(first, second, ...rest);\n}\n\n/**\n * Apply `orderBy` + `limit` to a hop's pipeline.\n *\n * `limitPerSource` translates to a per-pipeline `limit` on the\n * sub-pipeline (one nested pipeline per source row). For the root\n * pipeline it's `sources.length × limitPerSource` to mirror\n * `firestore-expand.ts`'s contract.\n */\nfunction applyHopOrderingAndLimit(\n P: typeof Pipelines,\n pipeline: Pipelines.Pipeline,\n hop: NormalizedEngineTraversal['hops'][number],\n totalLimit: number,\n): Pipelines.Pipeline {\n let p = pipeline;\n if (hop.orderBy) {\n const f = P.field(hop.orderBy.field);\n const ordering = hop.orderBy.direction === 'desc' ? f.descending() : f.ascending();\n p = p.sort(ordering);\n }\n p = p.limit(totalLimit);\n return p;\n}\n\n/**\n * Build the inner-most-to-outer-most chain of pipelines for hops at\n * depth ≥ 1. The deepest hop's pipeline has no children; each\n * shallower hop wraps the deeper-hop pipeline in\n * `addFields(child.toArrayExpression().as(...))`.\n *\n * The returned pipeline is bound to a variable at the parent depth,\n * so it does NOT itself include the source-side `equalAny`. Instead\n * the source-side predicate is `equal(field('aUid'), variable('hop_{parentDepth}_join'))`\n * (or `bUid` for reverse).\n */\nfunction buildSubPipeline(\n P: typeof Pipelines,\n db: Firestore,\n collectionPath: string,\n hops: NormalizedEngineTraversal['hops'],\n depth: number,\n): Pipelines.Pipeline {\n const hop = hops[depth];\n const parentDepth = depth - 1;\n const sourceField = hop.direction === 'forward' ? 'aUid' : 'bUid';\n\n const sourcePredicate: Pipelines.BooleanExpression = P.equal(\n sourceField,\n P.variable(joinVarName(parentDepth)),\n );\n const where = buildHopPredicates(P, hop, sourcePredicate);\n\n let pipeline = db.pipeline().collection(collectionPath).where(where);\n // limitPerSource × 1 (one source per nested pipeline invocation, since\n // we're already pinned to a single ancestor variable).\n pipeline = applyHopOrderingAndLimit(P, pipeline, hop, hop.limitPerSource);\n\n // If there's a deeper hop, bind this hop's join key and addFields the\n // child pipeline as an array expression.\n if (depth + 1 < hops.length) {\n const targetField = hop.direction === 'forward' ? 'bUid' : 'aUid';\n pipeline = pipeline.define(P.field(targetField).as(joinVarName(depth)));\n const child = buildSubPipeline(P, db, collectionPath, hops, depth + 1);\n pipeline = pipeline.addFields(child.toArrayExpression().as(childArrayKey(depth)));\n }\n\n return pipeline;\n}\n\n/**\n * Build the full nested pipeline for an engine-traversal call.\n *\n * The root pipeline does the multi-source fan-out via\n * `equalAny('aUid', sources)` (forward) or `equalAny('bUid', sources)`\n * (reverse). Subsequent hops are wrapped via `addFields` recursively.\n */\nfunction buildRootPipeline(\n P: typeof Pipelines,\n db: Firestore,\n collectionPath: string,\n spec: NormalizedEngineTraversal,\n): Pipelines.Pipeline {\n const hop0 = spec.hops[0];\n const sourceField = hop0.direction === 'forward' ? 'aUid' : 'bUid';\n const sourcePredicate: Pipelines.BooleanExpression = P.equalAny(\n sourceField,\n spec.sources as Array<unknown>,\n );\n const where = buildHopPredicates(P, hop0, sourcePredicate);\n\n let pipeline = db.pipeline().collection(collectionPath).where(where);\n pipeline = applyHopOrderingAndLimit(P, pipeline, hop0, spec.sources.length * hop0.limitPerSource);\n\n if (spec.hops.length > 1) {\n const targetField = hop0.direction === 'forward' ? 'bUid' : 'aUid';\n pipeline = pipeline.define(P.field(targetField).as(joinVarName(0)));\n const child = buildSubPipeline(P, db, collectionPath, spec.hops, 1);\n pipeline = pipeline.addFields(child.toArrayExpression().as(childArrayKey(0)));\n }\n\n return pipeline;\n}\n\n/**\n * Strip the engine-traversal scaffolding from one decoded row,\n * yielding a clean `StoredGraphRecord` whose shape matches what\n * `findEdges` and `expand` return.\n *\n * Two scaffolding keys are removed per depth:\n * - `hop_{depth}_children` — the `addFields(child.toArrayExpression())`\n * array used to nest the next-hop rows.\n * - `hop_{depth}_join` — the `define(field(...).as(...))` variable\n * bound so the child sub-pipeline can reference the parent join key.\n * Whether `define`'d variables appear in `data()` output is\n * SDK-behavior-dependent; we strip defensively.\n */\nfunction stripScaffolding(row: Record<string, unknown>, depth: number): StoredGraphRecord {\n const stripped: Record<string, unknown> = { ...row };\n delete stripped[childArrayKey(depth)];\n delete stripped[joinVarName(depth)];\n // Cast through `unknown` because `StoredGraphRecord` declares specific\n // required fields (`aType`, `aUid`, …) and TS won't narrow a generic\n // object with an index signature down to that nominal shape. The row\n // came from `pipeline.execute()` which uses `data()` on the result —\n // by construction it has the envelope fields the storage backend wrote.\n return stripped as unknown as StoredGraphRecord;\n}\n\n/**\n * Decode the nested-pipeline tree into per-depth `StoredGraphRecord[]`\n * arrays, deduped on the target-side UID.\n *\n * For each depth, the dedup key is `bUid` (forward) or `aUid`\n * (reverse). Ordering is preserved: the first occurrence of a UID\n * wins, matching `traverse.ts`'s existing dedup semantics.\n */\nfunction decodeTree(\n rootRows: Array<Record<string, unknown>>,\n hops: NormalizedEngineTraversal['hops'],\n): EngineTraversalResult {\n const out: EngineTraversalResult['hops'] = [];\n let frontier: Array<Record<string, unknown>> = rootRows;\n\n for (let depth = 0; depth < hops.length; depth++) {\n const hop = hops[depth];\n const targetField = hop.direction === 'forward' ? 'bUid' : 'aUid';\n\n const seen = new Set<string>();\n const edges: StoredGraphRecord[] = [];\n const nextFrontier: Array<Record<string, unknown>> = [];\n\n for (const row of frontier) {\n // Children for the next depth are stored under `hop_{depth}_children`\n // on this row. Pull them BEFORE we strip scaffolding from the row.\n const childKey = childArrayKey(depth);\n const children = row[childKey];\n if (Array.isArray(children)) {\n for (const child of children) {\n if (child && typeof child === 'object') {\n nextFrontier.push(child as Record<string, unknown>);\n }\n }\n }\n\n const stripped = stripScaffolding(row, depth);\n // Self-loop guard for the node-relation. `traverse.ts` never sends\n // it through engine traversal, but mirror `firestore-expand.ts`'s\n // post-pass for parity.\n if (hop.axbType === NODE_RELATION && stripped.aUid === stripped.bUid) continue;\n\n const dedupKey = (stripped as unknown as Record<string, unknown>)[targetField];\n if (typeof dedupKey !== 'string' || seen.has(dedupKey)) continue;\n seen.add(dedupKey);\n edges.push(stripped);\n }\n\n out.push({ edges, sourceCount: depth === 0 ? -1 : out[depth - 1].edges.length });\n frontier = nextFrontier;\n }\n\n // Patch sourceCount: depth 0's count is the input source count, which\n // we don't have in this scope — the caller fills it in below from\n // `spec.sources.length`. Leaving -1 here lets the caller override\n // without scanning the structure twice.\n return {\n hops: out,\n totalReads: 1,\n };\n}\n\n/**\n * Top-level entry point. Validates the spec, builds the nested\n * pipeline, dispatches one round trip, and decodes the result.\n *\n * Throws `FiregraphError('UNSUPPORTED_OPERATION')` when the compiler\n * rejects the spec — the traversal layer's `engineTraversal: 'auto'`\n * default never reaches this code path with an ineligible spec\n * (eligibility is pre-checked), but `'force'` mode does, and a thrown\n * error is the right signal for that path.\n */\nexport async function runFirestoreEngineTraversal(\n db: Firestore,\n collectionPath: string,\n params: EngineTraversalParams,\n): Promise<EngineTraversalResult> {\n // The Firestore emulator does not support the nested Pipeline primitives\n // (`define`, `addFields`, `toArrayExpression`, `variable`) — dispatching\n // would result in an unresolved promise and a test timeout instead of a\n // clear error. Throw immediately so `tryEngineTraversal`'s auto-mode\n // catch can fall back to the per-hop loop without hanging.\n if (process.env.FIRESTORE_EMULATOR_HOST) {\n throw new FiregraphError(\n 'engine traversal requires Pipelines — not supported on the Firestore emulator',\n 'UNSUPPORTED_OPERATION',\n );\n }\n\n const compiled = compileEngineTraversal(params);\n if (!compiled.eligible) {\n throw new FiregraphError(\n `engine traversal not eligible: ${compiled.reason}`,\n 'UNSUPPORTED_OPERATION',\n );\n }\n const spec = compiled.normalized;\n\n if (spec.sources.length === 0) {\n return {\n hops: spec.hops.map(() => ({ edges: [], sourceCount: 0 })),\n totalReads: 0,\n };\n }\n\n const P = await getPipelines();\n const pipeline = buildRootPipeline(P, db, collectionPath, spec);\n const snap = await pipeline.execute();\n const rows = snap.results.map((r) => r.data() as Record<string, unknown>);\n\n const result = decodeTree(rows, spec.hops);\n // Patch hop[0].sourceCount with the input source count (the decoder\n // doesn't have visibility into the original `spec.sources` length).\n if (result.hops.length > 0) {\n result.hops[0] = { ...result.hops[0], sourceCount: spec.sources.length };\n }\n return result;\n}\n","/**\n * Pipeline query adapter — translates QueryFilter[] to Firestore Pipeline\n * expressions and executes them via db.pipeline().\n *\n * Only handles query() — doc-level operations (get/set/update/delete) stay\n * on the standard FirestoreAdapter.\n */\nimport type { Firestore, Pipelines } from '@google-cloud/firestore';\n\nimport type { QueryFilter, QueryOptions, StoredGraphRecord } from '../types.js';\n\n/**\n * Minimal interface for the Pipeline query adapter.\n * Only implements the query path — doc operations are handled by FirestoreAdapter.\n */\nexport interface PipelineQueryAdapter {\n query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]>;\n}\n\n/**\n * Lazily loaded Pipelines module. We use dynamic import so that standard-mode\n * users (and the emulator) don't pull in pipeline-related code at module load.\n */\nlet _Pipelines: typeof Pipelines | null = null;\n\nasync function getPipelines(): Promise<typeof Pipelines> {\n if (!_Pipelines) {\n const mod = await import('@google-cloud/firestore');\n _Pipelines = mod.Pipelines;\n }\n return _Pipelines;\n}\n\ntype PipelinesType = typeof Pipelines;\ntype BooleanExpr = Pipelines.BooleanExpression;\n\n/**\n * Maps a QueryFilter to a Pipeline BooleanExpression.\n *\n * Uses the string-based overloads (e.g. `equal(fieldName, value)`) which\n * accept `unknown` values, avoiding type issues with `constant()` overloads.\n */\nfunction buildFilterExpression(P: PipelinesType, filter: QueryFilter): BooleanExpr {\n const { field: fieldName, op, value } = filter;\n\n switch (op) {\n case '==':\n return P.equal(fieldName, value);\n case '!=':\n return P.notEqual(fieldName, value);\n case '<':\n return P.lessThan(fieldName, value);\n case '<=':\n return P.lessThanOrEqual(fieldName, value);\n case '>':\n return P.greaterThan(fieldName, value);\n case '>=':\n return P.greaterThanOrEqual(fieldName, value);\n case 'in':\n return P.equalAny(fieldName, value as Array<unknown>);\n case 'not-in':\n return P.notEqualAny(fieldName, value as Array<unknown>);\n case 'array-contains':\n return P.arrayContains(fieldName, value);\n case 'array-contains-any':\n return P.arrayContainsAny(fieldName, value as Array<unknown>);\n default:\n throw new Error(`Unsupported filter op for pipeline mode: ${op}`);\n }\n}\n\nexport function createPipelineQueryAdapter(\n db: Firestore,\n collectionPath: string,\n): PipelineQueryAdapter {\n return {\n async query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]> {\n const P = await getPipelines();\n\n // Build pipeline\n let pipeline = db.pipeline().collection(collectionPath);\n\n // Apply filters\n if (filters.length === 1) {\n pipeline = pipeline.where(buildFilterExpression(P, filters[0]));\n } else if (filters.length > 1) {\n const [first, second, ...rest] = filters.map((f) => buildFilterExpression(P, f));\n pipeline = pipeline.where(P.and(first, second, ...rest));\n }\n\n // Apply sort\n if (options?.orderBy) {\n const f = P.field(options.orderBy.field);\n const ordering = options.orderBy.direction === 'desc' ? f.descending() : f.ascending();\n pipeline = pipeline.sort(ordering);\n }\n\n // Apply limit\n if (options?.limit !== undefined) {\n pipeline = pipeline.limit(options.limit);\n }\n\n const snap = await pipeline.execute();\n return snap.results.map((r) => r.data() as StoredGraphRecord);\n },\n };\n}\n"],"mappings":"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","names":["_Pipelines","_Pipelines","getPipelines","ENVELOPE_FIELDS","_Pipelines","getPipelines","_Pipelines","getPipelines","_Pipelines","getPipelines","buildFilterExpression"]}