@typicalday/firegraph 0.14.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -3
- package/dist/{backend-DuvHGgK1.d.cts → backend-BpYLdwCW.d.cts} +1 -1
- package/dist/{backend-DuvHGgK1.d.ts → backend-BpYLdwCW.d.ts} +1 -1
- package/dist/backend-CvImIwTY.d.cts +137 -0
- package/dist/backend-YH5HtawN.d.ts +137 -0
- package/dist/backend.d.cts +2 -2
- package/dist/backend.d.ts +2 -2
- package/dist/{chunk-3AHHXMWX.js → chunk-5HIRYV2S.js} +12 -35
- package/dist/chunk-5HIRYV2S.js.map +1 -0
- package/dist/{chunk-DJI3VXXA.js → chunk-7IEZ6IYY.js} +2 -2
- package/dist/chunk-7IEZ6IYY.js.map +1 -0
- package/dist/chunk-FODIMIWY.js +721 -0
- package/dist/chunk-FODIMIWY.js.map +1 -0
- package/dist/chunk-NGAJCALM.js +34 -0
- package/dist/chunk-NGAJCALM.js.map +1 -0
- package/dist/chunk-ULRDQ6HZ.js +862 -0
- package/dist/chunk-ULRDQ6HZ.js.map +1 -0
- package/dist/{client-BKi3vk0Q.d.ts → client-B5o39X79.d.ts} +1 -1
- package/dist/{client-BrsaXtDV.d.cts → client-BGHwxwPg.d.cts} +1 -1
- package/dist/{client-Bk2Cm6xv.d.cts → client-DoyEdJ5w.d.cts} +1 -1
- package/dist/{client-Bk2Cm6xv.d.ts → client-DoyEdJ5w.d.ts} +1 -1
- package/dist/cloudflare/index.cjs +148 -158
- package/dist/cloudflare/index.cjs.map +1 -1
- package/dist/cloudflare/index.d.cts +73 -70
- package/dist/cloudflare/index.d.ts +73 -70
- package/dist/cloudflare/index.js +53 -588
- package/dist/cloudflare/index.js.map +1 -1
- package/dist/codegen/index.d.cts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/firestore-enterprise/index.cjs.map +1 -1
- package/dist/firestore-enterprise/index.d.cts +3 -3
- package/dist/firestore-enterprise/index.d.ts +3 -3
- package/dist/firestore-enterprise/index.js +5 -3
- package/dist/firestore-enterprise/index.js.map +1 -1
- package/dist/firestore-standard/index.cjs.map +1 -1
- package/dist/firestore-standard/index.d.cts +3 -3
- package/dist/firestore-standard/index.d.ts +3 -3
- package/dist/firestore-standard/index.js +3 -2
- package/dist/firestore-standard/index.js.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/query-client/index.d.cts +2 -2
- package/dist/query-client/index.d.ts +2 -2
- package/dist/{registry-Bc7h6WTM.d.cts → registry-BGh7Jqpb.d.cts} +2 -2
- package/dist/{registry-C2KUPVZj.d.ts → registry-tKTb5Kx1.d.ts} +2 -2
- package/dist/sqlite/index.cjs +578 -371
- package/dist/sqlite/index.cjs.map +1 -1
- package/dist/sqlite/index.d.cts +4 -110
- package/dist/sqlite/index.d.ts +4 -110
- package/dist/sqlite/index.js +7 -1144
- package/dist/sqlite/index.js.map +1 -1
- package/dist/sqlite/local.cjs +1835 -0
- package/dist/sqlite/local.cjs.map +1 -0
- package/dist/sqlite/local.d.cts +83 -0
- package/dist/sqlite/local.d.ts +83 -0
- package/dist/sqlite/local.js +121 -0
- package/dist/sqlite/local.js.map +1 -0
- package/package.json +15 -1
- package/dist/chunk-3AHHXMWX.js.map +0 -1
- package/dist/chunk-DJI3VXXA.js.map +0 -1
- package/dist/chunk-NNBSUOOF.js +0 -289
- package/dist/chunk-NNBSUOOF.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { c as createGraphClient } from '../client-
|
|
2
|
-
export { M as META_EDGE_TYPE, a as META_NODE_TYPE,
|
|
1
|
+
export { c as createGraphClient } from '../client-BGHwxwPg.cjs';
|
|
2
|
+
export { M as META_EDGE_TYPE, a as META_NODE_TYPE, c as createMergedRegistry, b as createRegistry, g as generateId } from '../registry-BGh7Jqpb.cjs';
|
|
3
3
|
import { Firestore } from '@google-cloud/firestore';
|
|
4
|
-
import { S as StorageBackend } from '../backend-
|
|
4
|
+
import { S as StorageBackend } from '../backend-BpYLdwCW.cjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Firestore Standard edition `StorageBackend`.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { c as createGraphClient } from '../client-
|
|
2
|
-
export { M as META_EDGE_TYPE, a as META_NODE_TYPE,
|
|
1
|
+
export { c as createGraphClient } from '../client-B5o39X79.js';
|
|
2
|
+
export { M as META_EDGE_TYPE, a as META_NODE_TYPE, c as createMergedRegistry, b as createRegistry, g as generateId } from '../registry-tKTb5Kx1.js';
|
|
3
3
|
import { Firestore } from '@google-cloud/firestore';
|
|
4
|
-
import { S as StorageBackend } from '../backend-
|
|
4
|
+
import { S as StorageBackend } from '../backend-BpYLdwCW.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Firestore Standard edition `StorageBackend`.
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
runFirestoreClassicExpand,
|
|
10
10
|
runFirestoreFindEdgesProjected,
|
|
11
11
|
runFirestoreFindNearest
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-7IEZ6IYY.js";
|
|
13
13
|
import "../chunk-C2QMD7RY.js";
|
|
14
14
|
import {
|
|
15
15
|
createCapabilities
|
|
@@ -22,7 +22,8 @@ import {
|
|
|
22
22
|
createMergedRegistry,
|
|
23
23
|
createRegistry,
|
|
24
24
|
generateId
|
|
25
|
-
} from "../chunk-
|
|
25
|
+
} from "../chunk-5HIRYV2S.js";
|
|
26
|
+
import "../chunk-NGAJCALM.js";
|
|
26
27
|
import {
|
|
27
28
|
FiregraphError
|
|
28
29
|
} from "../chunk-SIHE4UY4.js";
|
|
@@ -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 { 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":[]}
|
|
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.d.cts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
export { c as createGraphClient, a as createGraphClientFromBackend } from './client-
|
|
1
|
+
export { c as createGraphClient, a as createGraphClientFromBackend } from './client-BGHwxwPg.cjs';
|
|
2
2
|
export { CodegenOptions, generateTypes } from './codegen/index.cjs';
|
|
3
|
-
import { I as IndexSpec, l as DiscoveryResult, R as RegistryEntry,
|
|
4
|
-
export {
|
|
3
|
+
import { I as IndexSpec, l as DiscoveryResult, R as RegistryEntry, v as StoredGraphRecord, H as MigrationWriteBack, K as MigrationStep, p as GraphRegistry, F as FindEdgesParams, L as QueryPlan, N as FindNodesParams, Q as QueryFilter, M as MigrationExecutor, O as MigrationFn, P as StoredMigrationStep, o as GraphClient, q as GraphReader, V as TraversalBuilder } from './backend-BpYLdwCW.cjs';
|
|
4
|
+
export { w as AggregateExtension, x as AggregateField, y as AggregateOp, z as AggregateResult, A as AggregateSpec, X as BulkBatchError, t as BulkOptions, Y as BulkProgress, u as BulkResult, b as BulkUpdatePatch, C as Capability, s as CascadeResult, Z as CoreGraphClient, _ as DefineTypeOptions, $ as DiscoveredEntity, a0 as DistanceMeasure, d as DmlExtension, n as DynamicGraphClient, a1 as DynamicGraphMethods, m as DynamicRegistryConfig, a2 as EdgeTopology, a3 as EdgeTypeData, E as ExpandParams, e as ExpandResult, a4 as FindEdgesProjectedParams, a5 as FindNearestParams, a6 as FiregraphConfig, a7 as FullTextSearchExtension, a8 as GeoExtension, a9 as GraphBatch, G as GraphClientOptions, aa as GraphRecord, ab as GraphTransaction, ac as GraphWriter, ad as HopDefinition, ae as HopResult, af as IndexFieldSpec, J as JoinExtension, ag as NodeTypeData, ah as ProjectedRow, ai as QueryMode, r as QueryOptions, aj as RawFirestoreExtension, ak as RawSqlExtension, al as RealtimeListenExtension, am as ScanProtection, an as SelectExtension, ao as TraversalOptions, ap as TraversalResult, aq as VectorExtension, ar as ViewContext, as as ViewDefaultsConfig, at as ViewResolverConfig, au as WhereClause, av as defineConfig, h as deleteField, aw as resolveView } from './backend-BpYLdwCW.cjs';
|
|
5
5
|
import { F as FiregraphError } from './errors-BRc3I_eH.cjs';
|
|
6
6
|
export { C as CapabilityNotSupportedError, a as CrossBackendTransactionError, D as DynamicRegistryError, E as EdgeNotFoundError, I as InvalidQueryError, M as MigrationError, N as NodeNotFoundError, Q as QuerySafetyError, R as RegistryScopeError, b as RegistryViolationError, T as TraversalError, V as ValidationError } from './errors-BRc3I_eH.cjs';
|
|
7
|
-
export { B as BOOTSTRAP_ENTRIES, E as EDGE_TYPE_SCHEMA, M as META_EDGE_TYPE, a as META_NODE_TYPE, N as NODE_TYPE_SCHEMA,
|
|
8
|
-
export { Q as QueryClient,
|
|
7
|
+
export { B as BOOTSTRAP_ENTRIES, E as EDGE_TYPE_SCHEMA, M as META_EDGE_TYPE, a as META_NODE_TYPE, N as NODE_TYPE_SCHEMA, d as createBootstrapRegistry, c as createMergedRegistry, b as createRegistry, e as createRegistryFromGraph, f as generateDeterministicUid, g as generateId } from './registry-BGh7Jqpb.cjs';
|
|
8
|
+
export { Q as QueryClient, f as QueryClientError, g as QueryClientErrorCode, h as QueryClientOptions } from './client-DoyEdJ5w.cjs';
|
|
9
9
|
export { S as StorageScopeSegment, a as appendStorageScope, i as isAncestorScopeUid, p as parseStorageScope, r as resolveAncestorScope } from './scope-path-CROFZGr9.cjs';
|
|
10
10
|
import { Firestore } from '@google-cloud/firestore';
|
|
11
11
|
export { E as EntityViewConfig, a as EntityViewMeta, V as ViewComponentClass, b as ViewMeta, c as ViewRegistry, d as ViewRegistryInput, e as defineViews } from './views-DL60k0cf.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
export { c as createGraphClient, a as createGraphClientFromBackend } from './client-
|
|
1
|
+
export { c as createGraphClient, a as createGraphClientFromBackend } from './client-B5o39X79.js';
|
|
2
2
|
export { CodegenOptions, generateTypes } from './codegen/index.js';
|
|
3
|
-
import { I as IndexSpec, l as DiscoveryResult, R as RegistryEntry,
|
|
4
|
-
export {
|
|
3
|
+
import { I as IndexSpec, l as DiscoveryResult, R as RegistryEntry, v as StoredGraphRecord, H as MigrationWriteBack, K as MigrationStep, p as GraphRegistry, F as FindEdgesParams, L as QueryPlan, N as FindNodesParams, Q as QueryFilter, M as MigrationExecutor, O as MigrationFn, P as StoredMigrationStep, o as GraphClient, q as GraphReader, V as TraversalBuilder } from './backend-BpYLdwCW.js';
|
|
4
|
+
export { w as AggregateExtension, x as AggregateField, y as AggregateOp, z as AggregateResult, A as AggregateSpec, X as BulkBatchError, t as BulkOptions, Y as BulkProgress, u as BulkResult, b as BulkUpdatePatch, C as Capability, s as CascadeResult, Z as CoreGraphClient, _ as DefineTypeOptions, $ as DiscoveredEntity, a0 as DistanceMeasure, d as DmlExtension, n as DynamicGraphClient, a1 as DynamicGraphMethods, m as DynamicRegistryConfig, a2 as EdgeTopology, a3 as EdgeTypeData, E as ExpandParams, e as ExpandResult, a4 as FindEdgesProjectedParams, a5 as FindNearestParams, a6 as FiregraphConfig, a7 as FullTextSearchExtension, a8 as GeoExtension, a9 as GraphBatch, G as GraphClientOptions, aa as GraphRecord, ab as GraphTransaction, ac as GraphWriter, ad as HopDefinition, ae as HopResult, af as IndexFieldSpec, J as JoinExtension, ag as NodeTypeData, ah as ProjectedRow, ai as QueryMode, r as QueryOptions, aj as RawFirestoreExtension, ak as RawSqlExtension, al as RealtimeListenExtension, am as ScanProtection, an as SelectExtension, ao as TraversalOptions, ap as TraversalResult, aq as VectorExtension, ar as ViewContext, as as ViewDefaultsConfig, at as ViewResolverConfig, au as WhereClause, av as defineConfig, h as deleteField, aw as resolveView } from './backend-BpYLdwCW.js';
|
|
5
5
|
import { F as FiregraphError } from './errors-BRc3I_eH.js';
|
|
6
6
|
export { C as CapabilityNotSupportedError, a as CrossBackendTransactionError, D as DynamicRegistryError, E as EdgeNotFoundError, I as InvalidQueryError, M as MigrationError, N as NodeNotFoundError, Q as QuerySafetyError, R as RegistryScopeError, b as RegistryViolationError, T as TraversalError, V as ValidationError } from './errors-BRc3I_eH.js';
|
|
7
|
-
export { B as BOOTSTRAP_ENTRIES, E as EDGE_TYPE_SCHEMA, M as META_EDGE_TYPE, a as META_NODE_TYPE, N as NODE_TYPE_SCHEMA,
|
|
8
|
-
export { Q as QueryClient,
|
|
7
|
+
export { B as BOOTSTRAP_ENTRIES, E as EDGE_TYPE_SCHEMA, M as META_EDGE_TYPE, a as META_NODE_TYPE, N as NODE_TYPE_SCHEMA, d as createBootstrapRegistry, c as createMergedRegistry, b as createRegistry, e as createRegistryFromGraph, f as generateDeterministicUid, g as generateId } from './registry-tKTb5Kx1.js';
|
|
8
|
+
export { Q as QueryClient, f as QueryClientError, g as QueryClientErrorCode, h as QueryClientOptions } from './client-DoyEdJ5w.js';
|
|
9
9
|
export { S as StorageScopeSegment, a as appendStorageScope, i as isAncestorScopeUid, p as parseStorageScope, r as resolveAncestorScope } from './scope-path-CROFZGr9.js';
|
|
10
10
|
import { Firestore } from '@google-cloud/firestore';
|
|
11
11
|
export { E as EntityViewConfig, a as EntityViewMeta, V as ViewComponentClass, b as ViewMeta, c as ViewRegistry, d as ViewRegistryInput, e as defineViews } from './views-DL60k0cf.js';
|
package/dist/index.js
CHANGED
|
@@ -23,7 +23,6 @@ import {
|
|
|
23
23
|
} from "./chunk-C2QMD7RY.js";
|
|
24
24
|
import {
|
|
25
25
|
BOOTSTRAP_ENTRIES,
|
|
26
|
-
DEFAULT_QUERY_LIMIT,
|
|
27
26
|
EDGE_TYPE_SCHEMA,
|
|
28
27
|
META_EDGE_TYPE,
|
|
29
28
|
META_NODE_TYPE,
|
|
@@ -35,8 +34,6 @@ import {
|
|
|
35
34
|
compileMigrationFn,
|
|
36
35
|
compileMigrations,
|
|
37
36
|
compileSchema,
|
|
38
|
-
computeEdgeDocId,
|
|
39
|
-
computeNodeDocId,
|
|
40
37
|
createBootstrapRegistry,
|
|
41
38
|
createGraphClient,
|
|
42
39
|
createGraphClientFromBackend,
|
|
@@ -54,7 +51,12 @@ import {
|
|
|
54
51
|
migrateRecords,
|
|
55
52
|
precompileSource,
|
|
56
53
|
validateMigrationChain
|
|
57
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-5HIRYV2S.js";
|
|
55
|
+
import {
|
|
56
|
+
DEFAULT_QUERY_LIMIT,
|
|
57
|
+
computeEdgeDocId,
|
|
58
|
+
computeNodeDocId
|
|
59
|
+
} from "./chunk-NGAJCALM.js";
|
|
58
60
|
import {
|
|
59
61
|
CapabilityNotSupportedError,
|
|
60
62
|
CrossBackendTransactionError,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts","../src/cross-graph.ts","../src/discover.ts","../src/indexes.ts","../src/traverse.ts","../src/views.ts"],"sourcesContent":["/**\n * Firegraph Configuration — project-level config file support.\n *\n * Projects create a `firegraph.config.ts` (or `.js`/`.mjs`) in their root:\n *\n * @example\n * ```ts\n * import { defineConfig } from 'firegraph';\n *\n * export default defineConfig({\n * entities: './entities',\n * project: 'my-project',\n * collection: 'graph',\n * });\n * ```\n */\n\nimport type { DynamicRegistryConfig, QueryMode } from './types.js';\n\n// ---------------------------------------------------------------------------\n// View Resolution Types\n// ---------------------------------------------------------------------------\n\n/** Display contexts where views can appear. */\nexport type ViewContext = 'listing' | 'detail' | 'inline';\n\n/** View resolution configuration for a single entity type. */\nexport interface ViewResolverConfig {\n /** Default view name (e.g. 'card'). Falls back to 'json' if unset. */\n default?: string;\n /** View to use in NodeBrowser listing rows. */\n listing?: string;\n /** View to use on the NodeDetail page. */\n detail?: string;\n /** View to use for inline/embedded previews (edge rows, traversal). */\n inline?: string;\n}\n\n/** Declarative view defaults, keyed by entity type. */\nexport interface ViewDefaultsConfig {\n /** Node view defaults keyed by aType (e.g. 'user', 'task'). */\n nodes?: Record<string, ViewResolverConfig>;\n /** Edge view defaults keyed by axbType (e.g. 'hasDeparture'). */\n edges?: Record<string, ViewResolverConfig>;\n}\n\n// ---------------------------------------------------------------------------\n// Config Shape\n// ---------------------------------------------------------------------------\n\n/** Project-level firegraph configuration. */\nexport interface FiregraphConfig {\n /** Path to entities directory (per-entity folder convention). */\n entities?: string;\n /** GCP project ID. */\n project?: string;\n /** Firestore collection path (default: 'graph'). */\n collection?: string;\n /** Firestore emulator address (e.g. '127.0.0.1:8080'). */\n emulator?: string;\n /**\n * Query execution backend.\n *\n * - `'pipeline'` (default) — Uses Firestore Pipeline API. Requires Enterprise\n * Firestore. Enables indexless queries on `data.*` fields.\n * - `'standard'` — Uses standard Firestore `.where().get()` queries. Not\n * recommended for production. See README for risk details.\n *\n * When the emulator is active, always falls back to `'standard'`.\n */\n queryMode?: QueryMode;\n\n /**\n * AI chat configuration. Auto-detects `claude` CLI on PATH by default.\n * Set to `false` to disable chat even if claude is available.\n */\n chat?:\n | false\n | {\n /** Claude model to use (default: 'sonnet'). */\n model?: string;\n /** Maximum concurrent claude processes (default: 2). */\n maxConcurrency?: number;\n };\n\n /** Editor-specific settings. */\n editor?: {\n /** Server port (default: 3883). */\n port?: number;\n /** Force read-only mode. */\n readonly?: boolean;\n };\n\n /** Declarative view defaults per entity type (overrides per-entity meta.json). */\n viewDefaults?: ViewDefaultsConfig;\n\n /**\n * Dynamic registry mode. When set, the editor loads type definitions\n * from Firestore meta-nodes in addition to filesystem entities.\n * Filesystem types take precedence on name conflicts.\n */\n registryMode?: DynamicRegistryConfig;\n}\n\n// ---------------------------------------------------------------------------\n// defineConfig()\n// ---------------------------------------------------------------------------\n\n/**\n * Identity function providing type-checking and autocomplete for config files.\n *\n * @example\n * ```ts\n * import { defineConfig } from 'firegraph';\n * export default defineConfig({ entities: './entities' });\n * ```\n */\nexport function defineConfig(config: FiregraphConfig): FiregraphConfig {\n return config;\n}\n\n// ---------------------------------------------------------------------------\n// View Resolution (pure — works client-side and server-side)\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve which view to show for a given entity.\n *\n * 1. If `context` is provided and a context-specific default exists, use it.\n * 2. Falls back to `resolverConfig.default`.\n * 3. Ultimate fallback: `'json'`.\n *\n * Only returns view names that exist in `availableViewNames`.\n */\nexport function resolveView(\n resolverConfig: ViewResolverConfig | undefined,\n availableViewNames: string[],\n context?: ViewContext,\n): string {\n if (!resolverConfig) return 'json';\n\n const available = new Set(availableViewNames);\n\n if (context) {\n const contextDefault = resolverConfig[context];\n if (contextDefault && available.has(contextDefault)) {\n return contextDefault;\n }\n }\n\n if (resolverConfig.default && available.has(resolverConfig.default)) {\n return resolverConfig.default;\n }\n\n return 'json';\n}\n","/**\n * Cross-graph edge resolution utilities.\n *\n * Provides path-scanning resolution for determining whether an edge's source\n * (aUid) is an ancestor node by checking if the UID appears in the Firestore\n * collection path.\n *\n * Firestore paths have a rigid alternating structure:\n * collection / docId / collection / docId / collection\n *\n * Given a path like `graph/A/workspace/B/context`, segments at even indices\n * are collection names and odd indices are document IDs. When we find a UID\n * at an odd index, the collection containing that document is the path up to\n * (and including) the preceding even-index segment.\n */\n\n/**\n * Parse a Firestore collection path and determine the collection path\n * where a given UID's document lives, if that UID is an ancestor in the path.\n *\n * @param collectionPath - The full Firestore collection path of the current client\n * @param uid - The UID to search for in the path\n * @returns The collection path containing the UID, or `null` if not found in the path\n *\n * @example\n * ```ts\n * // Path: graph/A/workspace/B/context\n * resolveAncestorCollection('graph/A/workspace/B/context', 'A')\n * // → 'graph'\n *\n * resolveAncestorCollection('graph/A/workspace/B/context', 'B')\n * // → 'graph/A/workspace'\n *\n * resolveAncestorCollection('graph/A/workspace/B/context', 'unknown')\n * // → null\n * ```\n */\nexport function resolveAncestorCollection(collectionPath: string, uid: string): string | null {\n const segments = collectionPath.split('/');\n\n // Walk odd-indexed segments (document IDs in Firestore's alternating path structure)\n for (let i = 1; i < segments.length; i += 2) {\n if (segments[i] === uid) {\n // The collection containing this doc is everything up to index i-1\n return segments.slice(0, i).join('/');\n }\n }\n\n return null;\n}\n\n/**\n * Check whether a UID belongs to an ancestor node by scanning the collection path.\n *\n * @param collectionPath - The full Firestore collection path of the current client\n * @param uid - The UID to check\n * @returns `true` if the UID appears as a document segment in the path\n */\nexport function isAncestorUid(collectionPath: string, uid: string): boolean {\n return resolveAncestorCollection(collectionPath, uid) !== null;\n}\n","/**\n * Entity Discovery — convention-based auto-discovery of entities from\n * a per-entity folder structure.\n *\n * Scans `entitiesDir/nodes/` and `entitiesDir/edges/` subdirectories.\n * Each subfolder is treated as an entity type.\n *\n * Schema files can be either `schema.json` (plain JSON Schema) or\n * `schema.ts` / `schema.js` (a module whose default export is a JSON Schema\n * object). When both exist, the TS/JS file takes precedence so that authors\n * can compose schemas programmatically while keeping a JSON fallback.\n *\n * @example\n * ```\n * entities/\n * nodes/\n * task/\n * schema.json | schema.ts (required — one or both)\n * views.ts (optional)\n * sample.json (optional)\n * meta.json (optional)\n * edges/\n * hasStep/\n * schema.json | schema.ts (required — one or both)\n * edge.json (required — topology)\n * views.ts (optional)\n * sample.json (optional)\n * meta.json (optional)\n * ```\n */\n\nimport { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { join, resolve } from 'node:path';\n\nimport type * as jitiNS from 'jiti';\n\nimport type { ViewResolverConfig } from './config.js';\nimport { FiregraphError } from './errors.js';\nimport type {\n DiscoveredEntity,\n DiscoveryResult,\n EdgeTopology,\n IndexSpec,\n MigrationStep,\n MigrationWriteBack,\n} from './types.js';\n\nexport class DiscoveryError extends FiregraphError {\n constructor(message: string) {\n super(message, 'DISCOVERY_ERROR');\n this.name = 'DiscoveryError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// JSON parsing helpers\n// ---------------------------------------------------------------------------\n\nfunction readJson(filePath: string): unknown {\n try {\n const raw = readFileSync(filePath, 'utf-8');\n return JSON.parse(raw);\n } catch (err: unknown) {\n const msg =\n err instanceof SyntaxError\n ? `Invalid JSON in ${filePath}: ${err.message}`\n : `Cannot read ${filePath}: ${(err as Error).message}`;\n throw new DiscoveryError(msg);\n }\n}\n\nfunction readJsonIfExists(filePath: string): unknown | undefined {\n if (!existsSync(filePath)) return undefined;\n return readJson(filePath);\n}\n\n// ---------------------------------------------------------------------------\n// Schema file loading (JSON or TS/JS via jiti)\n// ---------------------------------------------------------------------------\n\nconst SCHEMA_SCRIPT_EXTENSIONS = ['.ts', '.js', '.mts', '.mjs'];\n\n/**\n * Attempt to load a schema from a TS/JS module (default export) or fall back\n * to schema.json. Returns the parsed schema object or throws.\n */\nfunction loadSchema(dir: string, entityLabel: string): object {\n // Prefer TS/JS schema — allows programmatic composition & shared definitions\n for (const ext of SCHEMA_SCRIPT_EXTENSIONS) {\n const candidate = join(dir, `schema${ext}`);\n if (existsSync(candidate)) {\n return loadSchemaModule(candidate, entityLabel);\n }\n }\n\n // Fall back to schema.json\n const jsonPath = join(dir, 'schema.json');\n if (existsSync(jsonPath)) {\n return readJson(jsonPath) as object;\n }\n\n throw new DiscoveryError(\n `Missing schema for ${entityLabel} in ${dir}. ` +\n 'Provide a schema.ts (or .js/.mts/.mjs) or schema.json file.',\n );\n}\n\nlet _jiti: ((id: string) => unknown) | undefined;\n\nfunction getJiti(): (id: string) => unknown {\n if (!_jiti) {\n const base = typeof __filename !== 'undefined' ? __filename : import.meta.url;\n const esmRequire = createRequire(base);\n const { createJiti } = esmRequire('jiti') as typeof jitiNS;\n _jiti = createJiti(base, { interopDefault: true });\n }\n return _jiti;\n}\n\nfunction loadSchemaModule(filePath: string, entityLabel: string): object {\n try {\n const jiti = getJiti();\n const mod = jiti(filePath) as { default?: unknown } | unknown;\n const schema =\n mod && typeof mod === 'object' && 'default' in mod\n ? (mod as { default: unknown }).default\n : mod;\n\n if (!schema || typeof schema !== 'object') {\n throw new DiscoveryError(\n `Schema file ${filePath} for ${entityLabel} must default-export a JSON Schema object.`,\n );\n }\n return schema as object;\n } catch (err: unknown) {\n if (err instanceof DiscoveryError) throw err;\n throw new DiscoveryError(\n `Failed to load schema module ${filePath} for ${entityLabel}: ${(err as Error).message}`,\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// View file detection\n// ---------------------------------------------------------------------------\n\nconst VIEW_EXTENSIONS = ['.ts', '.js', '.mts', '.mjs'];\n\nfunction findViewsFile(dir: string): string | undefined {\n for (const ext of VIEW_EXTENSIONS) {\n const candidate = join(dir, `views${ext}`);\n if (existsSync(candidate)) return candidate;\n }\n return undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Migration file detection & loading\n// ---------------------------------------------------------------------------\n\nconst MIGRATION_EXTENSIONS = ['.ts', '.js', '.mts', '.mjs'];\n\nfunction findMigrationsFile(dir: string): string | undefined {\n for (const ext of MIGRATION_EXTENSIONS) {\n const candidate = join(dir, `migrations${ext}`);\n if (existsSync(candidate)) return candidate;\n }\n return undefined;\n}\n\nfunction loadMigrations(filePath: string, entityLabel: string): MigrationStep[] {\n try {\n const jiti = getJiti();\n const mod = jiti(filePath) as { default?: unknown } | unknown;\n const migrations =\n mod && typeof mod === 'object' && 'default' in mod\n ? (mod as { default: unknown }).default\n : mod;\n\n if (!Array.isArray(migrations)) {\n throw new DiscoveryError(\n `Migrations file ${filePath} for ${entityLabel} must default-export an array of MigrationStep.`,\n );\n }\n return migrations as MigrationStep[];\n } catch (err: unknown) {\n if (err instanceof DiscoveryError) throw err;\n throw new DiscoveryError(\n `Failed to load migrations ${filePath} for ${entityLabel}: ${(err as Error).message}`,\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Entity loaders\n// ---------------------------------------------------------------------------\n\nfunction loadNodeEntity(dir: string, name: string): DiscoveredEntity {\n const schema = loadSchema(dir, `node type \"${name}\"`);\n const meta = readJsonIfExists(join(dir, 'meta.json')) as\n | {\n description?: string;\n titleField?: string;\n subtitleField?: string;\n viewDefaults?: ViewResolverConfig;\n allowedIn?: string[];\n migrationWriteBack?: MigrationWriteBack;\n indexes?: IndexSpec[];\n }\n | undefined;\n const sampleData = readJsonIfExists(join(dir, 'sample.json')) as\n | Record<string, unknown>\n | undefined;\n const viewsPath = findViewsFile(dir);\n const migrationsPath = findMigrationsFile(dir);\n const migrations = migrationsPath\n ? loadMigrations(migrationsPath, `node type \"${name}\"`)\n : undefined;\n\n return {\n kind: 'node',\n name,\n schema,\n description: meta?.description,\n titleField: meta?.titleField,\n subtitleField: meta?.subtitleField,\n viewDefaults: meta?.viewDefaults,\n viewsPath,\n sampleData,\n allowedIn: meta?.allowedIn,\n migrations,\n migrationWriteBack: meta?.migrationWriteBack,\n indexes: meta?.indexes,\n };\n}\n\nfunction loadEdgeEntity(dir: string, name: string): DiscoveredEntity {\n const schema = loadSchema(dir, `edge type \"${name}\"`);\n\n const edgePath = join(dir, 'edge.json');\n if (!existsSync(edgePath)) {\n throw new DiscoveryError(\n `Missing edge.json for edge type \"${name}\" in ${dir}. ` +\n 'Edge entities must declare topology (from/to node types).',\n );\n }\n const topology = readJson(edgePath) as EdgeTopology;\n\n // Validate topology shape\n if (!topology.from) {\n throw new DiscoveryError(`edge.json for \"${name}\" is missing required \"from\" field`);\n }\n if (!topology.to) {\n throw new DiscoveryError(`edge.json for \"${name}\" is missing required \"to\" field`);\n }\n\n const meta = readJsonIfExists(join(dir, 'meta.json')) as\n | {\n description?: string;\n titleField?: string;\n subtitleField?: string;\n viewDefaults?: ViewResolverConfig;\n allowedIn?: string[];\n targetGraph?: string;\n migrationWriteBack?: MigrationWriteBack;\n indexes?: IndexSpec[];\n }\n | undefined;\n const sampleData = readJsonIfExists(join(dir, 'sample.json')) as\n | Record<string, unknown>\n | undefined;\n const viewsPath = findViewsFile(dir);\n const migrationsPath = findMigrationsFile(dir);\n const migrations = migrationsPath\n ? loadMigrations(migrationsPath, `edge type \"${name}\"`)\n : undefined;\n\n return {\n kind: 'edge',\n name,\n schema,\n topology,\n description: meta?.description,\n titleField: meta?.titleField,\n subtitleField: meta?.subtitleField,\n viewDefaults: meta?.viewDefaults,\n viewsPath,\n sampleData,\n allowedIn: meta?.allowedIn,\n targetGraph:\n topology.targetGraph ?? (meta as { targetGraph?: string } | undefined)?.targetGraph,\n migrations,\n migrationWriteBack: meta?.migrationWriteBack,\n indexes: meta?.indexes,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Directory scanner\n// ---------------------------------------------------------------------------\n\nfunction getSubdirectories(dir: string): string[] {\n if (!existsSync(dir)) return [];\n return readdirSync(dir, { withFileTypes: true })\n .filter((d) => d.isDirectory())\n .map((d) => d.name);\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport interface DiscoveryWarning {\n code: 'DANGLING_TOPOLOGY_REF';\n message: string;\n}\n\nexport interface DiscoverResult {\n result: DiscoveryResult;\n warnings: DiscoveryWarning[];\n}\n\n/**\n * Scan an entities directory and return all discovered nodes and edges.\n *\n * @param entitiesDir - Path to the entities directory (absolute or relative to cwd)\n * @returns Discovery result with nodes and edges maps, plus any warnings\n */\nexport function discoverEntities(entitiesDir: string): DiscoverResult {\n const absDir = resolve(entitiesDir);\n\n if (!existsSync(absDir) || !statSync(absDir).isDirectory()) {\n throw new DiscoveryError(`Entities directory not found: ${entitiesDir}`);\n }\n\n const nodes = new Map<string, DiscoveredEntity>();\n const edges = new Map<string, DiscoveredEntity>();\n const warnings: DiscoveryWarning[] = [];\n\n // Discover nodes\n const nodesDir = join(absDir, 'nodes');\n for (const name of getSubdirectories(nodesDir)) {\n nodes.set(name, loadNodeEntity(join(nodesDir, name), name));\n }\n\n // Discover edges\n const edgesDir = join(absDir, 'edges');\n for (const name of getSubdirectories(edgesDir)) {\n edges.set(name, loadEdgeEntity(join(edgesDir, name), name));\n }\n\n // Validate topology references\n const nodeNames = new Set(nodes.keys());\n for (const [axbType, entity] of edges) {\n const topology = entity.topology!;\n const fromTypes = Array.isArray(topology.from) ? topology.from : [topology.from];\n const toTypes = Array.isArray(topology.to) ? topology.to : [topology.to];\n\n for (const ref of [...fromTypes, ...toTypes]) {\n if (!nodeNames.has(ref)) {\n warnings.push({\n code: 'DANGLING_TOPOLOGY_REF',\n message: `Edge \"${axbType}\" references node type \"${ref}\" which was not found in the nodes directory`,\n });\n }\n }\n }\n\n return {\n result: { nodes, edges },\n warnings,\n };\n}\n","/**\n * Firestore composite index generator.\n *\n * Translates firegraph's declarative `IndexSpec[]` (core preset plus per-entry\n * registry indexes) into the `firestore.indexes.json` shape consumed by\n * `firebase deploy --only firestore:indexes`.\n *\n * ## What Firestore needs\n *\n * Firestore auto-indexes every top-level field (including `data.*`) for\n * single-field equality queries — we only need to emit *composite* indexes\n * here. That means:\n *\n * 1. Single-field specs are dropped (Firestore already covers them).\n * 2. Composite specs (two or more fields) get one `FirestoreIndex`.\n * 3. Specs with `where` are dropped with a warning — Firestore composite\n * indexes do not support partial predicates.\n * 4. When a registry entry has `targetGraph` set, every composite is also\n * emitted with `queryScope: 'COLLECTION_GROUP'` under the targetGraph\n * name, so `findEdgesGlobal()` queries across subgraphs can hit an\n * index.\n *\n * The SQLite-flavored backends (DO, legacy) consume the same `IndexSpec[]`\n * via `src/internal/sqlite-index-ddl.ts` but emit every spec (single fields\n * included) as `CREATE INDEX` DDL.\n */\n\nimport { DEFAULT_CORE_INDEXES } from './default-indexes.js';\nimport type { DiscoveryResult, IndexFieldSpec, IndexSpec, RegistryEntry } from './types.js';\n\nexport interface FirestoreIndexField {\n fieldPath: string;\n order: 'ASCENDING' | 'DESCENDING';\n}\n\nexport interface FirestoreIndex {\n collectionGroup: string;\n queryScope: 'COLLECTION' | 'COLLECTION_GROUP';\n fields: FirestoreIndexField[];\n}\n\nexport interface FirestoreIndexConfig {\n indexes: FirestoreIndex[];\n fieldOverrides: unknown[];\n}\n\nexport interface GenerateIndexOptions {\n /**\n * Replaces firegraph's built-in core preset. Defaults to\n * `DEFAULT_CORE_INDEXES`. Pass `[]` to disable core indexes entirely.\n */\n coreIndexes?: IndexSpec[];\n /**\n * Registry entries supplying per-triple `indexes`. Entries without\n * `indexes` contribute no composites; entries with `targetGraph` also\n * trigger `COLLECTION_GROUP` mirrors under each distinct targetGraph\n * segment name.\n */\n registryEntries?: ReadonlyArray<RegistryEntry>;\n /**\n * Entity discovery result. Convenience for callers that have a\n * `DiscoveryResult` but not a built registry — treated as if every\n * discovered entity were expanded to its registry entries carrying just\n * `indexes` + `targetGraph`. Mutually usable with `registryEntries`\n * (both are concatenated and deduplicated at the spec level).\n */\n entities?: DiscoveryResult;\n}\n\nfunction normalizeField(f: string | IndexFieldSpec): IndexFieldSpec {\n return typeof f === 'string' ? { path: f, desc: false } : { path: f.path, desc: !!f.desc };\n}\n\nfunction specFingerprint(spec: IndexSpec, scope: string): string {\n const normalized = spec.fields.map(normalizeField);\n return `${scope}::${JSON.stringify(normalized)}`;\n}\n\nfunction toFirestoreFields(spec: IndexSpec): FirestoreIndexField[] {\n return spec.fields.map((f) => {\n const n = normalizeField(f);\n return {\n fieldPath: n.path,\n order: n.desc ? 'DESCENDING' : 'ASCENDING',\n };\n });\n}\n\nlet warnedOnPartialIndex = false;\n\n/**\n * Build a Firestore index configuration from firegraph's declarative index\n * specs. Deduplicates by field list + scope before emitting. Single-field\n * specs are dropped; partial-index specs (`where` set) are dropped with a\n * one-time warning.\n */\nexport function generateIndexConfig(\n collection: string,\n options: GenerateIndexOptions = {},\n): FirestoreIndexConfig {\n const core = options.coreIndexes ?? [...DEFAULT_CORE_INDEXES];\n const fromEntries = (options.registryEntries ?? []).flatMap((e) => {\n if (!e.indexes) return [] as IndexSpec[];\n return e.indexes;\n });\n\n // DiscoveryResult is a pre-registry shape — it doesn't carry `indexes`\n // per triple (those live on registry entries once built). Accept it to\n // keep the CLI ergonomic, but the only thing we can pull from it right\n // now is the set of distinct `targetGraph` values, which belongs to\n // discovery-time topology metadata. Consumers who need per-entity data\n // indexes must go through the registry path.\n const targetGraphNames = new Set<string>();\n for (const entry of options.registryEntries ?? []) {\n if (entry.targetGraph) targetGraphNames.add(entry.targetGraph);\n }\n if (options.entities) {\n for (const [, entity] of options.entities.edges) {\n const tg = entity.targetGraph ?? entity.topology?.targetGraph;\n if (tg) targetGraphNames.add(tg);\n }\n }\n\n const allSpecs = [...core, ...fromEntries];\n const seen = new Set<string>();\n const indexes: FirestoreIndex[] = [];\n\n for (const spec of allSpecs) {\n if (!spec.fields || spec.fields.length < 2) {\n // Single-field: Firestore auto-indexes — nothing to emit.\n continue;\n }\n if (spec.where) {\n if (!warnedOnPartialIndex) {\n warnedOnPartialIndex = true;\n console.warn(\n 'firegraph: IndexSpec.where is ignored by the Firestore generator — ' +\n 'Firestore composite indexes do not support predicates. ' +\n 'The SQLite backends will still honor `where`.',\n );\n }\n continue;\n }\n\n const fields = toFirestoreFields(spec);\n\n const colKey = specFingerprint(spec, `col:${collection}`);\n if (!seen.has(colKey)) {\n seen.add(colKey);\n indexes.push({\n collectionGroup: collection,\n queryScope: 'COLLECTION',\n fields,\n });\n }\n\n // Mirror into every distinct `targetGraph` as a collection group index.\n // `findEdgesGlobal()` runs across all subcollections matching the\n // targetGraph name, and each pattern needs its own CG index.\n for (const tg of targetGraphNames) {\n const cgKey = specFingerprint(spec, `cg:${tg}`);\n if (seen.has(cgKey)) continue;\n seen.add(cgKey);\n indexes.push({\n collectionGroup: tg,\n queryScope: 'COLLECTION_GROUP',\n fields,\n });\n }\n }\n\n return { indexes, fieldOverrides: [] };\n}\n\n/**\n * Internal test hook — reset the one-time partial-index warning flag so\n * tests covering the warn branch can run sequentially without sharing\n * state.\n */\nexport function _resetIndexGenWarningsForTest(): void {\n warnedOnPartialIndex = false;\n}\n","import { FiregraphError, TraversalError } from './errors.js';\nimport { compileEngineTraversal } from './internal/firestore-traverse-compiler.js';\nimport type {\n EngineHopSpec,\n EngineTraversalParams,\n EngineTraversalResult,\n ExpandParams,\n FindEdgesParams,\n GraphClient,\n GraphReader,\n GraphRegistry,\n HopDefinition,\n HopResult,\n StoredGraphRecord,\n TraversalBuilder,\n TraversalOptions,\n TraversalResult,\n} from './types.js';\n\nconst DEFAULT_LIMIT = 10;\nconst DEFAULT_MAX_READS = 100;\nconst DEFAULT_CONCURRENCY = 5;\n\n/** One-time warning flag: emitted when cross-graph hop is silently skipped. */\nlet _crossGraphWarned = false;\n\n/** Type guard to check if a reader is a GraphClient (has subgraph method). */\nfunction isGraphClient(reader: GraphReader): reader is GraphClient {\n return 'subgraph' in reader && typeof (reader as GraphClient).subgraph === 'function';\n}\n\n/**\n * Type guard to detect whether a reader has the `query.join` capability —\n * i.e. the backend supports server-side multi-source fan-out via `expand()`.\n *\n * Branching on this lets us dispatch one `expand()` call per hop instead of\n * one `findEdges()` per source. The savings scale linearly with source-set\n * size; for the common case of a 50-source hop, that's 50 round trips\n * collapsed into 1.\n *\n * Cross-graph hops are explicitly NOT routed through `expand()` even when\n * the cap is present — each source UID resolves to a distinct subgraph\n * reader, which can't be batched into one server-side statement. The\n * traversal driver enforces that boundary directly (see the `isCrossGraph`\n * branch below).\n */\nfunction readerSupportsExpand(reader: GraphReader): reader is GraphClient & {\n expand(params: ExpandParams): Promise<{ edges: StoredGraphRecord[] }>;\n} {\n if (!isGraphClient(reader)) return false;\n const client = reader as GraphClient;\n // `capabilities` lives on the public client surface (see `CoreGraphClient`).\n // The runtime check is required because `expand` exists on every\n // `GraphClientImpl` (the permissive `GraphClient<Capability>` shape) but\n // throws `UNSUPPORTED_OPERATION` when the backend doesn't declare the cap.\n // Reading `capabilities` instead of feeling for the method is the cap-aware\n // dispatch the rest of the codebase uses.\n return (\n 'capabilities' in client &&\n typeof client.capabilities?.has === 'function' &&\n client.capabilities.has('query.join') &&\n typeof (client as { expand?: unknown }).expand === 'function'\n );\n}\n\n/**\n * Type guard mirroring `readerSupportsExpand` but for the `traversal.serverSide`\n * capability. When this returns `true`, the reader can dispatch a multi-hop\n * spec as one nested-Pipeline round trip via `runEngineTraversal()`.\n *\n * Eligibility at the spec level (no cross-graph hops, no JS filter callbacks,\n * `limitPerSource` set on every hop, depth ≤ `MAX_PIPELINE_DEPTH`,\n * response-size product ≤ `maxReads`) is checked separately by\n * `compileEngineTraversal`. This guard only certifies the reader has the\n * method to call.\n */\nfunction readerSupportsEngineTraversal(reader: GraphReader): reader is GraphClient & {\n runEngineTraversal(params: EngineTraversalParams): Promise<EngineTraversalResult>;\n} {\n if (!isGraphClient(reader)) return false;\n const client = reader as GraphClient;\n return (\n 'capabilities' in client &&\n typeof client.capabilities?.has === 'function' &&\n client.capabilities.has('traversal.serverSide') &&\n typeof (client as { runEngineTraversal?: unknown }).runEngineTraversal === 'function'\n );\n}\n\nclass Semaphore {\n private queue: Array<() => void> = [];\n private active = 0;\n\n constructor(private readonly slots: number) {}\n\n async acquire(): Promise<void> {\n if (this.active < this.slots) {\n this.active++;\n return;\n }\n return new Promise<void>((resolve) => {\n this.queue.push(resolve);\n });\n }\n\n release(): void {\n this.active--;\n const next = this.queue.shift();\n if (next) {\n this.active++;\n next();\n }\n }\n}\n\nclass TraversalBuilderImpl implements TraversalBuilder {\n private readonly hops: HopDefinition[] = [];\n\n constructor(\n private readonly reader: GraphReader,\n private readonly startUid: string,\n private readonly registry?: GraphRegistry,\n ) {}\n\n follow(axbType: string, options?: Omit<HopDefinition, 'axbType'>): TraversalBuilder {\n this.hops.push({ axbType, ...options });\n return this;\n }\n\n async run(options?: TraversalOptions): Promise<TraversalResult> {\n if (this.hops.length === 0) {\n throw new TraversalError('Traversal requires at least one follow() hop');\n }\n\n const maxReads = options?.maxReads ?? DEFAULT_MAX_READS;\n const concurrency = options?.concurrency ?? DEFAULT_CONCURRENCY;\n const returnIntermediates = options?.returnIntermediates ?? false;\n const engineMode = options?.engineTraversal ?? 'auto';\n const semaphore = new Semaphore(concurrency);\n\n // Engine-level traversal — try to compile the whole hop chain into one\n // nested-Pipeline round trip. Eligibility (in order of cheap-first):\n //\n // 1. `engineMode !== 'off'` → caller didn't opt out\n // 2. reader declares `traversal.serverSide` → backend has the path\n // 3. no hop carries a JS `filter` callback → can't run JS server-side\n // 4. no hop is cross-graph → distinct collection paths\n // 5. compiler accepts the spec → depth, limits, response size\n //\n // `engineMode === 'force'` flips failures from silent fallback to a\n // thrown `UNSUPPORTED_OPERATION`, which is what tests/benchmarks want.\n // `engineMode === 'auto'` (the default) silently falls back so existing\n // callers see the new fast-path on Enterprise without any code change.\n if (engineMode !== 'off') {\n const engineResult = await this.tryEngineTraversal({\n engineMode,\n returnIntermediates,\n });\n if (engineResult) return engineResult;\n }\n\n let totalReads = 0;\n let truncated = false;\n // Track (uid, reader) pairs to support context carry-forward across hops.\n // When a hop crosses into a subgraph, the resulting UIDs carry the subgraph\n // reader so subsequent hops without targetGraph stay in that subgraph.\n let sources: Array<{ uid: string; reader: GraphReader }> = [\n { uid: this.startUid, reader: this.reader },\n ];\n const hopResults: HopResult[] = [];\n\n for (let depth = 0; depth < this.hops.length; depth++) {\n const hop = this.hops[depth];\n\n if (sources.length === 0) {\n hopResults.push({\n axbType: hop.axbType,\n depth,\n edges: [],\n sourceCount: 0,\n truncated: false,\n });\n continue;\n }\n\n const hopEdges: Array<{ edge: StoredGraphRecord; reader: GraphReader }> = [];\n const sourceCount = sources.length;\n let hopTruncated = false;\n\n // Resolve targetGraph for this hop:\n // 1. Explicit on the hop definition takes precedence\n // 2. Otherwise check the registry for the axbType\n const resolvedTargetGraph = this.resolveTargetGraph(hop);\n const direction = hop.direction ?? 'forward';\n const isCrossGraph = direction === 'forward' && !!resolvedTargetGraph;\n\n // Fast path: server-side fan-out via `expand()` when the reader supports\n // `query.join`. Eligibility:\n // 1. Not a cross-graph hop — each cross-graph source resolves to its\n // own subgraph reader, which can't be batched into one statement.\n // 2. All sources share the same reader. Mixed readers happen only\n // after a previous cross-graph carry-forward; for the typical\n // single-graph or fully-routed-to-one-DO case, this is true.\n // 3. The shared reader's backend declares `query.join`.\n //\n // Budget accounting: one `expand()` call counts as ONE read against\n // `maxReads`, regardless of source-set size. That reflects reality\n // (1 server round trip = 1 read) and is the entire point of the\n // capability — a 50-source hop collapses 50 round trips into 1.\n // Callers who expect \"1 read per source\" semantics from `maxReads`\n // will see traversals reach further than they did with the per-source\n // loop; this is an improvement, not a regression.\n // Fast-path eligibility check (3): sources share a reader. Mixed-reader\n // sources happen only after a cross-graph carry-forward (hop N had a\n // `targetGraph`, fanning each source UID into its own subgraph reader).\n // The empty-sources branch is already handled by the `if (sources.length === 0)`\n // continue earlier in the loop, so `sources` is non-empty here.\n const sharedReader = sources.every((s) => s.reader === sources[0].reader)\n ? sources[0].reader\n : null;\n const canFastPath = !isCrossGraph && sharedReader && readerSupportsExpand(sharedReader);\n\n if (canFastPath && sharedReader) {\n if (totalReads >= maxReads) {\n hopTruncated = true;\n } else {\n totalReads++;\n const limit = hop.limit ?? DEFAULT_LIMIT;\n const expandParams: ExpandParams = {\n sources: sources.map((s) => s.uid),\n axbType: hop.axbType,\n direction,\n };\n if (hop.aType) expandParams.aType = hop.aType;\n if (hop.bType) expandParams.bType = hop.bType;\n if (hop.orderBy) expandParams.orderBy = hop.orderBy;\n // With a hop-level `filter`, we can't apply `limitPerSource` at the\n // SQL layer — the filter is a JS predicate that runs after rows\n // come back. Pass undefined to fetch all matching edges, filter,\n // then enforce per-source limit in JS below.\n if (!hop.filter) {\n expandParams.limitPerSource = limit;\n }\n const result = await (\n sharedReader as GraphClient & {\n expand(p: ExpandParams): Promise<{ edges: StoredGraphRecord[] }>;\n }\n ).expand(expandParams);\n let edges = result.edges;\n if (hop.filter) {\n edges = edges.filter(hop.filter);\n // Enforce per-source post-filter limit. Without this, a source\n // whose post-filter edge count exceeds `limit` would carry\n // through more next-hop sources than the user requested.\n const counts = new Map<string, number>();\n const kept: StoredGraphRecord[] = [];\n for (const e of edges) {\n const sourceUid = direction === 'forward' ? e.aUid : e.bUid;\n const c = counts.get(sourceUid) ?? 0;\n if (c < limit) {\n counts.set(sourceUid, c + 1);\n kept.push(e);\n }\n }\n edges = kept;\n }\n for (const edge of edges) {\n hopEdges.push({ edge, reader: sharedReader });\n }\n }\n\n // Skip the per-source task loop — we already filled `hopEdges`.\n const fastEdges = hopEdges.map((h) => h.edge);\n hopResults.push({\n axbType: hop.axbType,\n depth,\n edges: returnIntermediates ? [...fastEdges] : fastEdges,\n sourceCount,\n truncated: hopTruncated,\n });\n if (hopTruncated) truncated = true;\n\n // Build next sources, same dedup logic as the slow path.\n const seen = new Map<string, GraphReader>();\n for (const { edge, reader: edgeReader } of hopEdges) {\n const nextUid = direction === 'forward' ? edge.bUid : edge.aUid;\n if (!seen.has(nextUid)) seen.set(nextUid, edgeReader);\n }\n sources = [...seen.entries()].map(([uid, reader]) => ({ uid, reader }));\n continue;\n }\n\n // Slow path (per-source loop): cross-graph hops, mixed-reader sources,\n // or backends without `query.join`.\n const tasks = sources.map(({ uid, reader: sourceReader }) => async () => {\n if (totalReads >= maxReads) {\n hopTruncated = true;\n return;\n }\n\n await semaphore.acquire();\n try {\n if (totalReads >= maxReads) {\n hopTruncated = true;\n return;\n }\n\n totalReads++;\n\n const params: FindEdgesParams = { axbType: hop.axbType };\n\n if (direction === 'forward') {\n params.aUid = uid;\n if (hop.bType) params.bType = hop.bType;\n } else {\n params.bUid = uid;\n if (hop.aType) params.aType = hop.aType;\n }\n\n if (direction === 'forward' && hop.aType) {\n params.aType = hop.aType;\n }\n if (direction === 'reverse' && hop.bType) {\n params.bType = hop.bType;\n }\n\n if (hop.orderBy) params.orderBy = hop.orderBy;\n\n const limit = hop.limit ?? DEFAULT_LIMIT;\n if (hop.filter) {\n params.limit = 0;\n } else {\n params.limit = limit;\n }\n\n // Choose the reader for this hop:\n // - Cross-graph hop: create a subgraph reader from the ROOT client\n // (targetGraph is always relative to root)\n // - No cross-graph: use the carried-forward reader from previous hop\n // (context tracking — stay in whatever subgraph we're already in)\n let hopReader: GraphReader;\n let nextReader: GraphReader;\n if (isCrossGraph) {\n if (isGraphClient(this.reader)) {\n hopReader = this.reader.subgraph(uid, resolvedTargetGraph!);\n nextReader = hopReader;\n } else {\n hopReader = sourceReader;\n nextReader = sourceReader;\n if (!_crossGraphWarned) {\n _crossGraphWarned = true;\n console.warn(\n `[firegraph] Traversal hop \"${hop.axbType}\" has targetGraph \"${resolvedTargetGraph}\" ` +\n 'but the reader does not support subgraph(). Cross-graph hop will query the current ' +\n 'collection instead. Pass a GraphClient to createTraversal() to enable cross-graph traversal.',\n );\n }\n }\n } else {\n // No targetGraph — carry forward context from previous hop\n hopReader = sourceReader;\n nextReader = sourceReader;\n }\n\n let edges = await hopReader.findEdges(params);\n\n if (hop.filter) {\n edges = edges.filter(hop.filter);\n edges = edges.slice(0, limit);\n }\n\n for (const edge of edges) {\n hopEdges.push({ edge, reader: nextReader });\n }\n } finally {\n semaphore.release();\n }\n });\n\n await Promise.all(tasks.map((task) => task()));\n\n const edges = hopEdges.map((h) => h.edge);\n\n hopResults.push({\n axbType: hop.axbType,\n depth,\n edges: returnIntermediates ? [...edges] : edges,\n sourceCount,\n truncated: hopTruncated,\n });\n\n if (hopTruncated) {\n truncated = true;\n }\n\n // Build next sources with deduplication by UID.\n // When the same UID appears from multiple source readers, the first one wins.\n const seen = new Map<string, GraphReader>();\n for (const { edge, reader: edgeReader } of hopEdges) {\n const nextUid = direction === 'forward' ? edge.bUid : edge.aUid;\n if (!seen.has(nextUid)) {\n seen.set(nextUid, edgeReader);\n }\n }\n sources = [...seen.entries()].map(([uid, reader]) => ({ uid, reader }));\n }\n\n const lastHop = hopResults[hopResults.length - 1];\n\n return {\n nodes: lastHop.edges,\n hops: hopResults,\n totalReads,\n truncated,\n };\n }\n\n /**\n * Try to dispatch the entire hop chain as one engine-traversal call.\n * Returns a `TraversalResult` on success, or `undefined` if the spec is\n * ineligible and the caller should fall through to the per-hop loop.\n *\n * `'force'` mode throws on any ineligibility instead of returning\n * `undefined` — the caller intentionally opted out of fallback.\n */\n private async tryEngineTraversal(args: {\n engineMode: 'auto' | 'force';\n returnIntermediates: boolean;\n }): Promise<TraversalResult | undefined> {\n const { engineMode, returnIntermediates } = args;\n\n const refuse = (reason: string): TraversalResult | undefined => {\n if (engineMode === 'force') {\n throw new FiregraphError(`engineTraversal: 'force' but ${reason}`, 'UNSUPPORTED_OPERATION');\n }\n return undefined;\n };\n\n if (!readerSupportsEngineTraversal(this.reader)) {\n return refuse('reader does not declare traversal.serverSide capability');\n }\n const client = this.reader;\n\n // Per-hop eligibility — JS filters and cross-graph hops both prevent\n // engine compilation. Walk the full chain so the failure reason can\n // point at the offending hop.\n const engineHops: EngineHopSpec[] = [];\n for (let i = 0; i < this.hops.length; i++) {\n const hop = this.hops[i];\n if (hop.filter) {\n return refuse(`hop ${i} (${hop.axbType}) carries a JS filter callback`);\n }\n const targetGraph = this.resolveTargetGraph(hop);\n const direction = hop.direction ?? 'forward';\n if (targetGraph) {\n return refuse(`hop ${i} (${hop.axbType}) is cross-graph (targetGraph=${targetGraph})`);\n }\n const limit = hop.limit ?? DEFAULT_LIMIT;\n const engineHop: EngineHopSpec = {\n axbType: hop.axbType,\n direction,\n limitPerSource: limit,\n };\n if (hop.aType) engineHop.aType = hop.aType;\n if (hop.bType) engineHop.bType = hop.bType;\n if (hop.orderBy) engineHop.orderBy = hop.orderBy;\n engineHops.push(engineHop);\n }\n\n const params: EngineTraversalParams = {\n sources: [this.startUid],\n hops: engineHops,\n };\n\n // Compile-side validation (depth, limits, response-size budget) lives\n // in `firestore-traverse-compiler.ts`. We invoke it from the traversal\n // layer (rather than relying on the executor to throw) so 'auto' can\n // silently fall back without ever entering the SDK.\n const compiled = compileEngineTraversal(params);\n if (!compiled.eligible) {\n return refuse(compiled.reason);\n }\n\n let engineResult: EngineTraversalResult;\n try {\n engineResult = await client.runEngineTraversal(params);\n } catch (err) {\n if (engineMode === 'force') throw err;\n return undefined;\n }\n\n // Translate `EngineTraversalResult` into `TraversalResult` (`HopResult[]`).\n // Truncation is detected per-hop: if the returned edge count equals the\n // limitPerSource enforced in the pipeline, the server hit its cap and\n // there may be more edges. This is conservative — for depth-1+ hops with\n // multiple parents, deduplication may reduce the count below limitPerSource\n // per-parent while the aggregate still triggers the check.\n const hopResults: HopResult[] = [];\n for (let i = 0; i < this.hops.length; i++) {\n const definedHop = this.hops[i];\n const engineHopResult = engineResult.hops[i] ?? { edges: [], sourceCount: 0 };\n const edges = engineHopResult.edges;\n const hopTruncated = edges.length >= engineHops[i].limitPerSource;\n hopResults.push({\n axbType: definedHop.axbType,\n depth: i,\n edges: returnIntermediates ? [...edges] : edges,\n sourceCount: engineHopResult.sourceCount,\n truncated: hopTruncated,\n });\n }\n\n const lastHop = hopResults[hopResults.length - 1];\n return {\n nodes: lastHop.edges,\n hops: hopResults,\n // One server-side round trip — same accounting as the `expand()`\n // fast path. The tree response can carry up to `estimatedReads`\n // docs total, but the budget is in round trips, not docs.\n totalReads: 1,\n truncated: hopResults.some((h) => h.truncated),\n };\n }\n\n /**\n * Resolve the targetGraph for a hop. Priority:\n * 1. Explicit `hop.targetGraph` (user override)\n * 2. Registry `targetGraph` for the axbType (if registry available)\n * 3. undefined (no cross-graph)\n */\n private resolveTargetGraph(hop: HopDefinition): string | undefined {\n if (hop.targetGraph) return hop.targetGraph;\n\n if (this.registry) {\n const entries = this.registry.lookupByAxbType(hop.axbType);\n // All entries for the same axbType should share targetGraph; use the first non-undefined\n for (const entry of entries) {\n if (entry.targetGraph) return entry.targetGraph;\n }\n }\n\n return undefined;\n }\n}\n\n/** @internal Reset the one-time cross-graph warning flag (for testing). */\nexport function _resetCrossGraphWarning(): void {\n _crossGraphWarned = false;\n}\n\n/**\n * Create a traversal builder for multi-hop graph traversal.\n *\n * Accepts either a `GraphReader` (backwards compatible) or a `GraphClient`.\n * When a `GraphClient` is provided, cross-graph traversal via `targetGraph`\n * is supported — the traversal can follow edges into subgraphs.\n *\n * @param reader - A `GraphClient` or `GraphReader` to execute queries against\n * @param startUid - UID of the starting node\n * @param registry - Optional registry for automatic `targetGraph` resolution\n */\nexport function createTraversal(\n reader: GraphClient | GraphReader,\n startUid: string,\n registry?: GraphRegistry,\n): TraversalBuilder {\n return new TraversalBuilderImpl(reader, startUid, registry);\n}\n","/**\n * Model Views — framework-agnostic view definitions for graph entities.\n *\n * Projects define Web Components that render entity data in purpose-driven\n * ways. Each view class declares a static `viewName`, and receives the\n * entity's `data` payload via a `data` property setter.\n *\n * @example\n * ```ts\n * import { defineViews } from 'firegraph';\n *\n * class UserCard extends HTMLElement {\n * static viewName = 'card';\n * static description = 'Compact user card';\n * private _data: Record<string, unknown> = {};\n * set data(v: Record<string, unknown>) { this._data = v; this.render(); }\n * connectedCallback() { this.render(); }\n * private render() {\n * this.innerHTML = `<strong>${this._data.displayName ?? ''}</strong>`;\n * }\n * }\n *\n * export default defineViews({\n * nodes: { user: { views: [UserCard] } },\n * });\n * ```\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A Web Component class used as a view. The class must have a static\n * `viewName` and must be constructable. It will be registered as a custom\n * element via `customElements.define()` in browser environments.\n *\n * Note: this interface avoids referencing `HTMLElement` directly so the\n * library can compile without DOM lib types. Consumer code (which has DOM)\n * will satisfy this constraint naturally.\n */\nexport interface ViewComponentClass {\n new (...args: any[]): { data: Record<string, unknown> };\n /** Short identifier for this view (e.g. 'card', 'profile'). */\n viewName: string;\n /** Optional human-readable description. */\n description?: string;\n}\n\n/** Configuration for all views of a single entity type. */\nexport interface EntityViewConfig {\n /** View component classes to register. */\n views: ViewComponentClass[];\n /**\n * Optional sample data for the gallery. A single object matching\n * the entity's JSON Schema — shared across all views.\n */\n sampleData?: Record<string, unknown>;\n}\n\n/** Input shape accepted by `defineViews()`. */\nexport interface ViewRegistryInput {\n /** Node views keyed by aType (e.g. 'user', 'tour'). */\n nodes?: Record<string, EntityViewConfig>;\n /** Edge views keyed by axbType (e.g. 'hasDeparture'). */\n edges?: Record<string, EntityViewConfig>;\n}\n\n/** Serialisable metadata for a single view. */\nexport interface ViewMeta {\n /** Custom element tag name (e.g. 'fg-user-card'). */\n tagName: string;\n /** Short identifier matching the component's static viewName. */\n viewName: string;\n /** Optional human-readable description. */\n description?: string;\n}\n\n/** Serialisable metadata for all views of a single entity type. */\nexport interface EntityViewMeta {\n views: ViewMeta[];\n sampleData?: Record<string, unknown>;\n}\n\n/** The resolved view registry returned by `defineViews()`. */\nexport interface ViewRegistry {\n nodes: Record<string, EntityViewMeta>;\n edges: Record<string, EntityViewMeta>;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Sanitise a string for use as part of a custom element tag name. */\nfunction sanitizeTagPart(s: string): string {\n return s\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n}\n\n/** Minimal interface for CustomElementRegistry (avoids depending on DOM lib). */\ninterface CustomElementRegistryLike {\n get(name: string): unknown;\n define(name: string, constructor: unknown): void;\n}\n\n/**\n * Try to access the browser's `customElements` registry.\n * Returns `null` in Node.js or environments without Web Components support.\n */\nfunction getCustomElements(): CustomElementRegistryLike | null {\n const g = globalThis as any;\n if (g.customElements && typeof g.customElements.define === 'function') {\n return g.customElements as CustomElementRegistryLike;\n }\n return null;\n}\n\n/**\n * Wrap a view class so that errors in connectedCallback, disconnectedCallback,\n * and the data setter are caught and logged rather than crashing the page.\n * Shows an inline error message when the view fails to render.\n */\nfunction resilientView(ViewClass: ViewComponentClass, tagName: string): ViewComponentClass {\n const g = globalThis as any;\n if (!g.HTMLElement) return ViewClass; // Node.js — no wrapping needed\n\n const Wrapped = class extends (ViewClass as unknown as new (...args: any[]) => any) {\n connectedCallback() {\n try {\n super.connectedCallback?.();\n } catch (err) {\n console.warn(`[firegraph] <${tagName}> connectedCallback error:`, err);\n this._showError(err);\n }\n }\n\n disconnectedCallback() {\n try {\n super.disconnectedCallback?.();\n } catch (err) {\n console.warn(`[firegraph] <${tagName}> disconnectedCallback error:`, err);\n }\n }\n\n set data(v: Record<string, unknown>) {\n try {\n super.data = v;\n } catch (err) {\n console.warn(`[firegraph] <${tagName}> data setter error:`, err);\n this._showError(err);\n }\n }\n\n get data(): Record<string, unknown> {\n try {\n return super.data;\n } catch {\n return {};\n }\n }\n\n _showError(err: unknown) {\n try {\n this.innerHTML =\n `<div style=\"padding:6px;color:#f87171;font-size:11px;font-family:monospace;\">` +\n `View error in <${tagName}>: ${err instanceof Error ? err.message : String(err)}</div>`;\n } catch {\n /* last resort — don't throw from error handler */\n }\n }\n };\n\n // Preserve static metadata\n (Wrapped as unknown as ViewComponentClass).viewName = ViewClass.viewName;\n (Wrapped as unknown as ViewComponentClass).description = ViewClass.description;\n\n return Wrapped as unknown as ViewComponentClass;\n}\n\n// ---------------------------------------------------------------------------\n// defineViews()\n// ---------------------------------------------------------------------------\n\n/**\n * Build a `ViewRegistry` from component classes.\n *\n * In the browser the components are registered as custom elements with\n * deterministic tag names (`fg-{entityType}-{viewName}`). On the server\n * (Node.js) only metadata is returned — no custom element registration.\n */\nexport function defineViews(input: ViewRegistryInput): ViewRegistry {\n const nodes: Record<string, EntityViewMeta> = {};\n const edges: Record<string, EntityViewMeta> = {};\n const registry = getCustomElements();\n\n // --- nodes ---\n for (const [entityType, config] of Object.entries(input.nodes ?? {})) {\n const viewMetas: ViewMeta[] = [];\n for (const ViewClass of config.views) {\n const tagName = `fg-${sanitizeTagPart(entityType)}-${sanitizeTagPart(ViewClass.viewName)}`;\n viewMetas.push({\n tagName,\n viewName: ViewClass.viewName,\n description: ViewClass.description,\n });\n if (registry && !registry.get(tagName)) {\n registry.define(tagName, resilientView(ViewClass, tagName));\n }\n }\n nodes[entityType] = {\n views: viewMetas,\n sampleData: config.sampleData,\n };\n }\n\n // --- edges ---\n for (const [axbType, config] of Object.entries(input.edges ?? {})) {\n const viewMetas: ViewMeta[] = [];\n for (const ViewClass of config.views) {\n const tagName = `fg-edge-${sanitizeTagPart(axbType)}-${sanitizeTagPart(ViewClass.viewName)}`;\n viewMetas.push({\n tagName,\n viewName: ViewClass.viewName,\n description: ViewClass.description,\n });\n if (registry && !registry.get(tagName)) {\n registry.define(tagName, resilientView(ViewClass, tagName));\n }\n }\n edges[axbType] = {\n views: viewMetas,\n sampleData: config.sampleData,\n };\n }\n\n return { nodes, edges };\n}\n"],"mappings":"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","names":["resolve","edges","seen"]}
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/cross-graph.ts","../src/discover.ts","../src/indexes.ts","../src/traverse.ts","../src/views.ts"],"sourcesContent":["/**\n * Firegraph Configuration — project-level config file support.\n *\n * Projects create a `firegraph.config.ts` (or `.js`/`.mjs`) in their root:\n *\n * @example\n * ```ts\n * import { defineConfig } from 'firegraph';\n *\n * export default defineConfig({\n * entities: './entities',\n * project: 'my-project',\n * collection: 'graph',\n * });\n * ```\n */\n\nimport type { DynamicRegistryConfig, QueryMode } from './types.js';\n\n// ---------------------------------------------------------------------------\n// View Resolution Types\n// ---------------------------------------------------------------------------\n\n/** Display contexts where views can appear. */\nexport type ViewContext = 'listing' | 'detail' | 'inline';\n\n/** View resolution configuration for a single entity type. */\nexport interface ViewResolverConfig {\n /** Default view name (e.g. 'card'). Falls back to 'json' if unset. */\n default?: string;\n /** View to use in NodeBrowser listing rows. */\n listing?: string;\n /** View to use on the NodeDetail page. */\n detail?: string;\n /** View to use for inline/embedded previews (edge rows, traversal). */\n inline?: string;\n}\n\n/** Declarative view defaults, keyed by entity type. */\nexport interface ViewDefaultsConfig {\n /** Node view defaults keyed by aType (e.g. 'user', 'task'). */\n nodes?: Record<string, ViewResolverConfig>;\n /** Edge view defaults keyed by axbType (e.g. 'hasDeparture'). */\n edges?: Record<string, ViewResolverConfig>;\n}\n\n// ---------------------------------------------------------------------------\n// Config Shape\n// ---------------------------------------------------------------------------\n\n/** Project-level firegraph configuration. */\nexport interface FiregraphConfig {\n /** Path to entities directory (per-entity folder convention). */\n entities?: string;\n /** GCP project ID. */\n project?: string;\n /** Firestore collection path (default: 'graph'). */\n collection?: string;\n /** Firestore emulator address (e.g. '127.0.0.1:8080'). */\n emulator?: string;\n /**\n * Query execution backend.\n *\n * - `'pipeline'` (default) — Uses Firestore Pipeline API. Requires Enterprise\n * Firestore. Enables indexless queries on `data.*` fields.\n * - `'standard'` — Uses standard Firestore `.where().get()` queries. Not\n * recommended for production. See README for risk details.\n *\n * When the emulator is active, always falls back to `'standard'`.\n */\n queryMode?: QueryMode;\n\n /**\n * AI chat configuration. Auto-detects `claude` CLI on PATH by default.\n * Set to `false` to disable chat even if claude is available.\n */\n chat?:\n | false\n | {\n /** Claude model to use (default: 'sonnet'). */\n model?: string;\n /** Maximum concurrent claude processes (default: 2). */\n maxConcurrency?: number;\n };\n\n /** Editor-specific settings. */\n editor?: {\n /** Server port (default: 3883). */\n port?: number;\n /** Force read-only mode. */\n readonly?: boolean;\n };\n\n /** Declarative view defaults per entity type (overrides per-entity meta.json). */\n viewDefaults?: ViewDefaultsConfig;\n\n /**\n * Dynamic registry mode. When set, the editor loads type definitions\n * from Firestore meta-nodes in addition to filesystem entities.\n * Filesystem types take precedence on name conflicts.\n */\n registryMode?: DynamicRegistryConfig;\n}\n\n// ---------------------------------------------------------------------------\n// defineConfig()\n// ---------------------------------------------------------------------------\n\n/**\n * Identity function providing type-checking and autocomplete for config files.\n *\n * @example\n * ```ts\n * import { defineConfig } from 'firegraph';\n * export default defineConfig({ entities: './entities' });\n * ```\n */\nexport function defineConfig(config: FiregraphConfig): FiregraphConfig {\n return config;\n}\n\n// ---------------------------------------------------------------------------\n// View Resolution (pure — works client-side and server-side)\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve which view to show for a given entity.\n *\n * 1. If `context` is provided and a context-specific default exists, use it.\n * 2. Falls back to `resolverConfig.default`.\n * 3. Ultimate fallback: `'json'`.\n *\n * Only returns view names that exist in `availableViewNames`.\n */\nexport function resolveView(\n resolverConfig: ViewResolverConfig | undefined,\n availableViewNames: string[],\n context?: ViewContext,\n): string {\n if (!resolverConfig) return 'json';\n\n const available = new Set(availableViewNames);\n\n if (context) {\n const contextDefault = resolverConfig[context];\n if (contextDefault && available.has(contextDefault)) {\n return contextDefault;\n }\n }\n\n if (resolverConfig.default && available.has(resolverConfig.default)) {\n return resolverConfig.default;\n }\n\n return 'json';\n}\n","/**\n * Cross-graph edge resolution utilities.\n *\n * Provides path-scanning resolution for determining whether an edge's source\n * (aUid) is an ancestor node by checking if the UID appears in the Firestore\n * collection path.\n *\n * Firestore paths have a rigid alternating structure:\n * collection / docId / collection / docId / collection\n *\n * Given a path like `graph/A/workspace/B/context`, segments at even indices\n * are collection names and odd indices are document IDs. When we find a UID\n * at an odd index, the collection containing that document is the path up to\n * (and including) the preceding even-index segment.\n */\n\n/**\n * Parse a Firestore collection path and determine the collection path\n * where a given UID's document lives, if that UID is an ancestor in the path.\n *\n * @param collectionPath - The full Firestore collection path of the current client\n * @param uid - The UID to search for in the path\n * @returns The collection path containing the UID, or `null` if not found in the path\n *\n * @example\n * ```ts\n * // Path: graph/A/workspace/B/context\n * resolveAncestorCollection('graph/A/workspace/B/context', 'A')\n * // → 'graph'\n *\n * resolveAncestorCollection('graph/A/workspace/B/context', 'B')\n * // → 'graph/A/workspace'\n *\n * resolveAncestorCollection('graph/A/workspace/B/context', 'unknown')\n * // → null\n * ```\n */\nexport function resolveAncestorCollection(collectionPath: string, uid: string): string | null {\n const segments = collectionPath.split('/');\n\n // Walk odd-indexed segments (document IDs in Firestore's alternating path structure)\n for (let i = 1; i < segments.length; i += 2) {\n if (segments[i] === uid) {\n // The collection containing this doc is everything up to index i-1\n return segments.slice(0, i).join('/');\n }\n }\n\n return null;\n}\n\n/**\n * Check whether a UID belongs to an ancestor node by scanning the collection path.\n *\n * @param collectionPath - The full Firestore collection path of the current client\n * @param uid - The UID to check\n * @returns `true` if the UID appears as a document segment in the path\n */\nexport function isAncestorUid(collectionPath: string, uid: string): boolean {\n return resolveAncestorCollection(collectionPath, uid) !== null;\n}\n","/**\n * Entity Discovery — convention-based auto-discovery of entities from\n * a per-entity folder structure.\n *\n * Scans `entitiesDir/nodes/` and `entitiesDir/edges/` subdirectories.\n * Each subfolder is treated as an entity type.\n *\n * Schema files can be either `schema.json` (plain JSON Schema) or\n * `schema.ts` / `schema.js` (a module whose default export is a JSON Schema\n * object). When both exist, the TS/JS file takes precedence so that authors\n * can compose schemas programmatically while keeping a JSON fallback.\n *\n * @example\n * ```\n * entities/\n * nodes/\n * task/\n * schema.json | schema.ts (required — one or both)\n * views.ts (optional)\n * sample.json (optional)\n * meta.json (optional)\n * edges/\n * hasStep/\n * schema.json | schema.ts (required — one or both)\n * edge.json (required — topology)\n * views.ts (optional)\n * sample.json (optional)\n * meta.json (optional)\n * ```\n */\n\nimport { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { join, resolve } from 'node:path';\n\nimport type * as jitiNS from 'jiti';\n\nimport type { ViewResolverConfig } from './config.js';\nimport { FiregraphError } from './errors.js';\nimport type {\n DiscoveredEntity,\n DiscoveryResult,\n EdgeTopology,\n IndexSpec,\n MigrationStep,\n MigrationWriteBack,\n} from './types.js';\n\nexport class DiscoveryError extends FiregraphError {\n constructor(message: string) {\n super(message, 'DISCOVERY_ERROR');\n this.name = 'DiscoveryError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// JSON parsing helpers\n// ---------------------------------------------------------------------------\n\nfunction readJson(filePath: string): unknown {\n try {\n const raw = readFileSync(filePath, 'utf-8');\n return JSON.parse(raw);\n } catch (err: unknown) {\n const msg =\n err instanceof SyntaxError\n ? `Invalid JSON in ${filePath}: ${err.message}`\n : `Cannot read ${filePath}: ${(err as Error).message}`;\n throw new DiscoveryError(msg);\n }\n}\n\nfunction readJsonIfExists(filePath: string): unknown | undefined {\n if (!existsSync(filePath)) return undefined;\n return readJson(filePath);\n}\n\n// ---------------------------------------------------------------------------\n// Schema file loading (JSON or TS/JS via jiti)\n// ---------------------------------------------------------------------------\n\nconst SCHEMA_SCRIPT_EXTENSIONS = ['.ts', '.js', '.mts', '.mjs'];\n\n/**\n * Attempt to load a schema from a TS/JS module (default export) or fall back\n * to schema.json. Returns the parsed schema object or throws.\n */\nfunction loadSchema(dir: string, entityLabel: string): object {\n // Prefer TS/JS schema — allows programmatic composition & shared definitions\n for (const ext of SCHEMA_SCRIPT_EXTENSIONS) {\n const candidate = join(dir, `schema${ext}`);\n if (existsSync(candidate)) {\n return loadSchemaModule(candidate, entityLabel);\n }\n }\n\n // Fall back to schema.json\n const jsonPath = join(dir, 'schema.json');\n if (existsSync(jsonPath)) {\n return readJson(jsonPath) as object;\n }\n\n throw new DiscoveryError(\n `Missing schema for ${entityLabel} in ${dir}. ` +\n 'Provide a schema.ts (or .js/.mts/.mjs) or schema.json file.',\n );\n}\n\nlet _jiti: ((id: string) => unknown) | undefined;\n\nfunction getJiti(): (id: string) => unknown {\n if (!_jiti) {\n const base = typeof __filename !== 'undefined' ? __filename : import.meta.url;\n const esmRequire = createRequire(base);\n const { createJiti } = esmRequire('jiti') as typeof jitiNS;\n _jiti = createJiti(base, { interopDefault: true });\n }\n return _jiti;\n}\n\nfunction loadSchemaModule(filePath: string, entityLabel: string): object {\n try {\n const jiti = getJiti();\n const mod = jiti(filePath) as { default?: unknown } | unknown;\n const schema =\n mod && typeof mod === 'object' && 'default' in mod\n ? (mod as { default: unknown }).default\n : mod;\n\n if (!schema || typeof schema !== 'object') {\n throw new DiscoveryError(\n `Schema file ${filePath} for ${entityLabel} must default-export a JSON Schema object.`,\n );\n }\n return schema as object;\n } catch (err: unknown) {\n if (err instanceof DiscoveryError) throw err;\n throw new DiscoveryError(\n `Failed to load schema module ${filePath} for ${entityLabel}: ${(err as Error).message}`,\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// View file detection\n// ---------------------------------------------------------------------------\n\nconst VIEW_EXTENSIONS = ['.ts', '.js', '.mts', '.mjs'];\n\nfunction findViewsFile(dir: string): string | undefined {\n for (const ext of VIEW_EXTENSIONS) {\n const candidate = join(dir, `views${ext}`);\n if (existsSync(candidate)) return candidate;\n }\n return undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Migration file detection & loading\n// ---------------------------------------------------------------------------\n\nconst MIGRATION_EXTENSIONS = ['.ts', '.js', '.mts', '.mjs'];\n\nfunction findMigrationsFile(dir: string): string | undefined {\n for (const ext of MIGRATION_EXTENSIONS) {\n const candidate = join(dir, `migrations${ext}`);\n if (existsSync(candidate)) return candidate;\n }\n return undefined;\n}\n\nfunction loadMigrations(filePath: string, entityLabel: string): MigrationStep[] {\n try {\n const jiti = getJiti();\n const mod = jiti(filePath) as { default?: unknown } | unknown;\n const migrations =\n mod && typeof mod === 'object' && 'default' in mod\n ? (mod as { default: unknown }).default\n : mod;\n\n if (!Array.isArray(migrations)) {\n throw new DiscoveryError(\n `Migrations file ${filePath} for ${entityLabel} must default-export an array of MigrationStep.`,\n );\n }\n return migrations as MigrationStep[];\n } catch (err: unknown) {\n if (err instanceof DiscoveryError) throw err;\n throw new DiscoveryError(\n `Failed to load migrations ${filePath} for ${entityLabel}: ${(err as Error).message}`,\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Entity loaders\n// ---------------------------------------------------------------------------\n\nfunction loadNodeEntity(dir: string, name: string): DiscoveredEntity {\n const schema = loadSchema(dir, `node type \"${name}\"`);\n const meta = readJsonIfExists(join(dir, 'meta.json')) as\n | {\n description?: string;\n titleField?: string;\n subtitleField?: string;\n viewDefaults?: ViewResolverConfig;\n allowedIn?: string[];\n migrationWriteBack?: MigrationWriteBack;\n indexes?: IndexSpec[];\n }\n | undefined;\n const sampleData = readJsonIfExists(join(dir, 'sample.json')) as\n | Record<string, unknown>\n | undefined;\n const viewsPath = findViewsFile(dir);\n const migrationsPath = findMigrationsFile(dir);\n const migrations = migrationsPath\n ? loadMigrations(migrationsPath, `node type \"${name}\"`)\n : undefined;\n\n return {\n kind: 'node',\n name,\n schema,\n description: meta?.description,\n titleField: meta?.titleField,\n subtitleField: meta?.subtitleField,\n viewDefaults: meta?.viewDefaults,\n viewsPath,\n sampleData,\n allowedIn: meta?.allowedIn,\n migrations,\n migrationWriteBack: meta?.migrationWriteBack,\n indexes: meta?.indexes,\n };\n}\n\nfunction loadEdgeEntity(dir: string, name: string): DiscoveredEntity {\n const schema = loadSchema(dir, `edge type \"${name}\"`);\n\n const edgePath = join(dir, 'edge.json');\n if (!existsSync(edgePath)) {\n throw new DiscoveryError(\n `Missing edge.json for edge type \"${name}\" in ${dir}. ` +\n 'Edge entities must declare topology (from/to node types).',\n );\n }\n const topology = readJson(edgePath) as EdgeTopology;\n\n // Validate topology shape\n if (!topology.from) {\n throw new DiscoveryError(`edge.json for \"${name}\" is missing required \"from\" field`);\n }\n if (!topology.to) {\n throw new DiscoveryError(`edge.json for \"${name}\" is missing required \"to\" field`);\n }\n\n const meta = readJsonIfExists(join(dir, 'meta.json')) as\n | {\n description?: string;\n titleField?: string;\n subtitleField?: string;\n viewDefaults?: ViewResolverConfig;\n allowedIn?: string[];\n targetGraph?: string;\n migrationWriteBack?: MigrationWriteBack;\n indexes?: IndexSpec[];\n }\n | undefined;\n const sampleData = readJsonIfExists(join(dir, 'sample.json')) as\n | Record<string, unknown>\n | undefined;\n const viewsPath = findViewsFile(dir);\n const migrationsPath = findMigrationsFile(dir);\n const migrations = migrationsPath\n ? loadMigrations(migrationsPath, `edge type \"${name}\"`)\n : undefined;\n\n return {\n kind: 'edge',\n name,\n schema,\n topology,\n description: meta?.description,\n titleField: meta?.titleField,\n subtitleField: meta?.subtitleField,\n viewDefaults: meta?.viewDefaults,\n viewsPath,\n sampleData,\n allowedIn: meta?.allowedIn,\n targetGraph:\n topology.targetGraph ?? (meta as { targetGraph?: string } | undefined)?.targetGraph,\n migrations,\n migrationWriteBack: meta?.migrationWriteBack,\n indexes: meta?.indexes,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Directory scanner\n// ---------------------------------------------------------------------------\n\nfunction getSubdirectories(dir: string): string[] {\n if (!existsSync(dir)) return [];\n return readdirSync(dir, { withFileTypes: true })\n .filter((d) => d.isDirectory())\n .map((d) => d.name);\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport interface DiscoveryWarning {\n code: 'DANGLING_TOPOLOGY_REF';\n message: string;\n}\n\nexport interface DiscoverResult {\n result: DiscoveryResult;\n warnings: DiscoveryWarning[];\n}\n\n/**\n * Scan an entities directory and return all discovered nodes and edges.\n *\n * @param entitiesDir - Path to the entities directory (absolute or relative to cwd)\n * @returns Discovery result with nodes and edges maps, plus any warnings\n */\nexport function discoverEntities(entitiesDir: string): DiscoverResult {\n const absDir = resolve(entitiesDir);\n\n if (!existsSync(absDir) || !statSync(absDir).isDirectory()) {\n throw new DiscoveryError(`Entities directory not found: ${entitiesDir}`);\n }\n\n const nodes = new Map<string, DiscoveredEntity>();\n const edges = new Map<string, DiscoveredEntity>();\n const warnings: DiscoveryWarning[] = [];\n\n // Discover nodes\n const nodesDir = join(absDir, 'nodes');\n for (const name of getSubdirectories(nodesDir)) {\n nodes.set(name, loadNodeEntity(join(nodesDir, name), name));\n }\n\n // Discover edges\n const edgesDir = join(absDir, 'edges');\n for (const name of getSubdirectories(edgesDir)) {\n edges.set(name, loadEdgeEntity(join(edgesDir, name), name));\n }\n\n // Validate topology references\n const nodeNames = new Set(nodes.keys());\n for (const [axbType, entity] of edges) {\n const topology = entity.topology!;\n const fromTypes = Array.isArray(topology.from) ? topology.from : [topology.from];\n const toTypes = Array.isArray(topology.to) ? topology.to : [topology.to];\n\n for (const ref of [...fromTypes, ...toTypes]) {\n if (!nodeNames.has(ref)) {\n warnings.push({\n code: 'DANGLING_TOPOLOGY_REF',\n message: `Edge \"${axbType}\" references node type \"${ref}\" which was not found in the nodes directory`,\n });\n }\n }\n }\n\n return {\n result: { nodes, edges },\n warnings,\n };\n}\n","/**\n * Firestore composite index generator.\n *\n * Translates firegraph's declarative `IndexSpec[]` (core preset plus per-entry\n * registry indexes) into the `firestore.indexes.json` shape consumed by\n * `firebase deploy --only firestore:indexes`.\n *\n * ## What Firestore needs\n *\n * Firestore auto-indexes every top-level field (including `data.*`) for\n * single-field equality queries — we only need to emit *composite* indexes\n * here. That means:\n *\n * 1. Single-field specs are dropped (Firestore already covers them).\n * 2. Composite specs (two or more fields) get one `FirestoreIndex`.\n * 3. Specs with `where` are dropped with a warning — Firestore composite\n * indexes do not support partial predicates.\n * 4. When a registry entry has `targetGraph` set, every composite is also\n * emitted with `queryScope: 'COLLECTION_GROUP'` under the targetGraph\n * name, so `findEdgesGlobal()` queries across subgraphs can hit an\n * index.\n *\n * The SQLite-flavored backends (DO, legacy) consume the same `IndexSpec[]`\n * via `src/internal/sqlite-index-ddl.ts` but emit every spec (single fields\n * included) as `CREATE INDEX` DDL.\n */\n\nimport { DEFAULT_CORE_INDEXES } from './default-indexes.js';\nimport type { DiscoveryResult, IndexFieldSpec, IndexSpec, RegistryEntry } from './types.js';\n\nexport interface FirestoreIndexField {\n fieldPath: string;\n order: 'ASCENDING' | 'DESCENDING';\n}\n\nexport interface FirestoreIndex {\n collectionGroup: string;\n queryScope: 'COLLECTION' | 'COLLECTION_GROUP';\n fields: FirestoreIndexField[];\n}\n\nexport interface FirestoreIndexConfig {\n indexes: FirestoreIndex[];\n fieldOverrides: unknown[];\n}\n\nexport interface GenerateIndexOptions {\n /**\n * Replaces firegraph's built-in core preset. Defaults to\n * `DEFAULT_CORE_INDEXES`. Pass `[]` to disable core indexes entirely.\n */\n coreIndexes?: IndexSpec[];\n /**\n * Registry entries supplying per-triple `indexes`. Entries without\n * `indexes` contribute no composites; entries with `targetGraph` also\n * trigger `COLLECTION_GROUP` mirrors under each distinct targetGraph\n * segment name.\n */\n registryEntries?: ReadonlyArray<RegistryEntry>;\n /**\n * Entity discovery result. Convenience for callers that have a\n * `DiscoveryResult` but not a built registry — treated as if every\n * discovered entity were expanded to its registry entries carrying just\n * `indexes` + `targetGraph`. Mutually usable with `registryEntries`\n * (both are concatenated and deduplicated at the spec level).\n */\n entities?: DiscoveryResult;\n}\n\nfunction normalizeField(f: string | IndexFieldSpec): IndexFieldSpec {\n return typeof f === 'string' ? { path: f, desc: false } : { path: f.path, desc: !!f.desc };\n}\n\nfunction specFingerprint(spec: IndexSpec, scope: string): string {\n const normalized = spec.fields.map(normalizeField);\n return `${scope}::${JSON.stringify(normalized)}`;\n}\n\nfunction toFirestoreFields(spec: IndexSpec): FirestoreIndexField[] {\n return spec.fields.map((f) => {\n const n = normalizeField(f);\n return {\n fieldPath: n.path,\n order: n.desc ? 'DESCENDING' : 'ASCENDING',\n };\n });\n}\n\nlet warnedOnPartialIndex = false;\n\n/**\n * Build a Firestore index configuration from firegraph's declarative index\n * specs. Deduplicates by field list + scope before emitting. Single-field\n * specs are dropped; partial-index specs (`where` set) are dropped with a\n * one-time warning.\n */\nexport function generateIndexConfig(\n collection: string,\n options: GenerateIndexOptions = {},\n): FirestoreIndexConfig {\n const core = options.coreIndexes ?? [...DEFAULT_CORE_INDEXES];\n const fromEntries = (options.registryEntries ?? []).flatMap((e) => {\n if (!e.indexes) return [] as IndexSpec[];\n return e.indexes;\n });\n\n // DiscoveryResult is a pre-registry shape — it doesn't carry `indexes`\n // per triple (those live on registry entries once built). Accept it to\n // keep the CLI ergonomic, but the only thing we can pull from it right\n // now is the set of distinct `targetGraph` values, which belongs to\n // discovery-time topology metadata. Consumers who need per-entity data\n // indexes must go through the registry path.\n const targetGraphNames = new Set<string>();\n for (const entry of options.registryEntries ?? []) {\n if (entry.targetGraph) targetGraphNames.add(entry.targetGraph);\n }\n if (options.entities) {\n for (const [, entity] of options.entities.edges) {\n const tg = entity.targetGraph ?? entity.topology?.targetGraph;\n if (tg) targetGraphNames.add(tg);\n }\n }\n\n const allSpecs = [...core, ...fromEntries];\n const seen = new Set<string>();\n const indexes: FirestoreIndex[] = [];\n\n for (const spec of allSpecs) {\n if (!spec.fields || spec.fields.length < 2) {\n // Single-field: Firestore auto-indexes — nothing to emit.\n continue;\n }\n if (spec.where) {\n if (!warnedOnPartialIndex) {\n warnedOnPartialIndex = true;\n console.warn(\n 'firegraph: IndexSpec.where is ignored by the Firestore generator — ' +\n 'Firestore composite indexes do not support predicates. ' +\n 'The SQLite backends will still honor `where`.',\n );\n }\n continue;\n }\n\n const fields = toFirestoreFields(spec);\n\n const colKey = specFingerprint(spec, `col:${collection}`);\n if (!seen.has(colKey)) {\n seen.add(colKey);\n indexes.push({\n collectionGroup: collection,\n queryScope: 'COLLECTION',\n fields,\n });\n }\n\n // Mirror into every distinct `targetGraph` as a collection group index.\n // `findEdgesGlobal()` runs across all subcollections matching the\n // targetGraph name, and each pattern needs its own CG index.\n for (const tg of targetGraphNames) {\n const cgKey = specFingerprint(spec, `cg:${tg}`);\n if (seen.has(cgKey)) continue;\n seen.add(cgKey);\n indexes.push({\n collectionGroup: tg,\n queryScope: 'COLLECTION_GROUP',\n fields,\n });\n }\n }\n\n return { indexes, fieldOverrides: [] };\n}\n\n/**\n * Internal test hook — reset the one-time partial-index warning flag so\n * tests covering the warn branch can run sequentially without sharing\n * state.\n */\nexport function _resetIndexGenWarningsForTest(): void {\n warnedOnPartialIndex = false;\n}\n","import { FiregraphError, TraversalError } from './errors.js';\nimport { compileEngineTraversal } from './internal/firestore-traverse-compiler.js';\nimport type {\n EngineHopSpec,\n EngineTraversalParams,\n EngineTraversalResult,\n ExpandParams,\n FindEdgesParams,\n GraphClient,\n GraphReader,\n GraphRegistry,\n HopDefinition,\n HopResult,\n StoredGraphRecord,\n TraversalBuilder,\n TraversalOptions,\n TraversalResult,\n} from './types.js';\n\nconst DEFAULT_LIMIT = 10;\nconst DEFAULT_MAX_READS = 100;\nconst DEFAULT_CONCURRENCY = 5;\n\n/** One-time warning flag: emitted when cross-graph hop is silently skipped. */\nlet _crossGraphWarned = false;\n\n/** Type guard to check if a reader is a GraphClient (has subgraph method). */\nfunction isGraphClient(reader: GraphReader): reader is GraphClient {\n return 'subgraph' in reader && typeof (reader as GraphClient).subgraph === 'function';\n}\n\n/**\n * Type guard to detect whether a reader has the `query.join` capability —\n * i.e. the backend supports server-side multi-source fan-out via `expand()`.\n *\n * Branching on this lets us dispatch one `expand()` call per hop instead of\n * one `findEdges()` per source. The savings scale linearly with source-set\n * size; for the common case of a 50-source hop, that's 50 round trips\n * collapsed into 1.\n *\n * Cross-graph hops are explicitly NOT routed through `expand()` even when\n * the cap is present — each source UID resolves to a distinct subgraph\n * reader, which can't be batched into one server-side statement. The\n * traversal driver enforces that boundary directly (see the `isCrossGraph`\n * branch below).\n */\nfunction readerSupportsExpand(reader: GraphReader): reader is GraphClient & {\n expand(params: ExpandParams): Promise<{ edges: StoredGraphRecord[] }>;\n} {\n if (!isGraphClient(reader)) return false;\n const client = reader as GraphClient;\n // `capabilities` lives on the public client surface (see `CoreGraphClient`).\n // The runtime check is required because `expand` exists on every\n // `GraphClientImpl` (the permissive `GraphClient<Capability>` shape) but\n // throws `UNSUPPORTED_OPERATION` when the backend doesn't declare the cap.\n // Reading `capabilities` instead of feeling for the method is the cap-aware\n // dispatch the rest of the codebase uses.\n return (\n 'capabilities' in client &&\n typeof client.capabilities?.has === 'function' &&\n client.capabilities.has('query.join') &&\n typeof (client as { expand?: unknown }).expand === 'function'\n );\n}\n\n/**\n * Type guard mirroring `readerSupportsExpand` but for the `traversal.serverSide`\n * capability. When this returns `true`, the reader can dispatch a multi-hop\n * spec as one nested-Pipeline round trip via `runEngineTraversal()`.\n *\n * Eligibility at the spec level (no cross-graph hops, no JS filter callbacks,\n * `limitPerSource` set on every hop, depth ≤ `MAX_PIPELINE_DEPTH`,\n * response-size product ≤ `maxReads`) is checked separately by\n * `compileEngineTraversal`. This guard only certifies the reader has the\n * method to call.\n */\nfunction readerSupportsEngineTraversal(reader: GraphReader): reader is GraphClient & {\n runEngineTraversal(params: EngineTraversalParams): Promise<EngineTraversalResult>;\n} {\n if (!isGraphClient(reader)) return false;\n const client = reader as GraphClient;\n return (\n 'capabilities' in client &&\n typeof client.capabilities?.has === 'function' &&\n client.capabilities.has('traversal.serverSide') &&\n typeof (client as { runEngineTraversal?: unknown }).runEngineTraversal === 'function'\n );\n}\n\nclass Semaphore {\n private queue: Array<() => void> = [];\n private active = 0;\n\n constructor(private readonly slots: number) {}\n\n async acquire(): Promise<void> {\n if (this.active < this.slots) {\n this.active++;\n return;\n }\n return new Promise<void>((resolve) => {\n this.queue.push(resolve);\n });\n }\n\n release(): void {\n this.active--;\n const next = this.queue.shift();\n if (next) {\n this.active++;\n next();\n }\n }\n}\n\nclass TraversalBuilderImpl implements TraversalBuilder {\n private readonly hops: HopDefinition[] = [];\n\n constructor(\n private readonly reader: GraphReader,\n private readonly startUid: string,\n private readonly registry?: GraphRegistry,\n ) {}\n\n follow(axbType: string, options?: Omit<HopDefinition, 'axbType'>): TraversalBuilder {\n this.hops.push({ axbType, ...options });\n return this;\n }\n\n async run(options?: TraversalOptions): Promise<TraversalResult> {\n if (this.hops.length === 0) {\n throw new TraversalError('Traversal requires at least one follow() hop');\n }\n\n const maxReads = options?.maxReads ?? DEFAULT_MAX_READS;\n const concurrency = options?.concurrency ?? DEFAULT_CONCURRENCY;\n const returnIntermediates = options?.returnIntermediates ?? false;\n const engineMode = options?.engineTraversal ?? 'auto';\n const semaphore = new Semaphore(concurrency);\n\n // Engine-level traversal — try to compile the whole hop chain into one\n // nested-Pipeline round trip. Eligibility (in order of cheap-first):\n //\n // 1. `engineMode !== 'off'` → caller didn't opt out\n // 2. reader declares `traversal.serverSide` → backend has the path\n // 3. no hop carries a JS `filter` callback → can't run JS server-side\n // 4. no hop is cross-graph → distinct collection paths\n // 5. compiler accepts the spec → depth, limits, response size\n //\n // `engineMode === 'force'` flips failures from silent fallback to a\n // thrown `UNSUPPORTED_OPERATION`, which is what tests/benchmarks want.\n // `engineMode === 'auto'` (the default) silently falls back so existing\n // callers see the new fast-path on Enterprise without any code change.\n if (engineMode !== 'off') {\n const engineResult = await this.tryEngineTraversal({\n engineMode,\n returnIntermediates,\n });\n if (engineResult) return engineResult;\n }\n\n let totalReads = 0;\n let truncated = false;\n // Track (uid, reader) pairs to support context carry-forward across hops.\n // When a hop crosses into a subgraph, the resulting UIDs carry the subgraph\n // reader so subsequent hops without targetGraph stay in that subgraph.\n let sources: Array<{ uid: string; reader: GraphReader }> = [\n { uid: this.startUid, reader: this.reader },\n ];\n const hopResults: HopResult[] = [];\n\n for (let depth = 0; depth < this.hops.length; depth++) {\n const hop = this.hops[depth];\n\n if (sources.length === 0) {\n hopResults.push({\n axbType: hop.axbType,\n depth,\n edges: [],\n sourceCount: 0,\n truncated: false,\n });\n continue;\n }\n\n const hopEdges: Array<{ edge: StoredGraphRecord; reader: GraphReader }> = [];\n const sourceCount = sources.length;\n let hopTruncated = false;\n\n // Resolve targetGraph for this hop:\n // 1. Explicit on the hop definition takes precedence\n // 2. Otherwise check the registry for the axbType\n const resolvedTargetGraph = this.resolveTargetGraph(hop);\n const direction = hop.direction ?? 'forward';\n const isCrossGraph = direction === 'forward' && !!resolvedTargetGraph;\n\n // Fast path: server-side fan-out via `expand()` when the reader supports\n // `query.join`. Eligibility:\n // 1. Not a cross-graph hop — each cross-graph source resolves to its\n // own subgraph reader, which can't be batched into one statement.\n // 2. All sources share the same reader. Mixed readers happen only\n // after a previous cross-graph carry-forward; for the typical\n // single-graph or fully-routed-to-one-DO case, this is true.\n // 3. The shared reader's backend declares `query.join`.\n //\n // Budget accounting: one `expand()` call counts as ONE read against\n // `maxReads`, regardless of source-set size. That reflects reality\n // (1 server round trip = 1 read) and is the entire point of the\n // capability — a 50-source hop collapses 50 round trips into 1.\n // Callers who expect \"1 read per source\" semantics from `maxReads`\n // will see traversals reach further than they did with the per-source\n // loop; this is an improvement, not a regression.\n // Fast-path eligibility check (3): sources share a reader. Mixed-reader\n // sources happen only after a cross-graph carry-forward (hop N had a\n // `targetGraph`, fanning each source UID into its own subgraph reader).\n // The empty-sources branch is already handled by the `if (sources.length === 0)`\n // continue earlier in the loop, so `sources` is non-empty here.\n const sharedReader = sources.every((s) => s.reader === sources[0].reader)\n ? sources[0].reader\n : null;\n const canFastPath = !isCrossGraph && sharedReader && readerSupportsExpand(sharedReader);\n\n if (canFastPath && sharedReader) {\n if (totalReads >= maxReads) {\n hopTruncated = true;\n } else {\n totalReads++;\n const limit = hop.limit ?? DEFAULT_LIMIT;\n const expandParams: ExpandParams = {\n sources: sources.map((s) => s.uid),\n axbType: hop.axbType,\n direction,\n };\n if (hop.aType) expandParams.aType = hop.aType;\n if (hop.bType) expandParams.bType = hop.bType;\n if (hop.orderBy) expandParams.orderBy = hop.orderBy;\n // With a hop-level `filter`, we can't apply `limitPerSource` at the\n // SQL layer — the filter is a JS predicate that runs after rows\n // come back. Pass undefined to fetch all matching edges, filter,\n // then enforce per-source limit in JS below.\n if (!hop.filter) {\n expandParams.limitPerSource = limit;\n }\n const result = await (\n sharedReader as GraphClient & {\n expand(p: ExpandParams): Promise<{ edges: StoredGraphRecord[] }>;\n }\n ).expand(expandParams);\n let edges = result.edges;\n if (hop.filter) {\n edges = edges.filter(hop.filter);\n // Enforce per-source post-filter limit. Without this, a source\n // whose post-filter edge count exceeds `limit` would carry\n // through more next-hop sources than the user requested.\n const counts = new Map<string, number>();\n const kept: StoredGraphRecord[] = [];\n for (const e of edges) {\n const sourceUid = direction === 'forward' ? e.aUid : e.bUid;\n const c = counts.get(sourceUid) ?? 0;\n if (c < limit) {\n counts.set(sourceUid, c + 1);\n kept.push(e);\n }\n }\n edges = kept;\n }\n for (const edge of edges) {\n hopEdges.push({ edge, reader: sharedReader });\n }\n }\n\n // Skip the per-source task loop — we already filled `hopEdges`.\n const fastEdges = hopEdges.map((h) => h.edge);\n hopResults.push({\n axbType: hop.axbType,\n depth,\n edges: returnIntermediates ? [...fastEdges] : fastEdges,\n sourceCount,\n truncated: hopTruncated,\n });\n if (hopTruncated) truncated = true;\n\n // Build next sources, same dedup logic as the slow path.\n const seen = new Map<string, GraphReader>();\n for (const { edge, reader: edgeReader } of hopEdges) {\n const nextUid = direction === 'forward' ? edge.bUid : edge.aUid;\n if (!seen.has(nextUid)) seen.set(nextUid, edgeReader);\n }\n sources = [...seen.entries()].map(([uid, reader]) => ({ uid, reader }));\n continue;\n }\n\n // Slow path (per-source loop): cross-graph hops, mixed-reader sources,\n // or backends without `query.join`.\n const tasks = sources.map(({ uid, reader: sourceReader }) => async () => {\n if (totalReads >= maxReads) {\n hopTruncated = true;\n return;\n }\n\n await semaphore.acquire();\n try {\n if (totalReads >= maxReads) {\n hopTruncated = true;\n return;\n }\n\n totalReads++;\n\n const params: FindEdgesParams = { axbType: hop.axbType };\n\n if (direction === 'forward') {\n params.aUid = uid;\n if (hop.bType) params.bType = hop.bType;\n } else {\n params.bUid = uid;\n if (hop.aType) params.aType = hop.aType;\n }\n\n if (direction === 'forward' && hop.aType) {\n params.aType = hop.aType;\n }\n if (direction === 'reverse' && hop.bType) {\n params.bType = hop.bType;\n }\n\n if (hop.orderBy) params.orderBy = hop.orderBy;\n\n const limit = hop.limit ?? DEFAULT_LIMIT;\n if (hop.filter) {\n params.limit = 0;\n } else {\n params.limit = limit;\n }\n\n // Choose the reader for this hop:\n // - Cross-graph hop: create a subgraph reader from the ROOT client\n // (targetGraph is always relative to root)\n // - No cross-graph: use the carried-forward reader from previous hop\n // (context tracking — stay in whatever subgraph we're already in)\n let hopReader: GraphReader;\n let nextReader: GraphReader;\n if (isCrossGraph) {\n if (isGraphClient(this.reader)) {\n hopReader = this.reader.subgraph(uid, resolvedTargetGraph!);\n nextReader = hopReader;\n } else {\n hopReader = sourceReader;\n nextReader = sourceReader;\n if (!_crossGraphWarned) {\n _crossGraphWarned = true;\n console.warn(\n `[firegraph] Traversal hop \"${hop.axbType}\" has targetGraph \"${resolvedTargetGraph}\" ` +\n 'but the reader does not support subgraph(). Cross-graph hop will query the current ' +\n 'collection instead. Pass a GraphClient to createTraversal() to enable cross-graph traversal.',\n );\n }\n }\n } else {\n // No targetGraph — carry forward context from previous hop\n hopReader = sourceReader;\n nextReader = sourceReader;\n }\n\n let edges = await hopReader.findEdges(params);\n\n if (hop.filter) {\n edges = edges.filter(hop.filter);\n edges = edges.slice(0, limit);\n }\n\n for (const edge of edges) {\n hopEdges.push({ edge, reader: nextReader });\n }\n } finally {\n semaphore.release();\n }\n });\n\n await Promise.all(tasks.map((task) => task()));\n\n const edges = hopEdges.map((h) => h.edge);\n\n hopResults.push({\n axbType: hop.axbType,\n depth,\n edges: returnIntermediates ? [...edges] : edges,\n sourceCount,\n truncated: hopTruncated,\n });\n\n if (hopTruncated) {\n truncated = true;\n }\n\n // Build next sources with deduplication by UID.\n // When the same UID appears from multiple source readers, the first one wins.\n const seen = new Map<string, GraphReader>();\n for (const { edge, reader: edgeReader } of hopEdges) {\n const nextUid = direction === 'forward' ? edge.bUid : edge.aUid;\n if (!seen.has(nextUid)) {\n seen.set(nextUid, edgeReader);\n }\n }\n sources = [...seen.entries()].map(([uid, reader]) => ({ uid, reader }));\n }\n\n const lastHop = hopResults[hopResults.length - 1];\n\n return {\n nodes: lastHop.edges,\n hops: hopResults,\n totalReads,\n truncated,\n };\n }\n\n /**\n * Try to dispatch the entire hop chain as one engine-traversal call.\n * Returns a `TraversalResult` on success, or `undefined` if the spec is\n * ineligible and the caller should fall through to the per-hop loop.\n *\n * `'force'` mode throws on any ineligibility instead of returning\n * `undefined` — the caller intentionally opted out of fallback.\n */\n private async tryEngineTraversal(args: {\n engineMode: 'auto' | 'force';\n returnIntermediates: boolean;\n }): Promise<TraversalResult | undefined> {\n const { engineMode, returnIntermediates } = args;\n\n const refuse = (reason: string): TraversalResult | undefined => {\n if (engineMode === 'force') {\n throw new FiregraphError(`engineTraversal: 'force' but ${reason}`, 'UNSUPPORTED_OPERATION');\n }\n return undefined;\n };\n\n if (!readerSupportsEngineTraversal(this.reader)) {\n return refuse('reader does not declare traversal.serverSide capability');\n }\n const client = this.reader;\n\n // Per-hop eligibility — JS filters and cross-graph hops both prevent\n // engine compilation. Walk the full chain so the failure reason can\n // point at the offending hop.\n const engineHops: EngineHopSpec[] = [];\n for (let i = 0; i < this.hops.length; i++) {\n const hop = this.hops[i];\n if (hop.filter) {\n return refuse(`hop ${i} (${hop.axbType}) carries a JS filter callback`);\n }\n const targetGraph = this.resolveTargetGraph(hop);\n const direction = hop.direction ?? 'forward';\n if (targetGraph) {\n return refuse(`hop ${i} (${hop.axbType}) is cross-graph (targetGraph=${targetGraph})`);\n }\n const limit = hop.limit ?? DEFAULT_LIMIT;\n const engineHop: EngineHopSpec = {\n axbType: hop.axbType,\n direction,\n limitPerSource: limit,\n };\n if (hop.aType) engineHop.aType = hop.aType;\n if (hop.bType) engineHop.bType = hop.bType;\n if (hop.orderBy) engineHop.orderBy = hop.orderBy;\n engineHops.push(engineHop);\n }\n\n const params: EngineTraversalParams = {\n sources: [this.startUid],\n hops: engineHops,\n };\n\n // Compile-side validation (depth, limits, response-size budget) lives\n // in `firestore-traverse-compiler.ts`. We invoke it from the traversal\n // layer (rather than relying on the executor to throw) so 'auto' can\n // silently fall back without ever entering the SDK.\n const compiled = compileEngineTraversal(params);\n if (!compiled.eligible) {\n return refuse(compiled.reason);\n }\n\n let engineResult: EngineTraversalResult;\n try {\n engineResult = await client.runEngineTraversal(params);\n } catch (err) {\n if (engineMode === 'force') throw err;\n return undefined;\n }\n\n // Translate `EngineTraversalResult` into `TraversalResult` (`HopResult[]`).\n // Truncation is detected per-hop: if the returned edge count equals the\n // limitPerSource enforced in the pipeline, the server hit its cap and\n // there may be more edges. This is conservative — for depth-1+ hops with\n // multiple parents, deduplication may reduce the count below limitPerSource\n // per-parent while the aggregate still triggers the check.\n const hopResults: HopResult[] = [];\n for (let i = 0; i < this.hops.length; i++) {\n const definedHop = this.hops[i];\n const engineHopResult = engineResult.hops[i] ?? { edges: [], sourceCount: 0 };\n const edges = engineHopResult.edges;\n const hopTruncated = edges.length >= engineHops[i].limitPerSource;\n hopResults.push({\n axbType: definedHop.axbType,\n depth: i,\n edges: returnIntermediates ? [...edges] : edges,\n sourceCount: engineHopResult.sourceCount,\n truncated: hopTruncated,\n });\n }\n\n const lastHop = hopResults[hopResults.length - 1];\n return {\n nodes: lastHop.edges,\n hops: hopResults,\n // One server-side round trip — same accounting as the `expand()`\n // fast path. The tree response can carry up to `estimatedReads`\n // docs total, but the budget is in round trips, not docs.\n totalReads: 1,\n truncated: hopResults.some((h) => h.truncated),\n };\n }\n\n /**\n * Resolve the targetGraph for a hop. Priority:\n * 1. Explicit `hop.targetGraph` (user override)\n * 2. Registry `targetGraph` for the axbType (if registry available)\n * 3. undefined (no cross-graph)\n */\n private resolveTargetGraph(hop: HopDefinition): string | undefined {\n if (hop.targetGraph) return hop.targetGraph;\n\n if (this.registry) {\n const entries = this.registry.lookupByAxbType(hop.axbType);\n // All entries for the same axbType should share targetGraph; use the first non-undefined\n for (const entry of entries) {\n if (entry.targetGraph) return entry.targetGraph;\n }\n }\n\n return undefined;\n }\n}\n\n/** @internal Reset the one-time cross-graph warning flag (for testing). */\nexport function _resetCrossGraphWarning(): void {\n _crossGraphWarned = false;\n}\n\n/**\n * Create a traversal builder for multi-hop graph traversal.\n *\n * Accepts either a `GraphReader` (backwards compatible) or a `GraphClient`.\n * When a `GraphClient` is provided, cross-graph traversal via `targetGraph`\n * is supported — the traversal can follow edges into subgraphs.\n *\n * @param reader - A `GraphClient` or `GraphReader` to execute queries against\n * @param startUid - UID of the starting node\n * @param registry - Optional registry for automatic `targetGraph` resolution\n */\nexport function createTraversal(\n reader: GraphClient | GraphReader,\n startUid: string,\n registry?: GraphRegistry,\n): TraversalBuilder {\n return new TraversalBuilderImpl(reader, startUid, registry);\n}\n","/**\n * Model Views — framework-agnostic view definitions for graph entities.\n *\n * Projects define Web Components that render entity data in purpose-driven\n * ways. Each view class declares a static `viewName`, and receives the\n * entity's `data` payload via a `data` property setter.\n *\n * @example\n * ```ts\n * import { defineViews } from 'firegraph';\n *\n * class UserCard extends HTMLElement {\n * static viewName = 'card';\n * static description = 'Compact user card';\n * private _data: Record<string, unknown> = {};\n * set data(v: Record<string, unknown>) { this._data = v; this.render(); }\n * connectedCallback() { this.render(); }\n * private render() {\n * this.innerHTML = `<strong>${this._data.displayName ?? ''}</strong>`;\n * }\n * }\n *\n * export default defineViews({\n * nodes: { user: { views: [UserCard] } },\n * });\n * ```\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A Web Component class used as a view. The class must have a static\n * `viewName` and must be constructable. It will be registered as a custom\n * element via `customElements.define()` in browser environments.\n *\n * Note: this interface avoids referencing `HTMLElement` directly so the\n * library can compile without DOM lib types. Consumer code (which has DOM)\n * will satisfy this constraint naturally.\n */\nexport interface ViewComponentClass {\n new (...args: any[]): { data: Record<string, unknown> };\n /** Short identifier for this view (e.g. 'card', 'profile'). */\n viewName: string;\n /** Optional human-readable description. */\n description?: string;\n}\n\n/** Configuration for all views of a single entity type. */\nexport interface EntityViewConfig {\n /** View component classes to register. */\n views: ViewComponentClass[];\n /**\n * Optional sample data for the gallery. A single object matching\n * the entity's JSON Schema — shared across all views.\n */\n sampleData?: Record<string, unknown>;\n}\n\n/** Input shape accepted by `defineViews()`. */\nexport interface ViewRegistryInput {\n /** Node views keyed by aType (e.g. 'user', 'tour'). */\n nodes?: Record<string, EntityViewConfig>;\n /** Edge views keyed by axbType (e.g. 'hasDeparture'). */\n edges?: Record<string, EntityViewConfig>;\n}\n\n/** Serialisable metadata for a single view. */\nexport interface ViewMeta {\n /** Custom element tag name (e.g. 'fg-user-card'). */\n tagName: string;\n /** Short identifier matching the component's static viewName. */\n viewName: string;\n /** Optional human-readable description. */\n description?: string;\n}\n\n/** Serialisable metadata for all views of a single entity type. */\nexport interface EntityViewMeta {\n views: ViewMeta[];\n sampleData?: Record<string, unknown>;\n}\n\n/** The resolved view registry returned by `defineViews()`. */\nexport interface ViewRegistry {\n nodes: Record<string, EntityViewMeta>;\n edges: Record<string, EntityViewMeta>;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Sanitise a string for use as part of a custom element tag name. */\nfunction sanitizeTagPart(s: string): string {\n return s\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n}\n\n/** Minimal interface for CustomElementRegistry (avoids depending on DOM lib). */\ninterface CustomElementRegistryLike {\n get(name: string): unknown;\n define(name: string, constructor: unknown): void;\n}\n\n/**\n * Try to access the browser's `customElements` registry.\n * Returns `null` in Node.js or environments without Web Components support.\n */\nfunction getCustomElements(): CustomElementRegistryLike | null {\n const g = globalThis as any;\n if (g.customElements && typeof g.customElements.define === 'function') {\n return g.customElements as CustomElementRegistryLike;\n }\n return null;\n}\n\n/**\n * Wrap a view class so that errors in connectedCallback, disconnectedCallback,\n * and the data setter are caught and logged rather than crashing the page.\n * Shows an inline error message when the view fails to render.\n */\nfunction resilientView(ViewClass: ViewComponentClass, tagName: string): ViewComponentClass {\n const g = globalThis as any;\n if (!g.HTMLElement) return ViewClass; // Node.js — no wrapping needed\n\n const Wrapped = class extends (ViewClass as unknown as new (...args: any[]) => any) {\n connectedCallback() {\n try {\n super.connectedCallback?.();\n } catch (err) {\n console.warn(`[firegraph] <${tagName}> connectedCallback error:`, err);\n this._showError(err);\n }\n }\n\n disconnectedCallback() {\n try {\n super.disconnectedCallback?.();\n } catch (err) {\n console.warn(`[firegraph] <${tagName}> disconnectedCallback error:`, err);\n }\n }\n\n set data(v: Record<string, unknown>) {\n try {\n super.data = v;\n } catch (err) {\n console.warn(`[firegraph] <${tagName}> data setter error:`, err);\n this._showError(err);\n }\n }\n\n get data(): Record<string, unknown> {\n try {\n return super.data;\n } catch {\n return {};\n }\n }\n\n _showError(err: unknown) {\n try {\n this.innerHTML =\n `<div style=\"padding:6px;color:#f87171;font-size:11px;font-family:monospace;\">` +\n `View error in <${tagName}>: ${err instanceof Error ? err.message : String(err)}</div>`;\n } catch {\n /* last resort — don't throw from error handler */\n }\n }\n };\n\n // Preserve static metadata\n (Wrapped as unknown as ViewComponentClass).viewName = ViewClass.viewName;\n (Wrapped as unknown as ViewComponentClass).description = ViewClass.description;\n\n return Wrapped as unknown as ViewComponentClass;\n}\n\n// ---------------------------------------------------------------------------\n// defineViews()\n// ---------------------------------------------------------------------------\n\n/**\n * Build a `ViewRegistry` from component classes.\n *\n * In the browser the components are registered as custom elements with\n * deterministic tag names (`fg-{entityType}-{viewName}`). On the server\n * (Node.js) only metadata is returned — no custom element registration.\n */\nexport function defineViews(input: ViewRegistryInput): ViewRegistry {\n const nodes: Record<string, EntityViewMeta> = {};\n const edges: Record<string, EntityViewMeta> = {};\n const registry = getCustomElements();\n\n // --- nodes ---\n for (const [entityType, config] of Object.entries(input.nodes ?? {})) {\n const viewMetas: ViewMeta[] = [];\n for (const ViewClass of config.views) {\n const tagName = `fg-${sanitizeTagPart(entityType)}-${sanitizeTagPart(ViewClass.viewName)}`;\n viewMetas.push({\n tagName,\n viewName: ViewClass.viewName,\n description: ViewClass.description,\n });\n if (registry && !registry.get(tagName)) {\n registry.define(tagName, resilientView(ViewClass, tagName));\n }\n }\n nodes[entityType] = {\n views: viewMetas,\n sampleData: config.sampleData,\n };\n }\n\n // --- edges ---\n for (const [axbType, config] of Object.entries(input.edges ?? {})) {\n const viewMetas: ViewMeta[] = [];\n for (const ViewClass of config.views) {\n const tagName = `fg-edge-${sanitizeTagPart(axbType)}-${sanitizeTagPart(ViewClass.viewName)}`;\n viewMetas.push({\n tagName,\n viewName: ViewClass.viewName,\n description: ViewClass.description,\n });\n if (registry && !registry.get(tagName)) {\n registry.define(tagName, resilientView(ViewClass, tagName));\n }\n }\n edges[axbType] = {\n views: viewMetas,\n sampleData: config.sampleData,\n };\n }\n\n return { nodes, edges };\n}\n"],"mappings":"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","names":["resolve","edges","seen"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as SummarizedEdge,
|
|
2
|
-
export { G as GetEdgesInput,
|
|
1
|
+
import { S as SummarizedEdge, a as SummarizedRecord } from '../client-DoyEdJ5w.cjs';
|
|
2
|
+
export { G as GetEdgesInput, b as GetEdgesResult, c as GetNodeDetailInput, d as GetNodesInput, e as GetNodesResult, N as NodeDetailResult, Q as QueryClient, f as QueryClientError, g as QueryClientErrorCode, h as QueryClientOptions, i as SchemaResult, j as SearchInput, k as SearchResult, T as TraverseHop, l as TraverseHopResult, m as TraverseInput, n as TraverseResult, W as WhereClause } from '../client-DoyEdJ5w.cjs';
|
|
3
3
|
|
|
4
4
|
declare function runQueryCli(argv: string[]): Promise<void>;
|
|
5
5
|
|