@typicalday/firegraph 0.14.0 → 0.14.1

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.
@@ -1,4 +1,5 @@
1
1
  import {
2
+ buildFirestoreUpdateArgs,
2
3
  bulkRemoveEdges,
3
4
  createBatchAdapter,
4
5
  createFirestoreAdapter,
@@ -8,10 +9,8 @@ import {
8
9
  runFirestoreClassicExpand,
9
10
  runFirestoreFindEdgesProjected,
10
11
  runFirestoreFindNearest
11
- } from "../chunk-PAD7WFFU.js";
12
- import {
13
- deserializeFirestoreTypes
14
- } from "../chunk-C2QMD7RY.js";
12
+ } from "../chunk-DJI3VXXA.js";
13
+ import "../chunk-C2QMD7RY.js";
15
14
  import {
16
15
  createCapabilities
17
16
  } from "../chunk-N5HFDWQX.js";
@@ -23,12 +22,10 @@ import {
23
22
  createMergedRegistry,
24
23
  createRegistry,
25
24
  generateId
26
- } from "../chunk-WRTFC5NG.js";
25
+ } from "../chunk-3AHHXMWX.js";
27
26
  import {
28
- FiregraphError,
29
- assertSafePath,
30
- assertUpdatePayloadExclusive
31
- } from "../chunk-TK64DNVK.js";
27
+ FiregraphError
28
+ } from "../chunk-SIHE4UY4.js";
32
29
  import "../chunk-EQJUUVFG.js";
33
30
 
34
31
  // src/firestore-standard/backend.ts
@@ -45,28 +42,6 @@ var STANDARD_CAPS = /* @__PURE__ */ new Set([
45
42
  "search.vector",
46
43
  "raw.firestore"
47
44
  ]);
48
- function dottedDataPath(op) {
49
- assertSafePath(op.path);
50
- return `data.${op.path.join(".")}`;
51
- }
52
- function buildFirestoreUpdate(update, db) {
53
- assertUpdatePayloadExclusive(update);
54
- const out = {
55
- updatedAt: FieldValue.serverTimestamp()
56
- };
57
- if (update.replaceData) {
58
- out.data = deserializeFirestoreTypes(update.replaceData, db);
59
- } else if (update.dataOps) {
60
- for (const op of update.dataOps) {
61
- const key = dottedDataPath(op);
62
- out[key] = op.delete ? FieldValue.delete() : op.value;
63
- }
64
- }
65
- if (update.v !== void 0) {
66
- out.v = update.v;
67
- }
68
- return out;
69
- }
70
45
  function stampWritableRecord(record) {
71
46
  const now = FieldValue.serverTimestamp();
72
47
  const out = {
@@ -101,7 +76,7 @@ var FirestoreStandardTransactionBackend = class {
101
76
  );
102
77
  }
103
78
  async updateDoc(docId, update) {
104
- this.adapter.updateDoc(docId, buildFirestoreUpdate(update, this.db));
79
+ this.adapter.updateDoc(docId, buildFirestoreUpdateArgs(update, this.db));
105
80
  }
106
81
  async deleteDoc(docId) {
107
82
  this.adapter.deleteDoc(docId);
@@ -120,7 +95,7 @@ var FirestoreStandardBatchBackend = class {
120
95
  );
121
96
  }
122
97
  updateDoc(docId, update) {
123
- this.adapter.updateDoc(docId, buildFirestoreUpdate(update, this.db));
98
+ this.adapter.updateDoc(docId, buildFirestoreUpdateArgs(update, this.db));
124
99
  }
125
100
  deleteDoc(docId) {
126
101
  this.adapter.deleteDoc(docId);
@@ -156,7 +131,7 @@ var FirestoreStandardBackendImpl = class _FirestoreStandardBackendImpl {
156
131
  );
157
132
  }
158
133
  updateDoc(docId, update) {
159
- return this.adapter.updateDoc(docId, buildFirestoreUpdate(update, this.db));
134
+ return this.adapter.updateDoc(docId, buildFirestoreUpdateArgs(update, this.db));
160
135
  }
161
136
  deleteDoc(docId) {
162
137
  return this.adapter.deleteDoc(docId);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/firestore-standard/backend.ts"],"sourcesContent":["/**\n * Firestore Standard edition `StorageBackend`.\n *\n * The Standard edition only has the classic Query API — pipelines and\n * Enterprise-only product features (full-text search, geo) are not\n * available. This file deliberately does not import the pipeline adapter so\n * a Standard-only deployment never pulls Pipeline code into its bundle.\n *\n * Capability declarations follow the conservative invariant established in\n * Phase 1: only declare what the file actually implements at runtime.\n * `query.aggregate` (Phase 6), `query.select` (Phase 7), `search.vector`\n * (Phase 8), and `query.join` (Phase 13a — chunked classic-API fan-out)\n * are wired; `realtime.listen` will be added in a later phase once the\n * matching backend method exists. `query.dml` stays unwired because the\n * classic Query API has no server-side DML statement; mass-delete /\n * mass-update are still routed through the existing `bulkRemoveEdges`\n * fetch-then-write loop.\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 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 { runFirestoreFindEdgesProjected } from '../internal/firestore-projection.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 CascadeResult,\n ExpandParams,\n ExpandResult,\n FindEdgesParams,\n FindNearestParams,\n GraphReader,\n QueryFilter,\n QueryOptions,\n StoredGraphRecord,\n} from '../types.js';\n\n/**\n * Capability union declared by the Firestore Standard backend.\n *\n * Conservative declaration: only capabilities backed by an actual runtime\n * method are listed. `query.aggregate` (Phase 6) and `query.select`\n * (Phase 7) are now wired; `search.vector` and `realtime.listen` will be\n * layered in by their respective phases — this union and the matching\n * cap-set literal are updated in lockstep.\n */\nexport type FirestoreStandardCapability =\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 | 'search.vector'\n | 'raw.firestore';\n\nconst STANDARD_CAPS: ReadonlySet<FirestoreStandardCapability> =\n new Set<FirestoreStandardCapability>([\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 'search.vector',\n 'raw.firestore',\n ]);\n\nexport interface FirestoreStandardOptions {\n /** Internal: the logical scope path inherited from a parent subgraph. */\n scopePath?: string;\n}\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\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\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 FirestoreStandardTransactionBackend 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 FirestoreStandardBatchBackend 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 FirestoreStandardBackendImpl implements StorageBackend<FirestoreStandardCapability> {\n readonly capabilities: BackendCapabilities<FirestoreStandardCapability> =\n createCapabilities(STANDARD_CAPS);\n readonly collectionPath: string;\n readonly scopePath: string;\n private readonly adapter: FirestoreAdapter;\n\n constructor(\n private readonly db: Firestore,\n collectionPath: string,\n scopePath: string,\n ) {\n this.collectionPath = collectionPath;\n this.scopePath = scopePath;\n this.adapter = createFirestoreAdapter(db, collectionPath);\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 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 FirestoreStandardTransactionBackend(txAdapter, this.db));\n });\n }\n\n createBatch(): BatchBackend {\n const batchAdapter = createBatchAdapter(this.db, this.collectionPath);\n return new FirestoreStandardBatchBackend(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 return new FirestoreStandardBackendImpl(this.db, subPath, newScope);\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 aggregate(spec: AggregateSpec, filters: QueryFilter[]): Promise<Record<string, number>> {\n return runFirestoreAggregate(this.db.collection(this.collectionPath), spec, filters, {\n edition: 'standard',\n });\n }\n\n // --- Server-side projection (capability: query.select) ---\n\n /**\n * Run a projecting query via the shared classic-API helper. Both Firestore\n * editions delegate to one implementation so the projection contract\n * (bare-name normalization, builtin / `data.*` resolution, dedup,\n * original-key preservation) stays consistent across editions. See\n * `runFirestoreFindEdgesProjected` for the resolution rules.\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. Both Firestore editions delegate to one implementation so the\n * field-path normalisation and result shape stay consistent across\n * editions. See `runFirestoreFindNearest` for the resolution rules and\n * the validation surface.\n *\n * Standard-edition note: vector search requires a single-field vector\n * index on the indexed `vectorField`, plus a composite index whenever\n * additional `where` filters narrow the candidate set before the ANN\n * walk. Both indexes are configured per project in the Firestore\n * console — firegraph does not auto-provision them.\n */\n findNearest(params: FindNearestParams): Promise<StoredGraphRecord[]> {\n return runFirestoreFindNearest(this.db.collection(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 via the chunked\n * classic-API helper. The classic `'in'` operator caps at 30 elements\n * per call, so the helper splits sources into 30-element chunks and\n * dispatches them in parallel via `Promise.all`. With 100 sources this\n * is `ceil(100/30) = 4` round trips; the per-source `findEdges` loop in\n * `traverse.ts` would have been 100. Enterprise can collapse this\n * further to a single Pipelines `equalAny(...)` call — Standard cannot,\n * so chunked classic is the best Standard can do.\n *\n * The helper applies a cross-chunk re-sort + total-limit slice so the\n * observable contract matches the SQL backends'\n * `WHERE … IN (?,?,…) ORDER BY … LIMIT N` semantics.\n */\n expand(params: ExpandParams): Promise<ExpandResult> {\n return runFirestoreClassicExpand(this.adapter, params);\n }\n}\n\n/**\n * Create a Firestore Standard-edition `StorageBackend`.\n *\n * Standard Firestore does not support pipelines or any Enterprise-only\n * features. `data.*` filters require composite indexes; callers that mostly\n * filter on built-in fields (`aUid`, `axbType`, `bUid`) avoid that\n * requirement.\n */\nexport function createFirestoreStandardBackend(\n db: Firestore,\n collectionPath: string,\n options: FirestoreStandardOptions = {},\n): StorageBackend<FirestoreStandardCapability> {\n const scopePath = options.scopePath ?? '';\n return new FirestoreStandardBackendImpl(db, collectionPath, scopePath);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/firestore-standard/backend.ts"],"sourcesContent":["/**\n * Firestore Standard edition `StorageBackend`.\n *\n * The Standard edition only has the classic Query API — pipelines and\n * Enterprise-only product features (full-text search, geo) are not\n * available. This file deliberately does not import the pipeline adapter so\n * a Standard-only deployment never pulls Pipeline code into its bundle.\n *\n * Capability declarations follow the conservative invariant established in\n * Phase 1: only declare what the file actually implements at runtime.\n * `query.aggregate` (Phase 6), `query.select` (Phase 7), `search.vector`\n * (Phase 8), and `query.join` (Phase 13a — chunked classic-API fan-out)\n * are wired; `realtime.listen` will be added in a later phase once the\n * matching backend method exists. `query.dml` stays unwired because the\n * classic Query API has no server-side DML statement; mass-delete /\n * mass-update are still routed through the existing `bulkRemoveEdges`\n * fetch-then-write loop.\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 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 { runFirestoreFindEdgesProjected } from '../internal/firestore-projection.js';\nimport { buildFirestoreUpdateArgs } from '../internal/firestore-update.js';\nimport { runFirestoreFindNearest } from '../internal/firestore-vector.js';\nimport { buildEdgeQueryPlan } from '../query.js';\nimport type {\n AggregateSpec,\n BulkOptions,\n BulkResult,\n CascadeResult,\n ExpandParams,\n ExpandResult,\n FindEdgesParams,\n FindNearestParams,\n GraphReader,\n QueryFilter,\n QueryOptions,\n StoredGraphRecord,\n} from '../types.js';\n\n/**\n * Capability union declared by the Firestore Standard backend.\n *\n * Conservative declaration: only capabilities backed by an actual runtime\n * method are listed. `query.aggregate` (Phase 6) and `query.select`\n * (Phase 7) are now wired; `search.vector` and `realtime.listen` will be\n * layered in by their respective phases — this union and the matching\n * cap-set literal are updated in lockstep.\n */\nexport type FirestoreStandardCapability =\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 | 'search.vector'\n | 'raw.firestore';\n\nconst STANDARD_CAPS: ReadonlySet<FirestoreStandardCapability> =\n new Set<FirestoreStandardCapability>([\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 'search.vector',\n 'raw.firestore',\n ]);\n\nexport interface FirestoreStandardOptions {\n /** Internal: the logical scope path inherited from a parent subgraph. */\n scopePath?: string;\n}\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 FirestoreStandardTransactionBackend 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, buildFirestoreUpdateArgs(update, this.db));\n }\n\n async deleteDoc(docId: string): Promise<void> {\n this.adapter.deleteDoc(docId);\n }\n}\n\nclass FirestoreStandardBatchBackend 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, buildFirestoreUpdateArgs(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 FirestoreStandardBackendImpl implements StorageBackend<FirestoreStandardCapability> {\n readonly capabilities: BackendCapabilities<FirestoreStandardCapability> =\n createCapabilities(STANDARD_CAPS);\n readonly collectionPath: string;\n readonly scopePath: string;\n private readonly adapter: FirestoreAdapter;\n\n constructor(\n private readonly db: Firestore,\n collectionPath: string,\n scopePath: string,\n ) {\n this.collectionPath = collectionPath;\n this.scopePath = scopePath;\n this.adapter = createFirestoreAdapter(db, collectionPath);\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 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, buildFirestoreUpdateArgs(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 FirestoreStandardTransactionBackend(txAdapter, this.db));\n });\n }\n\n createBatch(): BatchBackend {\n const batchAdapter = createBatchAdapter(this.db, this.collectionPath);\n return new FirestoreStandardBatchBackend(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 return new FirestoreStandardBackendImpl(this.db, subPath, newScope);\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 aggregate(spec: AggregateSpec, filters: QueryFilter[]): Promise<Record<string, number>> {\n return runFirestoreAggregate(this.db.collection(this.collectionPath), spec, filters, {\n edition: 'standard',\n });\n }\n\n // --- Server-side projection (capability: query.select) ---\n\n /**\n * Run a projecting query via the shared classic-API helper. Both Firestore\n * editions delegate to one implementation so the projection contract\n * (bare-name normalization, builtin / `data.*` resolution, dedup,\n * original-key preservation) stays consistent across editions. See\n * `runFirestoreFindEdgesProjected` for the resolution rules.\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. Both Firestore editions delegate to one implementation so the\n * field-path normalisation and result shape stay consistent across\n * editions. See `runFirestoreFindNearest` for the resolution rules and\n * the validation surface.\n *\n * Standard-edition note: vector search requires a single-field vector\n * index on the indexed `vectorField`, plus a composite index whenever\n * additional `where` filters narrow the candidate set before the ANN\n * walk. Both indexes are configured per project in the Firestore\n * console — firegraph does not auto-provision them.\n */\n findNearest(params: FindNearestParams): Promise<StoredGraphRecord[]> {\n return runFirestoreFindNearest(this.db.collection(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 via the chunked\n * classic-API helper. The classic `'in'` operator caps at 30 elements\n * per call, so the helper splits sources into 30-element chunks and\n * dispatches them in parallel via `Promise.all`. With 100 sources this\n * is `ceil(100/30) = 4` round trips; the per-source `findEdges` loop in\n * `traverse.ts` would have been 100. Enterprise can collapse this\n * further to a single Pipelines `equalAny(...)` call — Standard cannot,\n * so chunked classic is the best Standard can do.\n *\n * The helper applies a cross-chunk re-sort + total-limit slice so the\n * observable contract matches the SQL backends'\n * `WHERE … IN (?,?,…) ORDER BY … LIMIT N` semantics.\n */\n expand(params: ExpandParams): Promise<ExpandResult> {\n return runFirestoreClassicExpand(this.adapter, params);\n }\n}\n\n/**\n * Create a Firestore Standard-edition `StorageBackend`.\n *\n * Standard Firestore does not support pipelines or any Enterprise-only\n * features. `data.*` filters require composite indexes; callers that mostly\n * filter on built-in fields (`aUid`, `axbType`, `bUid`) avoid that\n * requirement.\n */\nexport function createFirestoreStandardBackend(\n db: Firestore,\n collectionPath: string,\n options: FirestoreStandardOptions = {},\n): StorageBackend<FirestoreStandardCapability> {\n const scopePath = options.scopePath ?? '';\n return new FirestoreStandardBackendImpl(db, collectionPath, scopePath);\n}\n"],"mappings":"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","names":[]}
package/dist/index.cjs CHANGED
@@ -298,7 +298,6 @@ function isTerminalValue(value) {
298
298
  if (ctor && typeof ctor.name === "string" && FIRESTORE_TERMINAL_CTOR.has(ctor.name)) return true;
299
299
  return true;
300
300
  }
301
- var SAFE_KEY_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
302
301
  function assertNoDeleteSentinels(data, callerLabel) {
303
302
  walkForDeleteSentinels(data, [], { kind: "root" }, ({ path }) => {
304
303
  const where = path.length === 0 ? "<root>" : path.map((p) => JSON.stringify(p)).join(" > ");
@@ -330,9 +329,9 @@ function walkForDeleteSentinels(node, path, parent, visit) {
330
329
  }
331
330
  function assertSafePath(path) {
332
331
  for (const seg of path) {
333
- if (!SAFE_KEY_RE.test(seg)) {
332
+ if (seg === "") {
334
333
  throw new Error(
335
- `firegraph: unsafe object key ${JSON.stringify(seg)} at path ${path.map((p) => JSON.stringify(p)).join(" > ")}. Keys used inside update payloads must match /^[A-Za-z_][A-Za-z0-9_-]*$/ so they can be embedded safely in SQLite JSON paths.`
334
+ `firegraph: empty object key at path ${path.map((p) => JSON.stringify(p)).join(" > ")}. Object keys in update payloads must be non-empty.`
336
335
  );
337
336
  }
338
337
  }