@typicalday/firegraph 0.7.0 → 0.8.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/dist/index.d.cts CHANGED
@@ -1,100 +1,158 @@
1
+ import { S as StoredGraphRecord, Q as QueryFilter, c as QueryOptions, d as GraphReader, B as BulkOptions, C as CascadeResult, F as FindEdgesParams, e as BulkResult, G as GraphClientOptions, b as GraphClient, a as DynamicGraphClient, f as DiscoveryResult, R as RegistryEntry, g as GraphRegistry, M as MigrationExecutor, D as DynamicRegistryConfig, h as MigrationWriteBack, i as MigrationStep, j as QueryPlan, k as FindNodesParams, l as GraphRecord, m as MigrationFn, n as StoredMigrationStep, T as TraversalBuilder } from './types-BVtx9zLv.cjs';
2
+ export { o as BulkBatchError, p as BulkProgress, q as DefineTypeOptions, r as DiscoveredEntity, E as EdgeTopology, s as EdgeTypeData, t as FiregraphConfig, u as GraphBatch, v as GraphTransaction, w as GraphWriter, H as HopDefinition, x as HopResult, N as NodeTypeData, y as QueryMode, z as ScanProtection, A as TraversalOptions, I as TraversalResult, V as ViewContext, J as ViewDefaultsConfig, K as ViewResolverConfig, W as WhereClause, L as defineConfig, O as resolveView } from './types-BVtx9zLv.cjs';
3
+ export { CodegenOptions, generateTypes } from './codegen/index.cjs';
1
4
  import { Firestore } from '@google-cloud/firestore';
2
- import { G as GraphClientOptions, D as DynamicRegistryConfig, a as DynamicGraphClient, b as GraphClient, c as GraphRegistry, R as RegistryEntry, d as DiscoveryResult, e as GraphReader, M as MigrationExecutor, f as GraphRecord, F as FindEdgesParams, Q as QueryPlan, g as FindNodesParams, T as TraversalBuilder, h as MigrationFn, S as StoredMigrationStep, i as MigrationStep, j as StoredGraphRecord, k as MigrationWriteBack, l as QueryFilter } from './index-B9aodfYD.cjs';
3
- export { B as BulkBatchError, m as BulkOptions, n as BulkProgress, o as BulkResult, C as CascadeResult, p as CodegenOptions, q as DefineTypeOptions, r as DiscoveredEntity, E as EdgeTopology, s as EdgeTypeData, t as FiregraphConfig, u as GraphBatch, v as GraphTransaction, w as GraphWriter, H as HopDefinition, x as HopResult, N as NodeTypeData, y as QueryMode, z as QueryOptions, A as ScanProtection, I as TraversalOptions, J as TraversalResult, V as ViewContext, K as ViewDefaultsConfig, L as ViewResolverConfig, W as WhereClause, O as defineConfig, P as generateTypes, U as resolveView } from './index-B9aodfYD.cjs';
4
- 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';
5
5
  export { Q as QueryClient, a as QueryClientError, b as QueryClientErrorCode, c as QueryClientOptions } from './client-Bk2Cm6xv.cjs';
6
-
7
- declare function createGraphClient(db: Firestore, collectionPath: string, options: GraphClientOptions & {
8
- registryMode: DynamicRegistryConfig;
9
- }): DynamicGraphClient;
10
- declare function createGraphClient(db: Firestore, collectionPath: string, options?: GraphClientOptions): GraphClient;
6
+ 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';
11
7
 
12
8
  /**
13
- * Build a registry from either explicit entries or a DiscoveryResult.
9
+ * Backend abstraction for firegraph.
14
10
  *
15
- * @example
16
- * ```ts
17
- * // From explicit entries (programmatic)
18
- * const registry = createRegistry([
19
- * { aType: 'user', axbType: 'is', bType: 'user', jsonSchema: userSchema },
20
- * { aType: 'user', axbType: 'follows', bType: 'user', jsonSchema: followsSchema },
21
- * ]);
11
+ * `StorageBackend` is the single interface every storage driver implements.
12
+ * The Firestore backend wraps `@google-cloud/firestore`; the SQLite backend
13
+ * (shared by D1 and Durable Object SQLite) uses a parameterized SQL executor.
22
14
  *
23
- * // From discovery result (folder convention)
24
- * const discovered = await discoverEntities('./entities');
25
- * const registry = createRegistry(discovered);
26
- * ```
15
+ * `GraphClientImpl` and friends depend only on this interface — they have
16
+ * no direct knowledge of Firestore or SQLite.
27
17
  */
28
- declare function createRegistry(input: RegistryEntry[] | DiscoveryResult): GraphRegistry;
18
+
29
19
  /**
30
- * Create a merged registry where `base` entries take priority and `extension`
31
- * entries fill in gaps. Lookups and validation check `base` first; only if the
32
- * triple is not found there does the merged registry fall through to
33
- * `extension`.
20
+ * Per-record write payload backend-agnostic. Timestamps are not present;
21
+ * the backend supplies them via `serverTimestamp()` placeholders that it
22
+ * itself resolves at commit time.
23
+ */
24
+ interface WritableRecord {
25
+ aType: string;
26
+ aUid: string;
27
+ axbType: string;
28
+ bType: string;
29
+ bUid: string;
30
+ data: Record<string, unknown>;
31
+ /** Schema version (set by the writer when registry has migrations). */
32
+ v?: number;
33
+ }
34
+ /**
35
+ * Patch shape for `updateDoc`. Captures the two patterns that exist today:
36
+ * - `dataFields`: shallow merge under `data` (used by `updateNode`)
37
+ * - `replaceData`: full data replacement (used by migration write-back)
38
+ * - `v`: optional schema-version stamp
34
39
  *
35
- * The `entries()` method returns a deduplicated list (base wins on collision).
36
- * The `lookupByAxbType()` method merges results from both registries,
37
- * deduplicating by triple key with base entries winning.
40
+ * `updatedAt` is always set by the backend.
38
41
  */
39
- declare function createMergedRegistry(base: GraphRegistry, extension: GraphRegistry): GraphRegistry;
40
-
41
- /** The aType used for node type definition meta-nodes. */
42
- declare const META_NODE_TYPE = "nodeType";
43
- /** The aType used for edge type definition meta-nodes. */
44
- declare const META_EDGE_TYPE = "edgeType";
45
- /** JSON Schema for the `data` payload of a `nodeType` meta-node. */
46
- declare const NODE_TYPE_SCHEMA: object;
47
- /** JSON Schema for the `data` payload of an `edgeType` meta-node. */
48
- declare const EDGE_TYPE_SCHEMA: object;
49
- /** Registry entries for the two meta-types (always present). */
50
- declare const BOOTSTRAP_ENTRIES: readonly RegistryEntry[];
42
+ interface UpdatePayload {
43
+ dataFields?: Record<string, unknown>;
44
+ replaceData?: Record<string, unknown>;
45
+ v?: number;
46
+ }
51
47
  /**
52
- * Build the bootstrap registry that validates meta-type writes.
53
- * This is always available, even before any dynamic types are loaded.
48
+ * Read/write transaction adapter. Mirrors Firestore's transaction semantics:
49
+ * reads are snapshot-consistent; writes are issued inside the transaction
50
+ * and a rejection from any write aborts the surrounding `runTransaction`.
51
+ *
52
+ * Writes return `Promise<void>` so SQL drivers can surface row-level errors
53
+ * (constraint violations, malformed JSON paths) rather than swallowing them.
54
+ * Firestore implementations can resolve synchronously since the underlying
55
+ * `Transaction.set/update/delete` calls are themselves synchronous buffers.
56
+ */
57
+ interface TransactionBackend {
58
+ getDoc(docId: string): Promise<StoredGraphRecord | null>;
59
+ query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]>;
60
+ setDoc(docId: string, record: WritableRecord): Promise<void>;
61
+ updateDoc(docId: string, update: UpdatePayload): Promise<void>;
62
+ deleteDoc(docId: string): Promise<void>;
63
+ }
64
+ /**
65
+ * Atomic multi-write batch.
54
66
  */
55
- declare function createBootstrapRegistry(): GraphRegistry;
67
+ interface BatchBackend {
68
+ setDoc(docId: string, record: WritableRecord): void;
69
+ updateDoc(docId: string, update: UpdatePayload): void;
70
+ deleteDoc(docId: string): void;
71
+ commit(): Promise<void>;
72
+ }
56
73
  /**
57
- * Generate a deterministic UID for a meta-type definition.
58
- * This ensures that defining the same type name always targets the same
59
- * Firestore document, enabling upsert semantics.
74
+ * The single storage abstraction.
75
+ *
76
+ * Each backend instance is scoped to a "graph location" — for Firestore
77
+ * that's a collection path; for SQLite it's a (table, scopePath) pair.
78
+ * `subgraph()` returns a child backend bound to a nested location.
79
+ */
80
+ interface StorageBackend {
81
+ /** Backend-internal location identifier (collection path or table name). */
82
+ readonly collectionPath: string;
83
+ /** Subgraph scope (empty string for root). */
84
+ readonly scopePath: string;
85
+ getDoc(docId: string): Promise<StoredGraphRecord | null>;
86
+ query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]>;
87
+ setDoc(docId: string, record: WritableRecord): Promise<void>;
88
+ updateDoc(docId: string, update: UpdatePayload): Promise<void>;
89
+ deleteDoc(docId: string): Promise<void>;
90
+ runTransaction<T>(fn: (tx: TransactionBackend) => Promise<T>): Promise<T>;
91
+ createBatch(): BatchBackend;
92
+ subgraph(parentNodeUid: string, name: string): StorageBackend;
93
+ removeNodeCascade(uid: string, reader: GraphReader, options?: BulkOptions): Promise<CascadeResult>;
94
+ bulkRemoveEdges(params: FindEdgesParams, reader: GraphReader, options?: BulkOptions): Promise<BulkResult>;
95
+ /**
96
+ * Find edges across all subgraphs sharing a given collection name.
97
+ * Optional — backends that can't support this should throw a clear error.
98
+ */
99
+ findEdgesGlobal?(params: FindEdgesParams, collectionName?: string): Promise<StoredGraphRecord[]>;
100
+ }
101
+
102
+ /**
103
+ * Create a `GraphClient` backed by an arbitrary `StorageBackend`.
60
104
  *
61
- * Format: 21-char base64url substring of SHA-256(`metaType:name`).
105
+ * Used by backend-specific factories (D1, DO-SQLite, etc.) — most callers
106
+ * should use the higher-level `createGraphClient(firestore, ...)` overload
107
+ * below or import the equivalent from `firegraph/d1` / `firegraph/do-sqlite`.
62
108
  */
63
- declare function generateDeterministicUid(metaType: string, name: string): string;
109
+ declare function createGraphClientFromBackend(backend: StorageBackend, options?: GraphClientOptions, metaBackend?: StorageBackend): GraphClient | DynamicGraphClient;
110
+
64
111
  /**
65
- * Read meta-type nodes from the graph and compile them into a GraphRegistry.
112
+ * Cross-graph edge resolution utilities.
66
113
  *
67
- * The returned registry includes both the dynamic entries AND the bootstrap
68
- * meta-type entries, so meta-type writes remain validateable after a reload.
114
+ * Provides path-scanning resolution for determining whether an edge's source
115
+ * (aUid) is an ancestor node by checking if the UID appears in the Firestore
116
+ * collection path.
69
117
  *
70
- * @param reader - A GraphReader pointed at the collection containing meta-nodes.
71
- * @param executor - Optional custom executor for compiling stored migration source strings.
118
+ * Firestore paths have a rigid alternating structure:
119
+ * collection / docId / collection / docId / collection
120
+ *
121
+ * Given a path like `graph/A/workspace/B/context`, segments at even indices
122
+ * are collection names and odd indices are document IDs. When we find a UID
123
+ * at an odd index, the collection containing that document is the path up to
124
+ * (and including) the preceding even-index segment.
72
125
  */
73
- declare function createRegistryFromGraph(reader: GraphReader, executor?: MigrationExecutor): Promise<GraphRegistry>;
74
-
75
- declare function generateId(): string;
76
-
77
- declare function computeNodeDocId(uid: string): string;
78
- declare function computeEdgeDocId(aUid: string, axbType: string, bUid: string): string;
79
-
80
- declare function buildNodeRecord(aType: string, uid: string, data: Record<string, unknown>): GraphRecord;
81
- declare function buildEdgeRecord(aType: string, aUid: string, axbType: string, bType: string, bUid: string, data: Record<string, unknown>): GraphRecord;
82
-
83
- declare function buildEdgeQueryPlan(params: FindEdgesParams): QueryPlan;
84
- declare function buildNodeQueryPlan(params: FindNodesParams): QueryPlan;
85
-
86
126
  /**
87
- * Create a traversal builder for multi-hop graph traversal.
127
+ * Parse a Firestore collection path and determine the collection path
128
+ * where a given UID's document lives, if that UID is an ancestor in the path.
88
129
  *
89
- * Accepts either a `GraphReader` (backwards compatible) or a `GraphClient`.
90
- * When a `GraphClient` is provided, cross-graph traversal via `targetGraph`
91
- * is supported the traversal can follow edges into subgraphs.
130
+ * @param collectionPath - The full Firestore collection path of the current client
131
+ * @param uid - The UID to search for in the path
132
+ * @returns The collection path containing the UID, or `null` if not found in the path
92
133
  *
93
- * @param reader - A `GraphClient` or `GraphReader` to execute queries against
94
- * @param startUid - UID of the starting node
95
- * @param registry - Optional registry for automatic `targetGraph` resolution
134
+ * @example
135
+ * ```ts
136
+ * // Path: graph/A/workspace/B/context
137
+ * resolveAncestorCollection('graph/A/workspace/B/context', 'A')
138
+ * // → 'graph'
139
+ *
140
+ * resolveAncestorCollection('graph/A/workspace/B/context', 'B')
141
+ * // → 'graph/A/workspace'
142
+ *
143
+ * resolveAncestorCollection('graph/A/workspace/B/context', 'unknown')
144
+ * // → null
145
+ * ```
96
146
  */
97
- declare function createTraversal(reader: GraphClient | GraphReader, startUid: string, registry?: GraphRegistry): TraversalBuilder;
147
+ declare function resolveAncestorCollection(collectionPath: string, uid: string): string | null;
148
+ /**
149
+ * Check whether a UID belongs to an ancestor node by scanning the collection path.
150
+ *
151
+ * @param collectionPath - The full Firestore collection path of the current client
152
+ * @param uid - The UID to check
153
+ * @returns `true` if the UID appears as a document segment in the path
154
+ */
155
+ declare function isAncestorUid(collectionPath: string, uid: string): boolean;
98
156
 
99
157
  declare class FiregraphError extends Error {
100
158
  readonly code: string;
@@ -182,82 +240,97 @@ interface DiscoverResult {
182
240
  */
183
241
  declare function discoverEntities(entitiesDir: string): DiscoverResult;
184
242
 
243
+ declare function computeNodeDocId(uid: string): string;
244
+ declare function computeEdgeDocId(aUid: string, axbType: string, bUid: string): string;
245
+
246
+ /** The aType used for node type definition meta-nodes. */
247
+ declare const META_NODE_TYPE = "nodeType";
248
+ /** The aType used for edge type definition meta-nodes. */
249
+ declare const META_EDGE_TYPE = "edgeType";
250
+ /** JSON Schema for the `data` payload of a `nodeType` meta-node. */
251
+ declare const NODE_TYPE_SCHEMA: object;
252
+ /** JSON Schema for the `data` payload of an `edgeType` meta-node. */
253
+ declare const EDGE_TYPE_SCHEMA: object;
254
+ /** Registry entries for the two meta-types (always present). */
255
+ declare const BOOTSTRAP_ENTRIES: readonly RegistryEntry[];
185
256
  /**
186
- * Scope path matching for subgraph-level registry constraints.
187
- *
188
- * Scope paths are slash-separated names derived from the chain of
189
- * `subgraph()` calls (e.g., `'agents'`, `'agents/memories'`).
190
- * The root graph has an empty scope path (`''`).
191
- *
192
- * Patterns:
193
- * - `'root'` — matches only the root graph (empty scope path)
194
- * - `'agents'` — matches exactly `'agents'`
195
- * - `'agents/memories'` — matches exactly `'agents/memories'`
196
- * - `'*​/agents'` — `*` matches one segment: `'foo/agents'` but not `'a/b/agents'`
197
- * - `'**​/memories'` — `**` matches zero or more segments
198
- * - `'**'` — matches everything including root
257
+ * Build the bootstrap registry that validates meta-type writes.
258
+ * This is always available, even before any dynamic types are loaded.
199
259
  */
260
+ declare function createBootstrapRegistry(): GraphRegistry;
200
261
  /**
201
- * Test whether a scope path matches a single pattern.
262
+ * Generate a deterministic UID for a meta-type definition.
263
+ * This ensures that defining the same type name always targets the same
264
+ * Firestore document, enabling upsert semantics.
202
265
  *
203
- * @param scopePath - The current scope path (empty string for root)
204
- * @param pattern - The pattern to match against
266
+ * Format: 21-char base64url substring of SHA-256(`metaType:name`).
205
267
  */
206
- declare function matchScope(scopePath: string, pattern: string): boolean;
268
+ declare function generateDeterministicUid(metaType: string, name: string): string;
207
269
  /**
208
- * Test whether a scope path matches any pattern in a list.
209
- * Returns `true` if the list is empty or undefined (allowed everywhere).
270
+ * Read meta-type nodes from the graph and compile them into a GraphRegistry.
210
271
  *
211
- * @param scopePath - The current scope path (empty string for root)
212
- * @param patterns - Array of patterns to match against
272
+ * The returned registry includes both the dynamic entries AND the bootstrap
273
+ * meta-type entries, so meta-type writes remain validateable after a reload.
274
+ *
275
+ * @param reader - A GraphReader pointed at the collection containing meta-nodes.
276
+ * @param executor - Optional custom executor for compiling stored migration source strings.
213
277
  */
214
- declare function matchScopeAny(scopePath: string, patterns: string[]): boolean;
278
+ declare function createRegistryFromGraph(reader: GraphReader, executor?: MigrationExecutor): Promise<GraphRegistry>;
215
279
 
216
280
  /**
217
- * Cross-graph edge resolution utilities.
218
- *
219
- * Provides path-scanning resolution for determining whether an edge's source
220
- * (aUid) is an ancestor node by checking if the UID appears in the Firestore
221
- * collection path.
281
+ * Firestore-specific client factory.
222
282
  *
223
- * Firestore paths have a rigid alternating structure:
224
- * collection / docId / collection / docId / collection
225
- *
226
- * Given a path like `graph/A/workspace/B/context`, segments at even indices
227
- * are collection names and odd indices are document IDs. When we find a UID
228
- * at an odd index, the collection containing that document is the path up to
229
- * (and including) the preceding even-index segment.
283
+ * Kept in its own module so that bundlers don't pull
284
+ * `@google-cloud/firestore` into SQLite-only entry points
285
+ * (`firegraph/d1`, `firegraph/do-sqlite`).
230
286
  */
287
+
288
+ declare function createGraphClient(db: Firestore, collectionPath: string, options: GraphClientOptions & {
289
+ registryMode: DynamicRegistryConfig;
290
+ }): DynamicGraphClient;
291
+ declare function createGraphClient(db: Firestore, collectionPath: string, options?: GraphClientOptions): GraphClient;
292
+
293
+ declare function generateId(): string;
294
+
295
+ interface FirestoreIndexField {
296
+ fieldPath: string;
297
+ order: 'ASCENDING' | 'DESCENDING';
298
+ }
299
+ interface FirestoreIndex {
300
+ collectionGroup: string;
301
+ queryScope: 'COLLECTION' | 'COLLECTION_GROUP';
302
+ fields: FirestoreIndexField[];
303
+ }
304
+ interface FirestoreIndexConfig {
305
+ indexes: FirestoreIndex[];
306
+ fieldOverrides: unknown[];
307
+ }
231
308
  /**
232
- * Parse a Firestore collection path and determine the collection path
233
- * where a given UID's document lives, if that UID is an ancestor in the path.
234
- *
235
- * @param collectionPath - The full Firestore collection path of the current client
236
- * @param uid - The UID to search for in the path
237
- * @returns The collection path containing the UID, or `null` if not found in the path
309
+ * Generates a Firestore index configuration for a firegraph collection.
238
310
  *
239
- * @example
240
- * ```ts
241
- * // Path: graph/A/workspace/B/context
242
- * resolveAncestorCollection('graph/A/workspace/B/context', 'A')
243
- * // → 'graph'
311
+ * Always includes the 4 base composite indexes. If an entity discovery result
312
+ * is provided, generates additional data-field indexes for common query
313
+ * patterns on node data fields:
314
+ * (aType, axbType, data.{field})
244
315
  *
245
- * resolveAncestorCollection('graph/A/workspace/B/context', 'B')
246
- * // 'graph/A/workspace'
316
+ * When registry entries with `targetGraph` are provided, also generates
317
+ * collection group indexes for `findEdgesGlobal()` queries. The collection
318
+ * group name defaults to `'graph'` (the standard subgraph name) but can be
319
+ * overridden per `targetGraph` value.
247
320
  *
248
- * resolveAncestorCollection('graph/A/workspace/B/context', 'unknown')
249
- * // null
250
- * ```
321
+ * @param collection - Firestore collection name (e.g. 'graph')
322
+ * @param entities - Optional discovery result for per-entity data field indexes
323
+ * @param registryEntries - Optional registry entries; when any have `targetGraph`,
324
+ * collection group indexes are generated for the distinct subgraph names
251
325
  */
252
- declare function resolveAncestorCollection(collectionPath: string, uid: string): string | null;
326
+ declare function generateIndexConfig(collection: string, entities?: DiscoveryResult, registryEntries?: ReadonlyArray<RegistryEntry>): FirestoreIndexConfig;
327
+
253
328
  /**
254
- * Check whether a UID belongs to an ancestor node by scanning the collection path.
255
- *
256
- * @param collectionPath - The full Firestore collection path of the current client
257
- * @param uid - The UID to check
258
- * @returns `true` if the UID appears as a document segment in the path
329
+ * Default result limit applied to findEdges/findNodes queries
330
+ * when no explicit limit is provided. Prevents unbounded result sets
331
+ * that could be expensive on Enterprise Firestore.
259
332
  */
260
- declare function isAncestorUid(collectionPath: string, uid: string): boolean;
333
+ declare const DEFAULT_QUERY_LIMIT = 500;
261
334
 
262
335
  /**
263
336
  * JSON Schema validation and introspection utilities.
@@ -292,6 +365,107 @@ declare function compileSchema(schema: object, label?: string): (data: unknown)
292
365
  */
293
366
  declare function jsonSchemaToFieldMeta(schema: any): FieldMeta[];
294
367
 
368
+ /**
369
+ * Migration pipeline for auto-migrating records on read.
370
+ *
371
+ * When a record's `v` is behind the version derived from the registry
372
+ * entry's migrations, the pipeline applies migration steps sequentially
373
+ * to bring the data up to the current version.
374
+ */
375
+
376
+ /** Result of attempting to migrate a single record. */
377
+ interface MigrationResult {
378
+ record: StoredGraphRecord;
379
+ migrated: boolean;
380
+ /** Resolved write-back mode for this record (entry-level > global > 'off'). */
381
+ writeBack: MigrationWriteBack;
382
+ }
383
+ /**
384
+ * Apply a chain of migration steps to transform data from `currentVersion`
385
+ * to `targetVersion`. Throws `MigrationError` if the chain is incomplete
386
+ * or a migration function fails.
387
+ *
388
+ * Returns the migrated data payload only — the caller is responsible for
389
+ * stamping `v` on the record envelope.
390
+ */
391
+ declare function applyMigrationChain(data: Record<string, unknown>, currentVersion: number, targetVersion: number, migrations: MigrationStep[]): Promise<Record<string, unknown>>;
392
+ /**
393
+ * Validate that a migration chain forms a contiguous path from version 0
394
+ * to the highest `toVersion`. Throws `MigrationError` if the chain has
395
+ * gaps or duplicate `fromVersion` values.
396
+ *
397
+ * Called at registry construction time to catch incomplete chains early,
398
+ * rather than at read time when a record is migrated.
399
+ */
400
+ declare function validateMigrationChain(migrations: MigrationStep[], label: string): void;
401
+ /**
402
+ * Attempt to migrate a single record based on its registry entry.
403
+ *
404
+ * Returns the original record unchanged if no migration is needed
405
+ * (no schema version, already at current version, or no migrations defined).
406
+ */
407
+ declare function migrateRecord(record: StoredGraphRecord, registry: GraphRegistry, globalWriteBack?: MigrationWriteBack): Promise<MigrationResult>;
408
+ /**
409
+ * Migrate an array of records, returning all results.
410
+ * If any single migration fails, the entire call rejects — a broken
411
+ * migration function is a bug that should surface immediately.
412
+ */
413
+ declare function migrateRecords(records: StoredGraphRecord[], registry: GraphRegistry, globalWriteBack?: MigrationWriteBack): Promise<MigrationResult[]>;
414
+
415
+ declare function buildEdgeQueryPlan(params: FindEdgesParams): QueryPlan;
416
+ declare function buildNodeQueryPlan(params: FindNodesParams): QueryPlan;
417
+
418
+ /**
419
+ * Result of analyzing a query for collection scan risk.
420
+ */
421
+ interface QuerySafetyResult {
422
+ /** Whether the query matches a known indexed pattern. */
423
+ safe: boolean;
424
+ /** Human-readable explanation when the query is unsafe. */
425
+ reason?: string;
426
+ }
427
+ /**
428
+ * Analyzes a set of query filters to determine whether the query would
429
+ * likely cause a full collection scan on Firestore Enterprise.
430
+ *
431
+ * A query is considered "safe" if the builtin fields present in the filters
432
+ * match at least one known composite index pattern. Queries that only use
433
+ * `data.*` fields without a safe base pattern are flagged as unsafe.
434
+ */
435
+ declare function analyzeQuerySafety(filters: QueryFilter[]): QuerySafetyResult;
436
+
437
+ declare function buildNodeRecord(aType: string, uid: string, data: Record<string, unknown>): GraphRecord;
438
+ declare function buildEdgeRecord(aType: string, aUid: string, axbType: string, bType: string, bUid: string, data: Record<string, unknown>): GraphRecord;
439
+
440
+ /**
441
+ * Build a registry from either explicit entries or a DiscoveryResult.
442
+ *
443
+ * @example
444
+ * ```ts
445
+ * // From explicit entries (programmatic)
446
+ * const registry = createRegistry([
447
+ * { aType: 'user', axbType: 'is', bType: 'user', jsonSchema: userSchema },
448
+ * { aType: 'user', axbType: 'follows', bType: 'user', jsonSchema: followsSchema },
449
+ * ]);
450
+ *
451
+ * // From discovery result (folder convention)
452
+ * const discovered = await discoverEntities('./entities');
453
+ * const registry = createRegistry(discovered);
454
+ * ```
455
+ */
456
+ declare function createRegistry(input: RegistryEntry[] | DiscoveryResult): GraphRegistry;
457
+ /**
458
+ * Create a merged registry where `base` entries take priority and `extension`
459
+ * entries fill in gaps. Lookups and validation check `base` first; only if the
460
+ * triple is not found there does the merged registry fall through to
461
+ * `extension`.
462
+ *
463
+ * The `entries()` method returns a deduplicated list (base wins on collision).
464
+ * The `lookupByAxbType()` method merges results from both registries,
465
+ * deduplicating by triple key with base entries winning.
466
+ */
467
+ declare function createMergedRegistry(base: GraphRegistry, extension: GraphRegistry): GraphRegistry;
468
+
295
469
  /**
296
470
  * Sandbox module for compiling dynamic registry migration source strings
297
471
  * into executable functions.
@@ -365,6 +539,37 @@ declare function compileMigrations(stored: StoredMigrationStep[], executor?: Mig
365
539
  */
366
540
  declare function destroySandboxWorker(): Promise<void>;
367
541
 
542
+ /**
543
+ * Scope path matching for subgraph-level registry constraints.
544
+ *
545
+ * Scope paths are slash-separated names derived from the chain of
546
+ * `subgraph()` calls (e.g., `'agents'`, `'agents/memories'`).
547
+ * The root graph has an empty scope path (`''`).
548
+ *
549
+ * Patterns:
550
+ * - `'root'` — matches only the root graph (empty scope path)
551
+ * - `'agents'` — matches exactly `'agents'`
552
+ * - `'agents/memories'` — matches exactly `'agents/memories'`
553
+ * - `'*​/agents'` — `*` matches one segment: `'foo/agents'` but not `'a/b/agents'`
554
+ * - `'**​/memories'` — `**` matches zero or more segments
555
+ * - `'**'` — matches everything including root
556
+ */
557
+ /**
558
+ * Test whether a scope path matches a single pattern.
559
+ *
560
+ * @param scopePath - The current scope path (empty string for root)
561
+ * @param pattern - The pattern to match against
562
+ */
563
+ declare function matchScope(scopePath: string, pattern: string): boolean;
564
+ /**
565
+ * Test whether a scope path matches any pattern in a list.
566
+ * Returns `true` if the list is empty or undefined (allowed everywhere).
567
+ *
568
+ * @param scopePath - The current scope path (empty string for root)
569
+ * @param patterns - Array of patterns to match against
570
+ */
571
+ declare function matchScopeAny(scopePath: string, patterns: string[]): boolean;
572
+
368
573
  /**
369
574
  * Firestore-aware serialization for the sandbox migration pipeline.
370
575
  *
@@ -402,109 +607,16 @@ declare function serializeFirestoreTypes(data: Record<string, unknown>): Record<
402
607
  declare function deserializeFirestoreTypes(data: Record<string, unknown>, db?: Firestore): Record<string, unknown>;
403
608
 
404
609
  /**
405
- * Migration pipeline for auto-migrating records on read.
406
- *
407
- * When a record's `v` is behind the version derived from the registry
408
- * entry's migrations, the pipeline applies migration steps sequentially
409
- * to bring the data up to the current version.
410
- */
411
-
412
- /** Result of attempting to migrate a single record. */
413
- interface MigrationResult {
414
- record: StoredGraphRecord;
415
- migrated: boolean;
416
- /** Resolved write-back mode for this record (entry-level > global > 'off'). */
417
- writeBack: MigrationWriteBack;
418
- }
419
- /**
420
- * Apply a chain of migration steps to transform data from `currentVersion`
421
- * to `targetVersion`. Throws `MigrationError` if the chain is incomplete
422
- * or a migration function fails.
423
- *
424
- * Returns the migrated data payload only — the caller is responsible for
425
- * stamping `v` on the record envelope.
426
- */
427
- declare function applyMigrationChain(data: Record<string, unknown>, currentVersion: number, targetVersion: number, migrations: MigrationStep[]): Promise<Record<string, unknown>>;
428
- /**
429
- * Validate that a migration chain forms a contiguous path from version 0
430
- * to the highest `toVersion`. Throws `MigrationError` if the chain has
431
- * gaps or duplicate `fromVersion` values.
432
- *
433
- * Called at registry construction time to catch incomplete chains early,
434
- * rather than at read time when a record is migrated.
435
- */
436
- declare function validateMigrationChain(migrations: MigrationStep[], label: string): void;
437
- /**
438
- * Attempt to migrate a single record based on its registry entry.
439
- *
440
- * Returns the original record unchanged if no migration is needed
441
- * (no schema version, already at current version, or no migrations defined).
442
- */
443
- declare function migrateRecord(record: StoredGraphRecord, registry: GraphRegistry, globalWriteBack?: MigrationWriteBack): Promise<MigrationResult>;
444
- /**
445
- * Migrate an array of records, returning all results.
446
- * If any single migration fails, the entire call rejects — a broken
447
- * migration function is a bug that should surface immediately.
448
- */
449
- declare function migrateRecords(records: StoredGraphRecord[], registry: GraphRegistry, globalWriteBack?: MigrationWriteBack): Promise<MigrationResult[]>;
450
-
451
- interface FirestoreIndexField {
452
- fieldPath: string;
453
- order: 'ASCENDING' | 'DESCENDING';
454
- }
455
- interface FirestoreIndex {
456
- collectionGroup: string;
457
- queryScope: 'COLLECTION' | 'COLLECTION_GROUP';
458
- fields: FirestoreIndexField[];
459
- }
460
- interface FirestoreIndexConfig {
461
- indexes: FirestoreIndex[];
462
- fieldOverrides: unknown[];
463
- }
464
- /**
465
- * Generates a Firestore index configuration for a firegraph collection.
466
- *
467
- * Always includes the 4 base composite indexes. If an entity discovery result
468
- * is provided, generates additional data-field indexes for common query
469
- * patterns on node data fields:
470
- * (aType, axbType, data.{field})
471
- *
472
- * When registry entries with `targetGraph` are provided, also generates
473
- * collection group indexes for `findEdgesGlobal()` queries. The collection
474
- * group name defaults to `'graph'` (the standard subgraph name) but can be
475
- * overridden per `targetGraph` value.
610
+ * Create a traversal builder for multi-hop graph traversal.
476
611
  *
477
- * @param collection - Firestore collection name (e.g. 'graph')
478
- * @param entities - Optional discovery result for per-entity data field indexes
479
- * @param registryEntries - Optional registry entries; when any have `targetGraph`,
480
- * collection group indexes are generated for the distinct subgraph names
481
- */
482
- declare function generateIndexConfig(collection: string, entities?: DiscoveryResult, registryEntries?: ReadonlyArray<RegistryEntry>): FirestoreIndexConfig;
483
-
484
- /**
485
- * Result of analyzing a query for collection scan risk.
486
- */
487
- interface QuerySafetyResult {
488
- /** Whether the query matches a known indexed pattern. */
489
- safe: boolean;
490
- /** Human-readable explanation when the query is unsafe. */
491
- reason?: string;
492
- }
493
- /**
494
- * Analyzes a set of query filters to determine whether the query would
495
- * likely cause a full collection scan on Firestore Enterprise.
612
+ * Accepts either a `GraphReader` (backwards compatible) or a `GraphClient`.
613
+ * When a `GraphClient` is provided, cross-graph traversal via `targetGraph`
614
+ * is supported the traversal can follow edges into subgraphs.
496
615
  *
497
- * A query is considered "safe" if the builtin fields present in the filters
498
- * match at least one known composite index pattern. Queries that only use
499
- * `data.*` fields without a safe base pattern are flagged as unsafe.
500
- */
501
- declare function analyzeQuerySafety(filters: QueryFilter[]): QuerySafetyResult;
502
-
503
- /**
504
- * Default result limit applied to findEdges/findNodes queries
505
- * when no explicit limit is provided. Prevents unbounded result sets
506
- * that could be expensive on Enterprise Firestore.
616
+ * @param reader - A `GraphClient` or `GraphReader` to execute queries against
617
+ * @param startUid - UID of the starting node
618
+ * @param registry - Optional registry for automatic `targetGraph` resolution
507
619
  */
508
- declare const DEFAULT_QUERY_LIMIT = 500;
620
+ declare function createTraversal(reader: GraphClient | GraphReader, startUid: string, registry?: GraphRegistry): TraversalBuilder;
509
621
 
510
- export { BOOTSTRAP_ENTRIES, DEFAULT_QUERY_LIMIT, type DiscoverResult, DiscoveryError, DiscoveryResult, type DiscoveryWarning, DynamicGraphClient, DynamicRegistryConfig, DynamicRegistryError, EDGE_TYPE_SCHEMA, EdgeNotFoundError, type FieldMeta, FindEdgesParams, FindNodesParams, FiregraphError, type FirestoreIndex, type FirestoreIndexConfig, type FirestoreIndexField, GraphClient, GraphClientOptions, GraphReader, GraphRecord, GraphRegistry, InvalidQueryError, META_EDGE_TYPE, META_NODE_TYPE, MigrationError, MigrationExecutor, MigrationFn, type MigrationResult, MigrationStep, MigrationWriteBack, NODE_TYPE_SCHEMA, NodeNotFoundError, QueryFilter, QueryPlan, QuerySafetyError, type QuerySafetyResult, RegistryEntry, RegistryScopeError, RegistryViolationError, SERIALIZATION_TAG, StoredGraphRecord, StoredMigrationStep, TraversalBuilder, TraversalError, ValidationError, analyzeQuerySafety, applyMigrationChain, buildEdgeQueryPlan, buildEdgeRecord, buildNodeQueryPlan, buildNodeRecord, compileMigrationFn, compileMigrations, compileSchema, computeEdgeDocId, computeNodeDocId, createBootstrapRegistry, createGraphClient, createMergedRegistry, createRegistry, createRegistryFromGraph, createTraversal, defaultExecutor, deserializeFirestoreTypes, destroySandboxWorker, discoverEntities, generateDeterministicUid, generateId, generateIndexConfig, isAncestorUid, isTaggedValue, jsonSchemaToFieldMeta, matchScope, matchScopeAny, migrateRecord, migrateRecords, precompileSource, resolveAncestorCollection, serializeFirestoreTypes, validateMigrationChain };
622
+ export { BOOTSTRAP_ENTRIES, BulkOptions, BulkResult, CascadeResult, DEFAULT_QUERY_LIMIT, type DiscoverResult, DiscoveryError, DiscoveryResult, type DiscoveryWarning, DynamicGraphClient, DynamicRegistryConfig, DynamicRegistryError, EDGE_TYPE_SCHEMA, EdgeNotFoundError, type FieldMeta, FindEdgesParams, FindNodesParams, FiregraphError, type FirestoreIndex, type FirestoreIndexConfig, type FirestoreIndexField, GraphClient, GraphClientOptions, GraphReader, GraphRecord, GraphRegistry, InvalidQueryError, META_EDGE_TYPE, META_NODE_TYPE, MigrationError, MigrationExecutor, MigrationFn, type MigrationResult, MigrationStep, MigrationWriteBack, NODE_TYPE_SCHEMA, NodeNotFoundError, QueryFilter, QueryOptions, QueryPlan, QuerySafetyError, type QuerySafetyResult, RegistryEntry, RegistryScopeError, RegistryViolationError, SERIALIZATION_TAG, StoredGraphRecord, StoredMigrationStep, TraversalBuilder, TraversalError, ValidationError, analyzeQuerySafety, applyMigrationChain, buildEdgeQueryPlan, buildEdgeRecord, buildNodeQueryPlan, buildNodeRecord, compileMigrationFn, compileMigrations, compileSchema, computeEdgeDocId, computeNodeDocId, createBootstrapRegistry, createGraphClient, createGraphClientFromBackend, createMergedRegistry, createRegistry, createRegistryFromGraph, createTraversal, defaultExecutor, deserializeFirestoreTypes, destroySandboxWorker, discoverEntities, generateDeterministicUid, generateId, generateIndexConfig, isAncestorUid, isTaggedValue, jsonSchemaToFieldMeta, matchScope, matchScopeAny, migrateRecord, migrateRecords, precompileSource, resolveAncestorCollection, serializeFirestoreTypes, validateMigrationChain };