@typicalday/firegraph 0.15.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +39 -17
  2. package/dist/{backend-CvImIwTY.d.cts → backend-CE3pM9-T.d.ts} +32 -2
  3. package/dist/{backend-BpYLdwCW.d.cts → backend-DNzv8KSR.d.cts} +33 -19
  4. package/dist/{backend-BpYLdwCW.d.ts → backend-DNzv8KSR.d.ts} +33 -19
  5. package/dist/{backend-YH5HtawN.d.ts → backend-EjFfw9yO.d.cts} +32 -2
  6. package/dist/backend.cjs.map +1 -1
  7. package/dist/backend.d.cts +2 -2
  8. package/dist/backend.d.ts +2 -2
  9. package/dist/backend.js +1 -1
  10. package/dist/{chunk-FODIMIWY.js → chunk-5JBNLH5W.js} +17 -6
  11. package/dist/chunk-5JBNLH5W.js.map +1 -0
  12. package/dist/{chunk-5HIRYV2S.js → chunk-6IO74NKD.js} +12 -10
  13. package/dist/{chunk-5HIRYV2S.js.map → chunk-6IO74NKD.js.map} +1 -1
  14. package/dist/{chunk-ULRDQ6HZ.js → chunk-NZVSLWNY.js} +6 -1
  15. package/dist/chunk-NZVSLWNY.js.map +1 -0
  16. package/dist/{chunk-N5HFDWQX.js → chunk-PWIO46RT.js} +1 -1
  17. package/dist/{chunk-N5HFDWQX.js.map → chunk-PWIO46RT.js.map} +1 -1
  18. package/dist/{client-B5o39X79.d.ts → client-CNAwJayO.d.ts} +1 -1
  19. package/dist/{client-BGHwxwPg.d.cts → client-CaXH5D5C.d.cts} +1 -1
  20. package/dist/cloudflare/index.cjs +11 -9
  21. package/dist/cloudflare/index.cjs.map +1 -1
  22. package/dist/cloudflare/index.d.cts +3 -3
  23. package/dist/cloudflare/index.d.ts +3 -3
  24. package/dist/cloudflare/index.js +3 -3
  25. package/dist/codegen/index.d.cts +1 -1
  26. package/dist/codegen/index.d.ts +1 -1
  27. package/dist/firestore-enterprise/index.cjs +11 -9
  28. package/dist/firestore-enterprise/index.cjs.map +1 -1
  29. package/dist/firestore-enterprise/index.d.cts +3 -3
  30. package/dist/firestore-enterprise/index.d.ts +3 -3
  31. package/dist/firestore-enterprise/index.js +2 -2
  32. package/dist/firestore-standard/index.cjs +11 -9
  33. package/dist/firestore-standard/index.cjs.map +1 -1
  34. package/dist/firestore-standard/index.d.cts +3 -3
  35. package/dist/firestore-standard/index.d.ts +3 -3
  36. package/dist/firestore-standard/index.js +2 -2
  37. package/dist/index.cjs +11 -9
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +4 -4
  40. package/dist/index.d.ts +4 -4
  41. package/dist/index.js +1 -1
  42. package/dist/{registry-tKTb5Kx1.d.ts → registry-By1i-zge.d.ts} +1 -1
  43. package/dist/{registry-BGh7Jqpb.d.cts → registry-CNToyEra.d.cts} +1 -1
  44. package/dist/sqlite/index.cjs +24 -12
  45. package/dist/sqlite/index.cjs.map +1 -1
  46. package/dist/sqlite/index.d.cts +4 -4
  47. package/dist/sqlite/index.d.ts +4 -4
  48. package/dist/sqlite/index.js +4 -4
  49. package/dist/sqlite/local.cjs +484 -47
  50. package/dist/sqlite/local.cjs.map +1 -1
  51. package/dist/sqlite/local.d.cts +31 -5
  52. package/dist/sqlite/local.d.ts +31 -5
  53. package/dist/sqlite/local.js +439 -4
  54. package/dist/sqlite/local.js.map +1 -1
  55. package/package.json +1 -1
  56. package/dist/chunk-FODIMIWY.js.map +0 -1
  57. package/dist/chunk-ULRDQ6HZ.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/sqlite/local.ts","../../src/errors.ts","../../src/docid.ts","../../src/internal/constants.ts","../../src/internal/backend.ts","../../src/default-indexes.ts","../../src/internal/sqlite-index-ddl.ts","../../src/internal/sqlite-schema.ts","../../src/timestamp.ts","../../src/internal/sqlite-data-ops.ts","../../src/internal/serialization-tag.ts","../../src/internal/write-plan.ts","../../src/internal/sqlite-payload-guard.ts","../../src/internal/sqlite-sql.ts","../../src/sqlite/catalog.ts","../../src/sqlite/backend.ts"],"sourcesContent":["/**\n * Local SQLite backend over `better-sqlite3`.\n *\n * This entry point is published as `firegraph/sqlite-local` and is the only\n * module in the library that references `better-sqlite3` — keep it out of\n * `firegraph/sqlite` so that D1 / workerd bundles never see the native\n * dependency. `better-sqlite3` is loaded via dynamic `import()` at factory\n * call time, so merely importing this module stays side-effect free.\n *\n * The factory accepts either a database file path (`':memory:'` works) or an\n * already-open `better-sqlite3` Database. Path-opened databases get\n * `journal_mode = WAL` and a `busy_timeout` applied; caller-provided\n * databases are used as-is (only `busy_timeout` is set) since the caller\n * owns their pragma configuration.\n */\n\nimport type { Database as BetterSqliteDb, default as BetterSqliteDatabase } from 'better-sqlite3';\n\nimport { FiregraphError } from '../errors.js';\nimport type { StorageBackend } from '../internal/backend.js';\nimport type { SqliteExecutor, SqliteTxExecutor } from '../internal/sqlite-executor.js';\nimport type { SqliteBackendOptions, SqliteCapability } from './backend.js';\nimport { createSqliteBackend } from './backend.js';\n\nexport interface LocalSqliteBackendOptions extends SqliteBackendOptions {\n /** Root graph table name. Defaults to `'firegraph'`. */\n tableName?: string;\n /**\n * `PRAGMA busy_timeout` in milliseconds — how long a connection waits on a\n * lock held by another process before erroring. Defaults to 5000.\n */\n busyTimeoutMs?: number;\n /**\n * Extra pragmas applied after the defaults, e.g.\n * `{ synchronous: 'NORMAL', cache_size: -64000 }`. Applied in object\n * order via `PRAGMA <key> = <value>`.\n */\n pragmas?: Record<string, string | number>;\n /**\n * When opening by path: throw if the file does not already exist instead\n * of creating it. Defaults to false.\n */\n fileMustExist?: boolean;\n}\n\nexport interface LocalSqliteBackend {\n /** The graph storage backend — pass to `createGraphClient`. */\n backend: StorageBackend<SqliteCapability>;\n /** The underlying better-sqlite3 database, for raw access. */\n db: BetterSqliteDb;\n /**\n * Close the database. No-op when the factory was given an already-open\n * Database (the caller owns its lifecycle).\n */\n close(): void;\n}\n\n/**\n * Build a transaction-capable `SqliteExecutor` over a better-sqlite3\n * Database. Interactive transactions use manual `BEGIN IMMEDIATE` /\n * `COMMIT` / `ROLLBACK` because `db.transaction()` requires a synchronous\n * callback while `SqliteExecutor.transaction` callbacks are async.\n *\n * Exported for callers that want to wire `createSqliteBackend` directly\n * (e.g. to share one executor across several root tables).\n */\nexport function createBetterSqliteExecutor(db: BetterSqliteDb): SqliteExecutor {\n return {\n async all(sql: string, params: unknown[]): Promise<Record<string, unknown>[]> {\n return db.prepare(sql).all(...params) as Record<string, unknown>[];\n },\n async run(sql: string, params: unknown[]): Promise<void> {\n db.prepare(sql).run(...params);\n },\n async batch(statements): Promise<void> {\n const tx = db.transaction((stmts: typeof statements) => {\n for (const s of stmts) {\n db.prepare(s.sql).run(...s.params);\n }\n });\n tx(statements);\n },\n async transaction<T>(fn: (tx: SqliteTxExecutor) => Promise<T>): Promise<T> {\n db.exec('BEGIN IMMEDIATE');\n try {\n const result = await fn({\n async all(sql: string, params: unknown[]) {\n return db.prepare(sql).all(...params) as Record<string, unknown>[];\n },\n async run(sql: string, params: unknown[]) {\n db.prepare(sql).run(...params);\n },\n });\n db.exec('COMMIT');\n return result;\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n },\n };\n}\n\nfunction isDatabase(value: unknown): value is BetterSqliteDb {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { prepare?: unknown }).prepare === 'function' &&\n typeof (value as { exec?: unknown }).exec === 'function'\n );\n}\n\nconst PRAGMA_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;\n// Pragma values are identifiers (WAL, NORMAL) or integers — never compound\n// expressions, so anything else is rejected rather than interpolated.\nconst PRAGMA_VALUE_PATTERN = /^-?[A-Za-z0-9_]+$/;\n\nfunction applyPragmas(db: BetterSqliteDb, pragmas: Record<string, string | number>): void {\n for (const [key, value] of Object.entries(pragmas)) {\n if (!PRAGMA_KEY_PATTERN.test(key)) {\n throw new FiregraphError(`Invalid pragma name: ${JSON.stringify(key)}`, 'INVALID_ARGUMENT');\n }\n if (\n !PRAGMA_VALUE_PATTERN.test(String(value)) ||\n (typeof value === 'number' && !Number.isFinite(value))\n ) {\n throw new FiregraphError(\n `Invalid pragma value for ${key}: ${JSON.stringify(value)}`,\n 'INVALID_ARGUMENT',\n );\n }\n db.pragma(`${key} = ${value}`);\n }\n}\n\n/**\n * Open (or wrap) a local SQLite database and return a graph storage backend\n * over it.\n *\n * ```typescript\n * import { createLocalSqliteBackend } from 'firegraph/sqlite-local';\n * import { createGraphClient } from 'firegraph/sqlite';\n *\n * const { backend, close } = await createLocalSqliteBackend('./graph.db');\n * const client = createGraphClient(backend);\n * // ... use the client ...\n * close();\n * ```\n *\n * Requires `better-sqlite3` to be installed (declared as an optional peer\n * dependency). The factory is async because the driver is loaded via\n * dynamic `import()`.\n */\nexport async function createLocalSqliteBackend(\n pathOrDb: string | BetterSqliteDb,\n options: LocalSqliteBackendOptions = {},\n): Promise<LocalSqliteBackend> {\n const {\n tableName = 'firegraph',\n busyTimeoutMs = 5000,\n pragmas,\n fileMustExist,\n ...backendOptions\n } = options;\n\n let db: BetterSqliteDb;\n let ownsDb: boolean;\n if (typeof pathOrDb === 'string') {\n let Database: typeof BetterSqliteDatabase;\n try {\n Database = (await import('better-sqlite3')).default;\n } catch (err) {\n throw new FiregraphError(\n `createLocalSqliteBackend requires the optional peer dependency 'better-sqlite3' — install it to use the local SQLite backend (${\n err instanceof Error ? err.message : String(err)\n })`,\n 'MISSING_DEPENDENCY',\n );\n }\n db = new Database(pathOrDb, fileMustExist ? { fileMustExist: true } : {});\n ownsDb = true;\n // WAL lets concurrent readers coexist with a writer — the right default\n // for a long-lived local graph file. On ':memory:' databases SQLite\n // reports 'memory' and ignores the request, which is fine.\n db.pragma('journal_mode = WAL');\n } else if (isDatabase(pathOrDb)) {\n db = pathOrDb;\n ownsDb = false;\n } else {\n throw new FiregraphError(\n 'createLocalSqliteBackend expects a file path or an open better-sqlite3 Database',\n 'INVALID_ARGUMENT',\n );\n }\n\n db.pragma(`busy_timeout = ${Math.max(0, Math.floor(busyTimeoutMs))}`);\n if (pragmas) {\n applyPragmas(db, pragmas);\n }\n\n const backend = createSqliteBackend(createBetterSqliteExecutor(db), tableName, backendOptions);\n let closed = false;\n return {\n backend,\n db,\n close(): void {\n if (closed || !ownsDb) return;\n closed = true;\n db.close();\n },\n };\n}\n","export class FiregraphError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n ) {\n super(message);\n this.name = 'FiregraphError';\n }\n}\n\nexport class NodeNotFoundError extends FiregraphError {\n constructor(uid: string) {\n super(`Node not found: ${uid}`, 'NODE_NOT_FOUND');\n this.name = 'NodeNotFoundError';\n }\n}\n\nexport class EdgeNotFoundError extends FiregraphError {\n constructor(aUid: string, axbType: string, bUid: string) {\n super(`Edge not found: ${aUid} -[${axbType}]-> ${bUid}`, 'EDGE_NOT_FOUND');\n this.name = 'EdgeNotFoundError';\n }\n}\n\nexport class ValidationError extends FiregraphError {\n constructor(\n message: string,\n public readonly details?: unknown,\n ) {\n super(message, 'VALIDATION_ERROR');\n this.name = 'ValidationError';\n }\n}\n\nexport class RegistryViolationError extends FiregraphError {\n constructor(aType: string, axbType: string, bType: string) {\n super(`Unregistered triple: (${aType}) -[${axbType}]-> (${bType})`, 'REGISTRY_VIOLATION');\n this.name = 'RegistryViolationError';\n }\n}\n\nexport class InvalidQueryError extends FiregraphError {\n constructor(message: string) {\n super(message, 'INVALID_QUERY');\n this.name = 'InvalidQueryError';\n }\n}\n\nexport class TraversalError extends FiregraphError {\n constructor(message: string) {\n super(message, 'TRAVERSAL_ERROR');\n this.name = 'TraversalError';\n }\n}\n\nexport class DynamicRegistryError extends FiregraphError {\n constructor(message: string) {\n super(message, 'DYNAMIC_REGISTRY_ERROR');\n this.name = 'DynamicRegistryError';\n }\n}\n\nexport class QuerySafetyError extends FiregraphError {\n constructor(message: string) {\n super(message, 'QUERY_SAFETY');\n this.name = 'QuerySafetyError';\n }\n}\n\nexport class RegistryScopeError extends FiregraphError {\n constructor(\n aType: string,\n axbType: string,\n bType: string,\n scopePath: string,\n allowedIn: string[],\n ) {\n super(\n `Type (${aType}) -[${axbType}]-> (${bType}) is not allowed at scope \"${scopePath || 'root'}\". ` +\n `Allowed in: [${allowedIn.join(', ')}]`,\n 'REGISTRY_SCOPE',\n );\n this.name = 'RegistryScopeError';\n }\n}\n\nexport class MigrationError extends FiregraphError {\n constructor(message: string) {\n super(message, 'MIGRATION_ERROR');\n this.name = 'MigrationError';\n }\n}\n\n/**\n * Thrown when a caller tries to perform an operation that would require\n * atomicity across two physical storage backends — e.g. opening a routed\n * subgraph client from inside a transaction callback. Cross-backend\n * atomicity cannot be honoured by real-world storage engines (Firestore,\n * SQLite drivers over D1/DO/better-sqlite3, etc.), so firegraph surfaces\n * this as a typed error instead of silently confining the write to the\n * base backend.\n *\n * Normally `TransactionBackend` and `BatchBackend` don't expose `subgraph()`\n * at the type level, so this error is unreachable through well-typed code.\n * It exists as a public catchable type for app code that needs to tolerate\n * this case deliberately (e.g. dynamic code paths that bypass the type\n * system) and as future-proofing if the interface ever grows a way to\n * request a sub-scope inside a transaction.\n */\nexport class CrossBackendTransactionError extends FiregraphError {\n constructor(message: string) {\n super(message, 'CROSS_BACKEND_TRANSACTION');\n this.name = 'CrossBackendTransactionError';\n }\n}\n\n/**\n * Thrown when a caller invokes a capability-gated operation on a backend\n * that does not declare the required capability. Capability gating is\n * primarily a compile-time concern (see `BackendCapabilities` and the\n * type-level extension surfaces in `GraphClient<C>`), but this runtime\n * error covers the cases where the type system is bypassed — dynamic\n * registries, `as any` casts, or callers explicitly downcasting through\n * the generic-erased `StorageBackend` shape.\n *\n * The error code is `CAPABILITY_NOT_SUPPORTED`. The message names the\n * missing capability and the backend that was asked, so app code can\n * diagnose without inspecting the cap set itself.\n */\nexport class CapabilityNotSupportedError extends FiregraphError {\n constructor(\n public readonly capability: string,\n backendDescription: string,\n ) {\n super(\n `Capability \"${capability}\" is not supported by ${backendDescription}.`,\n 'CAPABILITY_NOT_SUPPORTED',\n );\n this.name = 'CapabilityNotSupportedError';\n }\n}\n","import { createHash } from 'node:crypto';\n\nimport { SHARD_SEPARATOR } from './internal/constants.js';\n\nexport function computeNodeDocId(uid: string): string {\n return uid;\n}\n\nexport function computeEdgeDocId(aUid: string, axbType: string, bUid: string): string {\n const composite = `${aUid}${SHARD_SEPARATOR}${axbType}${SHARD_SEPARATOR}${bUid}`;\n const hash = createHash('sha256').update(composite).digest('hex');\n const shard = hash[0];\n return `${shard}${SHARD_SEPARATOR}${aUid}${SHARD_SEPARATOR}${axbType}${SHARD_SEPARATOR}${bUid}`;\n}\n","export const NODE_RELATION = 'is';\n\n/**\n * Default result limit applied to findEdges/findNodes queries\n * when no explicit limit is provided. Prevents unbounded result sets\n * that could be expensive on Enterprise Firestore.\n */\nexport const DEFAULT_QUERY_LIMIT = 500;\n\n/**\n * Fields that are part of the firegraph record structure (not user data).\n * Used by the query planner and safety analysis to distinguish builtin\n * fields from data.* fields.\n */\nexport const BUILTIN_FIELDS = new Set([\n 'aType',\n 'aUid',\n 'axbType',\n 'bType',\n 'bUid',\n 'createdAt',\n 'updatedAt',\n]);\n\nexport const SHARD_ALGORITHM = 'sha256';\nexport const SHARD_SEPARATOR = ':';\nexport const SHARD_BUCKETS = 16;\n","/**\n * Backend abstraction for firegraph.\n *\n * `StorageBackend` is the single interface every storage driver implements.\n * The Firestore backend wraps `@google-cloud/firestore`; the SQLite backend\n * (shared by D1 and Durable Object SQLite) uses a parameterized SQL executor.\n *\n * `GraphClientImpl` and friends depend only on this interface — they have\n * no direct knowledge of Firestore or SQLite.\n */\n\nimport type {\n AggregateSpec,\n BulkOptions,\n BulkResult,\n BulkUpdatePatch,\n Capability,\n CascadeResult,\n EngineTraversalParams,\n EngineTraversalResult,\n ExpandParams,\n ExpandResult,\n FindEdgesParams,\n FindNearestParams,\n FullTextSearchParams,\n GeoSearchParams,\n GraphReader,\n QueryFilter,\n QueryOptions,\n StoredGraphRecord,\n} from '../types.js';\nimport type { DataPathOp } from './write-plan.js';\n\n/**\n * Runtime descriptor of which `Capability`s a `StorageBackend` actually\n * implements. Static for the lifetime of a backend instance; declared at\n * construction. The phantom `_phantom` field is a type-level marker\n * (never read at runtime) that lets the type parameter `C` flow through\n * the descriptor for use by `GraphClient<C>` conditional gating.\n *\n * Use `createCapabilities` to construct one. Use `.has(c)` to check\n * membership at runtime; the type system gates extension methods on the\n * client level (see `.claude/backend-capabilities.md`).\n */\nexport interface BackendCapabilities<C extends Capability = Capability> {\n /** Runtime membership check. */\n has(capability: Capability): boolean;\n /** Iterate declared capabilities (diagnostics, error messages). */\n values(): IterableIterator<Capability>;\n /** Type-level marker. Never read at runtime. */\n readonly _phantom?: C;\n}\n\n/**\n * Construct a `BackendCapabilities<C>` from an explicit set. The set is\n * captured by reference; callers should treat it as readonly after passing\n * it in. The runtime cost of `has()` is one Set lookup.\n */\nexport function createCapabilities<C extends Capability>(\n caps: ReadonlySet<C>,\n): BackendCapabilities<C> {\n return {\n has: (capability: Capability): boolean => caps.has(capability as C),\n values: () => caps.values() as IterableIterator<Capability>,\n };\n}\n\n/**\n * Intersect multiple capability sets. Used by `RoutingStorageBackend` to\n * derive the capability set of a composite backend: a routed graph can\n * only honour a capability if every wrapped backend honours it.\n */\nexport function intersectCapabilities(\n parts: ReadonlyArray<BackendCapabilities>,\n): BackendCapabilities {\n if (parts.length === 0) return createCapabilities(new Set<Capability>());\n const sets = parts.map((p) => new Set<Capability>(p.values()));\n const [first, ...rest] = sets;\n const intersection = new Set<Capability>();\n for (const c of first) {\n if (rest.every((s) => s.has(c))) intersection.add(c);\n }\n return createCapabilities(intersection);\n}\n\n/**\n * Per-record write payload — backend-agnostic. Timestamps are not present;\n * the backend supplies them via `serverTimestamp()` placeholders that it\n * itself resolves at commit time.\n */\nexport interface WritableRecord {\n aType: string;\n aUid: string;\n axbType: string;\n bType: string;\n bUid: string;\n data: Record<string, unknown>;\n /** Schema version (set by the writer when registry has migrations). */\n v?: number;\n}\n\n/**\n * Write semantics for `setDoc`.\n *\n * - `'merge'` — the new contract (0.12+). Existing fields not mentioned\n * in the new data survive; nested objects are recursively merged;\n * arrays are replaced as a unit. This is the default for\n * `putNode` / `putEdge`.\n * - `'replace'` — the document is replaced wholesale, dropping any\n * fields not present in the payload. This is the explicit escape\n * hatch surfaced as `replaceNode` / `replaceEdge` and used by\n * migration write-back.\n */\nexport type WriteMode = 'merge' | 'replace';\n\n/**\n * Patch shape for `updateDoc`.\n *\n * - `dataOps`: list of deep-path terminal ops produced by\n * `flattenPatch()` (one op per leaf — arrays / primitives / Firestore\n * special types are terminal). Used by `updateNode` / `updateEdge`.\n * Sibling keys at every depth are preserved.\n * - `replaceData`: full `data` replacement. Used only by the migration\n * write-back path, which has already produced a complete migrated\n * document.\n * - `v`: optional schema-version stamp.\n *\n * `updatedAt` is always set by the backend.\n */\nexport interface UpdatePayload {\n dataOps?: DataPathOp[];\n replaceData?: Record<string, unknown>;\n v?: number;\n}\n\n/**\n * Read/write transaction adapter. Mirrors Firestore's transaction semantics:\n * reads are snapshot-consistent; writes are issued inside the transaction\n * and a rejection from any write aborts the surrounding `runTransaction`.\n *\n * Writes return `Promise<void>` so SQL drivers can surface row-level errors\n * (constraint violations, malformed JSON paths) rather than swallowing them.\n * Firestore implementations can resolve synchronously since the underlying\n * `Transaction.set/update/delete` calls are themselves synchronous buffers.\n */\nexport interface TransactionBackend {\n getDoc(docId: string): Promise<StoredGraphRecord | null>;\n query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]>;\n setDoc(docId: string, record: WritableRecord, mode: WriteMode): Promise<void>;\n updateDoc(docId: string, update: UpdatePayload): Promise<void>;\n deleteDoc(docId: string): Promise<void>;\n}\n\n/**\n * Atomic multi-write batch.\n */\nexport interface BatchBackend {\n setDoc(docId: string, record: WritableRecord, mode: WriteMode): void;\n updateDoc(docId: string, update: UpdatePayload): void;\n deleteDoc(docId: string): void;\n commit(): Promise<void>;\n}\n\n/**\n * The single storage abstraction.\n *\n * Each backend instance is scoped to a \"graph location\" — for Firestore\n * that's a collection path; for SQLite it's a (table, scopePath) pair.\n * `subgraph()` returns a child backend bound to a nested location.\n */\nexport interface StorageBackend<C extends Capability = Capability> {\n /** Capabilities this backend instance declares. Static for the lifetime of the backend. */\n readonly capabilities: BackendCapabilities<C>;\n /** Backend-internal location identifier (collection path or table name). */\n readonly collectionPath: string;\n /** Subgraph scope (empty string for root). */\n readonly scopePath: string;\n\n // --- Reads ---\n getDoc(docId: string): Promise<StoredGraphRecord | null>;\n query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]>;\n\n // --- Writes ---\n setDoc(docId: string, record: WritableRecord, mode: WriteMode): Promise<void>;\n updateDoc(docId: string, update: UpdatePayload): Promise<void>;\n deleteDoc(docId: string): Promise<void>;\n\n // --- Transactions & batches ---\n runTransaction<T>(fn: (tx: TransactionBackend) => Promise<T>): Promise<T>;\n createBatch(): BatchBackend;\n\n // --- Subgraphs ---\n subgraph(parentNodeUid: string, name: string): StorageBackend;\n\n // --- Cascade & bulk ---\n removeNodeCascade(\n uid: string,\n reader: GraphReader,\n options?: BulkOptions,\n ): Promise<CascadeResult>;\n bulkRemoveEdges(\n params: FindEdgesParams,\n reader: GraphReader,\n options?: BulkOptions,\n ): Promise<BulkResult>;\n\n // --- Cross-collection queries ---\n /**\n * Find edges across all subgraphs sharing a given collection name.\n * Optional — backends that can't support this should throw a clear error.\n */\n findEdgesGlobal?(params: FindEdgesParams, collectionName?: string): Promise<StoredGraphRecord[]>;\n\n // --- Aggregations ---\n /**\n * Run an aggregate query (count/sum/avg/min/max). Present only on backends\n * that declare `query.aggregate`. The map's keys are caller-defined aliases\n * matching `AggregateSpec`; values are the resolved numeric results.\n *\n * Backends that can't satisfy a particular op throw `FiregraphError` with\n * code `UNSUPPORTED_AGGREGATE` (e.g. Firestore Standard rejects min/max).\n */\n aggregate?(spec: AggregateSpec, filters: QueryFilter[]): Promise<Record<string, number>>;\n\n // --- Server-side DML ---\n /**\n * Delete every row matching `filters` in one server-side statement.\n * Present only on backends that declare `query.dml`. The default cascade\n * implementation in `bulk.ts` uses this when available; backends without\n * the cap (e.g. Firestore Standard) fall back to a fetch-then-delete\n * loop driven by `findEdges` + per-row `deleteDoc`.\n *\n * The contract matches `findEdges`: scope predicates are honoured\n * automatically by the backend's own internal scope tracking. Callers\n * supply only the filter list — the same shape produced by\n * `buildEdgeQueryPlan`.\n */\n bulkDelete?(filters: QueryFilter[], options?: BulkOptions): Promise<BulkResult>;\n /**\n * Update every row matching `filters` with `patch` in one server-side\n * statement. The patch is deep-merged into each row's `data` field, the\n * same flatten-then-merge pipeline `updateDoc` uses. Identifying columns\n * (`aType`, `axbType`, `aUid`, `bType`, `bUid`, `v`) are not writable\n * through this path.\n */\n bulkUpdate?(\n filters: QueryFilter[],\n patch: BulkUpdatePatch,\n options?: BulkOptions,\n ): Promise<BulkResult>;\n\n // --- Server-side multi-source fan-out ---\n /**\n * Fan out from `params.sources` over a single edge type in one server-side\n * round trip. Present only on backends that declare `query.join`. The\n * traversal layer (`traverse.ts`) calls `expand` once per hop when the\n * backend declares the cap; otherwise it falls back to the per-source\n * `findEdges` loop.\n *\n * Cross-graph hops are never dispatched through `expand` — each source\n * UID resolves to a distinct subgraph location, which can't be fanned\n * out as a single statement. The traversal layer enforces that\n * boundary; `expand` itself does not need to inspect `targetGraph`.\n */\n expand?(params: ExpandParams): Promise<ExpandResult>;\n\n // --- Engine-level multi-hop traversal ---\n /**\n * Compile a multi-hop traversal spec into one server-side query and\n * dispatch a single round trip. Present only on backends that declare\n * `traversal.serverSide` (Firestore Enterprise today, via nested\n * Pipelines that combine `define`, `addFields`, and\n * `toArrayExpression`).\n *\n * The traversal layer (`traverse.ts`) compiles a `TraversalBuilder`\n * spec into `EngineTraversalParams` only when the spec is eligible\n * (no cross-graph hops, no JS filters, depth ≤ `MAX_PIPELINE_DEPTH`,\n * `Π(limitPerSource_i × N_i) ≤ maxReads`, `limitPerSource` set on\n * every hop). Ineligible specs fall back to the per-hop `expand()`\n * loop without invoking this method.\n *\n * The result collapses the nested-pipeline tree into per-hop edge\n * arrays so the traversal layer can fold the result into the same\n * `HopResult[]` shape it produces from the per-hop loop.\n */\n runEngineTraversal?(params: EngineTraversalParams): Promise<EngineTraversalResult>;\n\n // --- Server-side projection ---\n /**\n * Run a projecting query — return only the listed fields per row. Present\n * only on backends that declare `query.select`. The cap-less fallback is\n * `findEdges` followed by a JS-side projection in user code; firegraph\n * does not auto-fall-back because the wire-payload reduction is the only\n * reason to call this method.\n *\n * `select` is the explicit field list; `filters` and `options` mirror the\n * `query()` shape. The returned rows have one slot per unique entry in\n * `select`. Field-name interpretation is the backend's responsibility:\n * built-in fields resolve to columns / Firestore field names, bare names\n * resolve to `data.<name>`, and dotted paths resolve verbatim. See\n * `FindEdgesProjectedParams` for the user-facing contract.\n *\n * Migrations are not applied to the result — the caller asked for a\n * specific projection shape, and rehydrating a partial record into the\n * migration pipeline would require synthesising every absent field.\n */\n findEdgesProjected?(\n select: ReadonlyArray<string>,\n filters: QueryFilter[],\n options?: QueryOptions,\n ): Promise<Array<Record<string, unknown>>>;\n\n // --- Native vector / nearest-neighbour search ---\n /**\n * Run a vector / nearest-neighbour query. Present only on backends that\n * declare `search.vector`. There is no client-side fallback — the\n * SQLite-shaped backends (shared SQLite, Cloudflare DO) genuinely have\n * no native ANN index, and a JS-side k-NN sweep over `findEdges()` would\n * scale catastrophically. Backends without the cap throw\n * `UNSUPPORTED_OPERATION` from the client wrapper.\n *\n * `params` carries the user-facing shape (vector field path, query\n * vector, distance metric, optional threshold and result-field). The\n * client wrapper has already run scan-protection on the identifying\n * / `where` filter list before dispatching.\n *\n * Path normalisation is the backend's responsibility: rewriting bare\n * `vectorField` / `distanceResultField` names to `data.<name>` and\n * rejecting envelope fields (`aType`, `axbType`, `bType`, `aUid`,\n * `bUid`, `v`, etc.) with `INVALID_QUERY` happens inside the\n * backend, not the client wrapper. The two in-tree Firestore-edition\n * backends share `runFirestoreFindNearest` (see\n * `src/internal/firestore-vector.ts`) for this; third-party backends\n * declaring `search.vector` must apply equivalent normalisation\n * before calling their underlying SDK.\n *\n * The backend is also responsible for translating to the underlying\n * SDK call (`Query.findNearest` on Firestore today) and decoding the\n * result snapshot into `StoredGraphRecord[]`.\n *\n * Migrations are not applied to the result. The vector index walks the\n * raw stored shape; rehydrating into the migration pipeline before\n * returning would change the candidate set the index already chose.\n */\n findNearest?(params: FindNearestParams): Promise<StoredGraphRecord[]>;\n\n // --- Native full-text search ---\n /**\n * Run a full-text search query. Present only on backends that declare\n * `search.fullText`. There is no client-side fallback — the only\n * in-tree backend that supports it is Firestore Enterprise (via\n * Pipeline `search({ query: documentMatches(...) })`); Standard and\n * the SQLite-shaped backends throw `UNSUPPORTED_OPERATION` from the\n * client wrapper.\n *\n * The backend is responsible for path normalisation (rewriting\n * bare `fields` entries to `data.<name>`, rejecting envelope fields\n * with `INVALID_QUERY`), translating to the underlying SDK call,\n * and decoding the result into `StoredGraphRecord[]`.\n *\n * Migrations are not applied to the result. The search index walked\n * the raw stored shape; rehydrating into the migration pipeline\n * would change the candidate set the index already scored.\n */\n fullTextSearch?(params: FullTextSearchParams): Promise<StoredGraphRecord[]>;\n\n // --- Native geospatial distance search ---\n /**\n * Run a geospatial distance search. Present only on backends that\n * declare `search.geo`. There is no client-side fallback — only\n * Firestore Enterprise has a native geo index (translated via\n * Pipeline `search({ query: geoDistance(...).lessThanOrEqual(...) })`).\n * Backends without the cap throw `UNSUPPORTED_OPERATION` from the\n * client wrapper.\n *\n * The backend is responsible for `geoField` path normalisation,\n * translating `point` to a Firestore `GeoPoint`, applying the\n * radius cap inside the search query, and (when\n * `orderByDistance` is true / unset) emitting the\n * `geoDistance(...).ascending()` ordering inside the search stage.\n *\n * Migrations are not applied to the result.\n */\n geoSearch?(params: GeoSearchParams): Promise<StoredGraphRecord[]>;\n}\n","/**\n * Default core index preset.\n *\n * This set covers the query patterns firegraph's query planner emits for\n * built-in operations — `findNodes`, `findEdges`, cascade delete, traversal,\n * and the DO/SQLite path compilers. Apps that need additional indexes\n * (descending timestamps, `data.*` filters, composite fields unique to\n * their query shapes) declare them on `RegistryEntry.indexes` or override\n * this preset wholesale via the backend-specific `coreIndexes` option —\n * `FiregraphDOOptions.coreIndexes` for the DO backend,\n * `BuildSchemaOptions.coreIndexes` for the legacy SQLite backend, and\n * `GenerateIndexOptions.coreIndexes` for the Firestore CLI generator.\n *\n * ## Ownership model\n *\n * This list is firegraph's *recommendation* — not non-negotiable policy.\n * Consumers can:\n *\n * 1. Accept the preset as-is (default).\n * 2. Extend it: `coreIndexes: [...DEFAULT_CORE_INDEXES, ...more]`.\n * 3. Replace it entirely with a tailored set.\n * 4. Disable it (`coreIndexes: []`) and take full responsibility for\n * index coverage — only do this if you're provisioning a complete\n * custom set.\n *\n * ## Per-backend emission\n *\n * The Firestore generator skips single-field entries (Firestore implicitly\n * indexes every field) and emits one composite index per multi-field spec.\n * The SQLite-flavored generators (DO, legacy) emit every spec as-is.\n *\n * ## Why these specific indexes\n *\n * - `aUid` / `bUid` — required for `_fgRemoveNodeCascade`, which scans by\n * each UID side independently. A composite `(aUid, axbType)` also\n * satisfies `aUid`-alone via leading-column prefix, but the single-field\n * form is cheaper for the common case.\n * - `aType` / `bType` — `findNodes({ aType })` and cross-type enumeration.\n * - `(aUid, axbType)` — forward edge lookup (`findEdges({ aUid, axbType })`)\n * and the `get` strategy fallback when only two of three triple fields\n * are present.\n * - `(axbType, bUid)` — reverse edge traversal.\n * - `(aType, axbType)` — type-scoped edge scans (e.g., `findEdges({ aType, axbType })`).\n * - `(axbType, bType)` — scope edges of one relation to a target type.\n */\n\nimport type { IndexSpec } from './types.js';\n\nexport const DEFAULT_CORE_INDEXES: ReadonlyArray<IndexSpec> = Object.freeze([\n { fields: ['aUid'] },\n { fields: ['bUid'] },\n { fields: ['aType'] },\n { fields: ['bType'] },\n { fields: ['aUid', 'axbType'] },\n { fields: ['axbType', 'bUid'] },\n { fields: ['aType', 'axbType'] },\n { fields: ['axbType', 'bType'] },\n]);\n","/**\n * Translator from `IndexSpec` to SQLite `CREATE INDEX` DDL.\n *\n * Shared by every SQLite-shaped backend (the table-per-graph edition in\n * `src/sqlite/` and the Cloudflare DO edition) via the common schema module\n * (`src/internal/sqlite-schema.ts`). Both use the same scope-free row shape,\n * so the only knob is the `fieldToColumn` mapping.\n *\n * ## JSON path expression indexes\n *\n * Data-field specs (`data.foo`, `data.nested.bar`) compile to\n * `json_extract(\"data\", '$.foo')` expression indexes. The JSON path\n * literal is inlined — not parametrized — so the SQLite query planner can\n * match the index against the expression emitted by the query compiler\n * (which also inlines the literal after this PR). Path components are\n * validated against a safe identifier pattern so inlining is not an\n * injection risk.\n *\n * ## Index naming\n *\n * Names are `{table}_idx_{hash}` where `hash` is a short FNV-1a of a\n * canonicalized spec. This keeps names stable across runs (so\n * `CREATE INDEX IF NOT EXISTS` is idempotent) and prevents collisions\n * between similar specs. The hash includes the field list, per-field\n * direction, and the `where` predicate.\n */\n\nimport { FiregraphError } from '../errors.js';\nimport type { IndexFieldSpec, IndexSpec } from '../types.js';\n\n/**\n * Valid SQLite identifier pattern — used for table and column names.\n * Mirrors the validation in `sqlite-schema.ts` / `cloudflare/schema.ts` so\n * this module doesn't need to import one over the other.\n */\nconst IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * Safe JSON path component. Must match `JSON_PATH_KEY_RE` in the SQLite\n * query compilers — an index is only useful if the query emits an\n * identical `json_extract` expression.\n */\nconst JSON_PATH_KEY_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;\n\nfunction quoteIdent(name: string): string {\n if (!IDENT_RE.test(name)) {\n throw new FiregraphError(\n `Invalid SQL identifier in index DDL: ${name}. Must match /^[A-Za-z_][A-Za-z0-9_]*$/.`,\n 'INVALID_INDEX',\n );\n }\n return `\"${name}\"`;\n}\n\n/**\n * FNV-1a 32-bit hash, returned as 8-char hex. Non-cryptographic;\n * used only to produce short, stable index names.\n */\nfunction fnv1a32(str: string): string {\n let h = 0x811c9dc5;\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return (h >>> 0).toString(16).padStart(8, '0');\n}\n\nfunction normalizeFields(\n fields: Array<string | IndexFieldSpec>,\n): Array<{ path: string; desc: boolean }> {\n return fields.map((f) => {\n if (typeof f === 'string') return { path: f, desc: false };\n if (!f.path || typeof f.path !== 'string') {\n throw new FiregraphError(\n `IndexSpec field must be a string or { path: string, desc?: boolean }; got ${JSON.stringify(f)}`,\n 'INVALID_INDEX',\n );\n }\n return { path: f.path, desc: !!f.desc };\n });\n}\n\nfunction specFingerprint(spec: IndexSpec): string {\n // Canonical form: JSON of normalized fields + where. The `lead` key is\n // kept (always empty now) so fingerprints — and therefore index names —\n // stay stable across the removal of the legacy scope leading column.\n const normalized = {\n lead: [] as string[],\n fields: normalizeFields(spec.fields),\n where: spec.where ?? '',\n };\n return fnv1a32(JSON.stringify(normalized));\n}\n\n/**\n * Compile one field path to its SQLite column expression.\n *\n * - Firegraph top-level fields (`aType`, `createdAt`, …) → mapped column.\n * - `data.foo` / `data.foo.bar` → `json_extract(\"data\", '$.foo.bar')`.\n * - `data` alone → `json_extract(\"data\", '$')`.\n */\nfunction compileFieldExpr(path: string, fieldToColumn: Record<string, string>): string {\n const col = fieldToColumn[path];\n if (col) return quoteIdent(col);\n\n if (path === 'data') {\n return `json_extract(\"data\", '$')`;\n }\n if (path.startsWith('data.')) {\n const suffix = path.slice(5);\n const parts = suffix.split('.');\n for (const part of parts) {\n if (!JSON_PATH_KEY_RE.test(part)) {\n throw new FiregraphError(\n `IndexSpec data path \"${path}\" has invalid component \"${part}\". ` +\n `Each component must match /^[A-Za-z_][A-Za-z0-9_-]*$/.`,\n 'INVALID_INDEX',\n );\n }\n }\n // Inline the path literal (no parameter). Validated components above\n // are safe to embed — no quote or escape characters.\n return `json_extract(\"data\", '$.${suffix}')`;\n }\n\n throw new FiregraphError(\n `IndexSpec field \"${path}\" is not a known firegraph field. ` +\n `Use a top-level field (aType, aUid, axbType, bType, bUid, createdAt, updatedAt, v) ` +\n `or a dotted data path like 'data.status'.`,\n 'INVALID_INDEX',\n );\n}\n\nexport interface SqliteIndexDDLOptions {\n /** Target table. */\n table: string;\n /** Map from firegraph field name to SQLite column name. */\n fieldToColumn: Record<string, string>;\n}\n\n/**\n * Emit the `CREATE INDEX IF NOT EXISTS` DDL for one `IndexSpec`.\n *\n * Returns a single SQL string. Name is deterministic (same spec → same\n * name across runs), so re-running the bootstrap is idempotent.\n */\nexport function buildIndexDDL(spec: IndexSpec, options: SqliteIndexDDLOptions): string {\n const { table, fieldToColumn } = options;\n\n if (!spec.fields || spec.fields.length === 0) {\n throw new FiregraphError('IndexSpec.fields must be a non-empty array', 'INVALID_INDEX');\n }\n\n const normalized = normalizeFields(spec.fields);\n const hash = specFingerprint(spec);\n const indexName = `${table}_idx_${hash}`;\n\n const cols: string[] = [];\n for (const f of normalized) {\n const expr = compileFieldExpr(f.path, fieldToColumn);\n cols.push(f.desc ? `${expr} DESC` : expr);\n }\n\n let ddl = `CREATE INDEX IF NOT EXISTS ${quoteIdent(indexName)} ON ${quoteIdent(table)}(${cols.join(', ')})`;\n\n if (spec.where) {\n // The predicate is inlined verbatim. It comes from library/app\n // configuration — never from user data — so we don't attempt to\n // parse, rewrite, or validate it. Callers authoring partial indexes\n // are responsible for writing a valid SQLite WHERE clause.\n ddl += ` WHERE ${spec.where}`;\n }\n\n return ddl;\n}\n\n/**\n * Deduplicate index specs by their deterministic fingerprint. Same spec\n * declared twice (e.g., by core preset + registry entry) collapses to a\n * single DDL statement.\n */\nexport function dedupeIndexSpecs(specs: ReadonlyArray<IndexSpec>): IndexSpec[] {\n const seen = new Set<string>();\n const out: IndexSpec[] = [];\n for (const spec of specs) {\n const fp = specFingerprint(spec);\n if (seen.has(fp)) continue;\n seen.add(fp);\n out.push(spec);\n }\n return out;\n}\n","/**\n * SQLite schema for firegraph triples.\n *\n * Single-table design — both nodes (self-loops with `axbType = 'is'`) and\n * edges share one row shape. Each table holds exactly one graph's triples:\n * subgraph isolation is physical (one table per graph, or one Durable\n * Object per graph on Cloudflare), so there is no `scope` discriminator\n * column. The table a row lives in *is* its scope.\n *\n * `data` is a JSON string. Built-in fields are projected to typed columns so\n * the query planner can use indexes without going through `json_extract`.\n *\n * ## Indexes\n *\n * Index specs come from the core preset (overridable via\n * `BuildSchemaOptions.coreIndexes`) plus per-entry `indexes` declared on\n * registry entries. Specs are deduplicated by canonical fingerprint before\n * emission.\n */\n\nimport { DEFAULT_CORE_INDEXES } from '../default-indexes.js';\nimport type { GraphRegistry, IndexSpec } from '../types.js';\nimport { buildIndexDDL, dedupeIndexSpecs } from './sqlite-index-ddl.js';\n\nexport const SQLITE_COLUMNS = [\n 'doc_id',\n 'a_type',\n 'a_uid',\n 'axb_type',\n 'b_type',\n 'b_uid',\n 'data',\n 'v',\n 'created_at',\n 'updated_at',\n] as const;\n\nexport type SqliteColumn = (typeof SQLITE_COLUMNS)[number];\n\n/**\n * Map firegraph field names (as they appear in `QueryFilter.field` and the\n * record envelope) to SQLite column names.\n */\nexport const FIELD_TO_COLUMN: Record<string, SqliteColumn> = {\n aType: 'a_type',\n aUid: 'a_uid',\n axbType: 'axb_type',\n bType: 'b_type',\n bUid: 'b_uid',\n v: 'v',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n};\n\n/**\n * Options controlling DDL emission for `buildSchemaStatements`.\n */\nexport interface BuildSchemaOptions {\n /**\n * Replaces the built-in core preset. Defaults to `DEFAULT_CORE_INDEXES`.\n * Pass `[]` to disable core indexes entirely.\n */\n coreIndexes?: IndexSpec[];\n /**\n * Registry contributing per-triple `indexes` declarations.\n */\n registry?: GraphRegistry;\n}\n\n/**\n * Build the DDL statements that create one graph's triple table and its\n * indexes. Returned as separate statements because some drivers (D1, DO\n * SQLite's `exec()`) require one statement per call.\n *\n * The CREATE TABLE statement is always first; index statements follow in\n * deterministic order. Same specs across runs produce the same statements,\n * so `CREATE … IF NOT EXISTS` is idempotent.\n */\nexport function buildSchemaStatements(table: string, options: BuildSchemaOptions = {}): string[] {\n const t = quoteIdent(table);\n const statements: string[] = [\n `CREATE TABLE IF NOT EXISTS ${t} (\n doc_id TEXT NOT NULL PRIMARY KEY,\n a_type TEXT NOT NULL,\n a_uid TEXT NOT NULL,\n axb_type TEXT NOT NULL,\n b_type TEXT NOT NULL,\n b_uid TEXT NOT NULL,\n data TEXT NOT NULL,\n v INTEGER,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n )`,\n ];\n\n const core = options.coreIndexes ?? [...DEFAULT_CORE_INDEXES];\n const fromRegistry = options.registry?.entries().flatMap((e) => e.indexes ?? []) ?? [];\n\n const deduped = dedupeIndexSpecs([...core, ...fromRegistry]);\n for (const spec of deduped) {\n statements.push(buildIndexDDL(spec, { table, fieldToColumn: FIELD_TO_COLUMN }));\n }\n return statements;\n}\n\n/**\n * Quote a SQL identifier with double quotes, escaping any embedded quotes.\n *\n * Identifier names (table, column, index) come from configuration and\n * static code in this module — never from user data — but quoting still\n * protects against accidental keyword collisions.\n */\nexport function quoteIdent(name: string): string {\n validateTableName(name);\n return `\"${name}\"`;\n}\n\n/**\n * Validate a SQLite identifier (table name, column name) against the\n * allowed character set. Exposed so factory functions can fail fast on\n * an invalid `options.table` rather than waiting until first SQL.\n */\nexport function validateTableName(name: string): void {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {\n throw new Error(`Invalid SQL identifier: ${name}. Must match /^[A-Za-z_][A-Za-z0-9_]*$/.`);\n }\n}\n\n/**\n * Quote a SQL column-alias label. Unlike `quoteIdent` (which validates the\n * input as a SQL identifier and is used for table/column names), this helper\n * accepts arbitrary text — projection aliases are pure labels we read back\n * out of the result row, never executed as identifiers, so they can carry\n * dots (e.g. `data.detail.region`) and other characters that\n * `validateTableName` rejects.\n *\n * Embedded double quotes are escaped per the SQL standard (`\"` → `\"\"`),\n * which is sufficient to prevent the alias text from terminating the quoted\n * label early. This is the only injection vector for an alias — even if\n * the input contained `\";--`, double-quote escaping would render it\n * `\"\"\";--` inside `\"...\"`, harmless.\n *\n * Used by `compileFindEdgesProjected` for the caller-supplied projection\n * field name; the underlying SQL expression (`json_extract(...)`, column\n * reference) still goes through the strict compiler with no caller input.\n */\nexport function quoteColumnAlias(label: string): string {\n return `\"${label.replace(/\"/g, '\"\"')}\"`;\n}\n","/**\n * Backend-agnostic timestamp.\n *\n * Structurally compatible with `@google-cloud/firestore`'s `Timestamp` so\n * that records returned by either the Firestore or SQLite backend can be\n * consumed through the same `StoredGraphRecord` shape.\n *\n * Firestore's native `Timestamp` already satisfies this interface, so\n * existing Firestore consumers see no behavior change. The SQLite backend\n * returns instances of `GraphTimestampImpl` which also satisfies it.\n */\n\nexport interface GraphTimestamp {\n readonly seconds: number;\n readonly nanoseconds: number;\n toDate(): Date;\n toMillis(): number;\n}\n\n/**\n * Concrete `GraphTimestamp` implementation used by non-Firestore backends.\n * Mirrors the surface of Firestore's `Timestamp` enough for typical use.\n */\nexport class GraphTimestampImpl implements GraphTimestamp {\n constructor(\n public readonly seconds: number,\n public readonly nanoseconds: number,\n ) {}\n\n toDate(): Date {\n return new Date(this.toMillis());\n }\n\n toMillis(): number {\n return this.seconds * 1000 + Math.floor(this.nanoseconds / 1e6);\n }\n\n toJSON(): { seconds: number; nanoseconds: number } {\n return { seconds: this.seconds, nanoseconds: this.nanoseconds };\n }\n\n static fromMillis(ms: number): GraphTimestampImpl {\n const seconds = Math.floor(ms / 1000);\n const nanoseconds = (ms - seconds * 1000) * 1e6;\n return new GraphTimestampImpl(seconds, nanoseconds);\n }\n\n static now(): GraphTimestampImpl {\n return GraphTimestampImpl.fromMillis(Date.now());\n }\n}\n\n/**\n * Sentinel returned by `StorageBackend.serverTimestamp()` when the backend\n * has no native server-time concept and just wants a placeholder that the\n * adapter resolves to a concrete time at write commit. SQLite backends\n * substitute the wall-clock millis at the moment of `setDoc`/`updateDoc`.\n */\nexport const SERVER_TIMESTAMP_SENTINEL = Symbol.for('firegraph.serverTimestamp');\nexport type ServerTimestampSentinel = typeof SERVER_TIMESTAMP_SENTINEL;\n\nexport function isServerTimestampSentinel(value: unknown): value is ServerTimestampSentinel {\n return value === SERVER_TIMESTAMP_SENTINEL;\n}\n","/**\n * Shared `dataOps` SQL compilation helpers used by both SQLite-style backends\n * (`internal/sqlite-sql.ts` for the shared-table backend and `cloudflare/sql.ts`\n * for the per-DO backend).\n *\n * The two backends differ in identifier quoting and scope handling, but the\n * `data` column lives in JSON in both, the deep-merge / replace contract is\n * identical, and the `json_set` / `json_remove` expression they emit for a\n * `DataPathOp[]` is byte-for-byte the same. Lifting the helpers here keeps\n * that shape in one place — the comment in `cloudflare/sql.ts` used to read\n * \"keep them in sync\"; this module is what they keep in sync against.\n *\n * The helpers take a `backendLabel` parameter so error messages still\n * distinguish `\"SQLite backend\"` (shared-table) from `\"DO SQLite backend\"`\n * (per-Durable-Object). Identifier quoting is the caller's job — the helpers\n * here only emit JSON-path expressions against an opaque `base` argument,\n * never bare column names.\n */\n\nimport { FiregraphError } from '../errors.js';\nimport type { DataPathOp } from './write-plan.js';\n\n/**\n * Constructor names of Firestore special types that don't survive a plain\n * `JSON.stringify` round-trip — they have non-enumerable accessors (e.g.\n * `Timestamp.seconds`) or class identity that JSON loses. Detection is by\n * `constructor.name` to keep this module dependency-free (importing\n * `@google-cloud/firestore` here would pollute the Cloudflare Workers bundle —\n * see tests/unit/bundle-pollution.test.ts).\n */\nexport const FIRESTORE_TYPE_NAMES = new Set([\n 'Timestamp',\n 'GeoPoint',\n 'VectorValue',\n 'DocumentReference',\n 'FieldValue',\n]);\n\nexport function isFirestoreSpecialType(value: object): string | null {\n const ctorName = (value as { constructor?: { name?: string } }).constructor?.name;\n if (ctorName && FIRESTORE_TYPE_NAMES.has(ctorName)) return ctorName;\n return null;\n}\n\n/**\n * Identifiers accepted in `data.<key>` paths and `dataOps` path segments.\n * The pattern (`/^[A-Za-z_][A-Za-z0-9_-]*$/`) covers code-style identifiers\n * (camel, snake, kebab). Silently quoting exotic keys would require symmetric\n * quoting at every read/write call site; any drift produces silent data\n * corruption. Failing loudly at compile time is safer — users with exotic\n * keys can use `replaceNode` / `replaceEdge` (full-data overwrite) instead.\n */\nexport const JSON_PATH_KEY_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;\n\nexport function validateJsonPathKey(key: string, backendLabel: string): void {\n if (key.length === 0) {\n throw new FiregraphError(\n `${backendLabel}: empty JSON path component is not allowed`,\n 'INVALID_QUERY',\n );\n }\n if (!JSON_PATH_KEY_RE.test(key)) {\n throw new FiregraphError(\n `${backendLabel}: data field path component \"${key}\" is not a safe JSON-path identifier. ` +\n `Allowed pattern: /^[A-Za-z_][A-Za-z0-9_-]*$/. Use replaceNode/replaceEdge (full-data overwrite) ` +\n `for keys with reserved characters (whitespace, dots, brackets, quotes, etc.).`,\n 'INVALID_QUERY',\n );\n }\n}\n\n/**\n * Build a SQLite JSON path (`$.\"a\".\"b\".\"c\"`) from `DataPathOp` segments.\n *\n * Each segment is wrapped as a double-quoted JSON-path label via\n * `JSON.stringify`, which quotes the key and backslash-escapes any embedded\n * double-quotes or backslashes — exactly the escaping SQLite's JSON path\n * parser accepts for quoted labels (verified on SQLite 3.53). Quoting every\n * segment means digit-leading keys (`4f9Kq_2bN`), hyphens, dots, brackets,\n * and whitespace all address the literal key rather than being reparsed as\n * path syntax. Dots stay inside the quotes, so `{ 'a.b': 1 }` writes the\n * single key `\"a.b\"` instead of a nested `a → b`.\n *\n * The result is always bound as a SQL parameter (never interpolated), so\n * there is no injection surface — the quoting here is purely about producing\n * a path string SQLite parses as the intended literal key.\n */\nexport function buildJsonPath(segments: readonly string[]): string {\n return '$' + segments.map((seg) => '.' + JSON.stringify(seg)).join('');\n}\n\n/**\n * Bind a value as a JSON-serializable string for `json(?)` placeholders in\n * the compiled `json_set` expression. `assertJsonSafePayload` already runs\n * eagerly at the write boundary, so the Firestore-special-type rejection\n * here is defense-in-depth — left in place per the team's preference for\n * symmetric guards across the SQLite compilers.\n */\nexport function jsonBind(value: unknown, backendLabel: string): string {\n if (value === undefined) return 'null';\n if (value !== null && typeof value === 'object') {\n const firestoreType = isFirestoreSpecialType(value);\n if (firestoreType) {\n throw new FiregraphError(\n `${backendLabel} cannot persist a Firestore ${firestoreType} value. ` +\n `Convert to a primitive before writing (e.g. \\`ts.toMillis()\\` for Timestamp).`,\n 'INVALID_ARGUMENT',\n );\n }\n }\n return JSON.stringify(value);\n}\n\n/**\n * Build the SQL expression that applies a list of `DataPathOp`s onto an\n * existing JSON column reference (e.g. `\"data\"` or `COALESCE(\"data\", '{}')`).\n *\n * Returns the full expression (already parenthesised where needed) and pushes\n * the bound parameters onto `params` in left-to-right order. Returns `null`\n * when there are no ops at all — the caller picks a fallback expression.\n *\n * Strategy:\n * 1. `json_remove(<base>, '$.a.b', '$.c', …)` strips delete-ops.\n * 2. `json_set(<#1>, '$.x.y', json(?), '$.z', json(?), …)` writes value-ops.\n * `json(?)` ensures non-string values bind as JSON (objects, arrays,\n * numbers, booleans, null).\n */\nexport function compileDataOpsExpr(\n ops: readonly DataPathOp[],\n base: string,\n params: unknown[],\n backendLabel: string,\n): string | null {\n if (ops.length === 0) return null;\n\n const deletes: DataPathOp[] = [];\n const sets: DataPathOp[] = [];\n for (const op of ops) (op.delete ? deletes : sets).push(op);\n\n let expr = base;\n\n if (deletes.length > 0) {\n const placeholders = deletes.map(() => '?').join(', ');\n expr = `json_remove(${expr}, ${placeholders})`;\n for (const op of deletes) {\n params.push(buildJsonPath(op.path));\n }\n }\n\n if (sets.length > 0) {\n const pieces = sets.map(() => '?, json(?)').join(', ');\n expr = `json_set(${expr}, ${pieces})`;\n for (const op of sets) {\n params.push(buildJsonPath(op.path));\n params.push(jsonBind(op.value, backendLabel));\n }\n }\n\n return expr;\n}\n","/**\n * Firegraph serialization tag — split from `src/serialization.ts` so it can\n * be imported from Workers-facing code without dragging in\n * `@google-cloud/firestore`.\n *\n * The full serialization module (with Timestamp/GeoPoint round-tripping)\n * lives one folder up because the sandbox migration pipeline needs it; the\n * write-plan helper only needs to recognise tagged objects to keep them\n * terminal during patch flattening, so it imports just the tag from here.\n */\n\n/** Sentinel key used to tag serialized Firestore types. */\nexport const SERIALIZATION_TAG = '__firegraph_ser__' as const;\n\nconst KNOWN_TYPES = new Set(['Timestamp', 'GeoPoint', 'VectorValue', 'DocumentReference']);\n\n/** Check if a value is a tagged serialized Firestore type. */\nexport function isTaggedValue(value: unknown): boolean {\n if (value === null || typeof value !== 'object') return false;\n const tag = (value as Record<string, unknown>)[SERIALIZATION_TAG];\n return typeof tag === 'string' && KNOWN_TYPES.has(tag);\n}\n","/**\n * Write-plan helper — flattens partial-update payloads into a list of\n * deep-path operations every backend can execute identically.\n *\n * Background: firegraph used to ship two write semantics that quietly\n * disagreed about depth.\n * - `putNode`/`putEdge` did a full document replace.\n * - `updateNode`/`updateEdge` did a one-level shallow merge: top-level\n * keys were preserved, but nested objects were replaced wholesale.\n *\n * Both behaviours dropped sibling keys silently. The 0.12 contract is that\n * `put*` and `update*` deep-merge by default (sibling keys at any depth\n * survive); `replace*` is the explicit escape hatch.\n *\n * `flattenPatch` walks a partial-update payload and emits one\n * {@link DataPathOp} per terminal value. Plain objects recurse; arrays,\n * primitives, Firestore special types, and tagged firegraph-serialization\n * objects are terminal (replaced as a unit). `undefined` values are\n * skipped; `null` is preserved as a real `null` write; the\n * {@link DELETE_FIELD} sentinel marks a field for removal.\n *\n * The output is deliberately backend-agnostic. Each backend translates ops\n * into its native dialect:\n * - Firestore: dotted field path → `data.a.b.c` for `update()`.\n * - SQLite / DO SQLite: `json_set(data, '$.a.b.c', ?)` /\n * `json_remove(data, '$.a.b.c')`.\n */\n\nimport { isTaggedValue, SERIALIZATION_TAG } from './serialization-tag.js';\n\n// ---------------------------------------------------------------------------\n// Public sentinel\n// ---------------------------------------------------------------------------\n\n/**\n * Sentinel returned by {@link deleteField}. Treated by all backends as\n * \"remove this field from the stored document\".\n *\n * Equivalent to Firestore's `FieldValue.delete()`, but works for SQLite\n * backends too. Use inside `updateNode`/`updateEdge` payloads.\n */\nexport const DELETE_FIELD: unique symbol = Symbol.for('firegraph.deleteField');\nexport type DeleteSentinel = typeof DELETE_FIELD;\n\n/**\n * Returns the firegraph delete sentinel. Place this anywhere in an\n * `updateNode`/`updateEdge` payload to remove the corresponding field.\n *\n * ```ts\n * await client.updateNode('tour', uid, {\n * attrs: { obsoleteFlag: deleteField() },\n * });\n * ```\n */\nexport function deleteField(): DeleteSentinel {\n return DELETE_FIELD;\n}\n\n/** Type guard for the delete sentinel. */\nexport function isDeleteSentinel(value: unknown): value is DeleteSentinel {\n return value === DELETE_FIELD;\n}\n\n// ---------------------------------------------------------------------------\n// Terminal-detection helpers\n// ---------------------------------------------------------------------------\n\nconst FIRESTORE_TERMINAL_CTOR = new Set([\n 'Timestamp',\n 'GeoPoint',\n 'VectorValue',\n 'DocumentReference',\n 'FieldValue',\n 'NumericIncrementTransform',\n 'ArrayUnionTransform',\n 'ArrayRemoveTransform',\n 'ServerTimestampTransform',\n 'DeleteTransform',\n]);\n\n/**\n * Should this value be written as a single terminal op (no recursion)?\n *\n * Plain JS objects (constructor === Object, or no prototype) are recursed.\n * Everything else — arrays, primitives, class instances, Firestore special\n * types, tagged serialization payloads — is terminal.\n */\nexport function isTerminalValue(value: unknown): boolean {\n if (value === null) return true;\n const t = typeof value;\n if (t !== 'object') return true;\n if (Array.isArray(value)) return true;\n // Tagged serialization payloads carry the SERIALIZATION_TAG sentinel and\n // should be persisted whole — never split into per-field ops.\n if (isTaggedValue(value)) return true;\n const proto = Object.getPrototypeOf(value);\n if (proto === null || proto === Object.prototype) return false;\n // Class instances — Firestore types or anything else exotic.\n const ctor = (value as { constructor?: { name?: string } }).constructor;\n if (ctor && typeof ctor.name === 'string' && FIRESTORE_TERMINAL_CTOR.has(ctor.name)) return true;\n // Unknown class instance: treat as terminal. Recursing into a class\n // instance is almost always wrong (Map, Set, Date, Buffer...).\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Core type\n// ---------------------------------------------------------------------------\n\n/**\n * Single terminal write operation produced by {@link flattenPatch}.\n *\n * `path` is a non-empty array of plain object keys. `value` is the value to\n * write; ignored when `delete` is `true`. Arrays / primitives / Firestore\n * special types appear here as whole terminal values.\n */\nexport interface DataPathOp {\n path: readonly string[];\n value: unknown;\n delete: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Path-segment validation\n// ---------------------------------------------------------------------------\n\n/**\n * Mutual-exclusion guard for {@link UpdatePayload}. The two branches of the\n * shape — `dataOps` (deep-merge) and `replaceData` (full replace) — are\n * structurally incompatible: combining them would tell the backend to\n * simultaneously merge AND wipe, and the three backends disagree on which\n * wins. This helper centralises the runtime check so all three backends\n * trip the same error.\n *\n * Imported as a runtime check from `firestore-backend`, `sqlite-sql`, and\n * `cloudflare/sql`. Backend authors implementing the public `StorageBackend`\n * contract should call it too.\n */\nexport function assertUpdatePayloadExclusive(update: {\n dataOps?: unknown;\n replaceData?: unknown;\n}): void {\n if (update.replaceData !== undefined && update.dataOps !== undefined) {\n throw new Error(\n 'firegraph: UpdatePayload cannot specify both `replaceData` and `dataOps`. ' +\n 'Use one or the other — `replaceData` is the migration-write-back form, ' +\n '`dataOps` is the standard partial-update form.',\n );\n }\n}\n\n/**\n * Reject `DELETE_FIELD` sentinels in payloads where field deletion isn't a\n * meaningful operation: full-document replace (`replaceNode`/`replaceEdge`)\n * and the merge-default put surface (`putNode`/`putEdge`).\n *\n * Why both:\n * - In **replace**, the entire `data` field is overwritten. A delete\n * sentinel in that payload either silently disappears (Firestore drops\n * the Symbol during `.set()` serialization) or produces an empty SQLite\n * `json_remove` no-op, depending on backend. Either way the caller's\n * intent — \"remove field X\" — is lost. Use `updateNode` instead.\n * - In **put** (merge mode), behaviour diverges across backends today:\n * SQLite's flattenPatch emits a real delete op, but Firestore's\n * `.set(..., {merge: true})` silently drops the Symbol. Until that's\n * fixed end-to-end, the safest contract is to reject sentinels at the\n * entry point and steer callers to `updateNode`.\n *\n * The walk mirrors `flattenPatch`: plain objects recurse, everything else\n * is terminal. Tagged serialization payloads short-circuit so we don't\n * recurse into the `__firegraph_ser__` envelope.\n */\nexport function assertNoDeleteSentinels(data: unknown, callerLabel: string): void {\n walkForDeleteSentinels(data, [], { kind: 'root' }, ({ path }) => {\n const where = path.length === 0 ? '<root>' : path.map((p) => JSON.stringify(p)).join(' > ');\n throw new Error(\n `firegraph: ${callerLabel} payload contains a deleteField() sentinel at ${where}. ` +\n `deleteField() is only valid inside updateNode/updateEdge — full-data ` +\n `writes (put*, replace*) cannot delete individual fields. Use updateNode ` +\n `with a deleteField() value, or omit the field from the replace payload.`,\n );\n });\n}\n\ntype SentinelParent = { kind: 'root' } | { kind: 'object' } | { kind: 'array'; index: number };\n\nfunction walkForDeleteSentinels(\n node: unknown,\n path: readonly string[],\n parent: SentinelParent,\n visit: (ctx: { path: readonly string[]; parent: SentinelParent }) => void,\n): void {\n if (node === null || node === undefined) return;\n if (isDeleteSentinel(node)) {\n visit({ path, parent });\n return;\n }\n if (typeof node !== 'object') return;\n if (isTaggedValue(node)) return;\n if (Array.isArray(node)) {\n for (let i = 0; i < node.length; i++) {\n walkForDeleteSentinels(node[i], [...path, String(i)], { kind: 'array', index: i }, visit);\n }\n return;\n }\n const proto = Object.getPrototypeOf(node);\n if (proto !== null && proto !== Object.prototype) return;\n const obj = node as Record<string, unknown>;\n for (const key of Object.keys(obj)) {\n walkForDeleteSentinels(obj[key], [...path, key], { kind: 'object' }, visit);\n }\n}\n\n/**\n * Throws if any path segment is an empty string.\n *\n * Empty keys are the one shape no backend can represent: Firestore's\n * `FieldPath` rejects empty segments, and a SQLite JSON-path label `$.\"\"`\n * is meaningless. Every other key — digits-first, hyphens, dots, brackets,\n * whitespace, non-ASCII — is escaped by the backends at path-construction\n * time (Firestore via `FieldPath` literal segments, SQLite via\n * `JSON.stringify`-quoted labels), so it is accepted here untouched.\n */\nexport function assertSafePath(path: readonly string[]): void {\n for (const seg of path) {\n if (seg === '') {\n throw new Error(\n `firegraph: empty object key at path ${path\n .map((p) => JSON.stringify(p))\n .join(' > ')}. Object keys in update payloads must be non-empty.`,\n );\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// flattenPatch\n// ---------------------------------------------------------------------------\n\n/**\n * Flatten a partial-update payload into a list of terminal {@link DataPathOp}s.\n *\n * Rules:\n * - Plain objects (no prototype or `Object.prototype`) recurse — each\n * key becomes another path segment.\n * - Arrays are terminal: writing `{tags: ['a']}` overwrites the whole\n * `tags` array. Element-wise array merging is intentionally NOT\n * supported — it's almost never what callers actually want, and\n * Firestore `arrayUnion`/`arrayRemove` give precise semantics when\n * they are.\n * - `undefined` values are skipped (no op generated). Use\n * {@link deleteField} if you actually want to remove a field.\n * - `null` is preserved verbatim — emits a terminal op with `value: null`.\n * - {@link DELETE_FIELD} produces an op with `delete: true`.\n * - Firestore special types and tagged serialization payloads are terminal.\n * - Class instances are terminal.\n *\n * Throws if any object key on the recursion path is unsafe (see\n * {@link assertSafePath}).\n */\nexport function flattenPatch(data: Record<string, unknown>): DataPathOp[] {\n const ops: DataPathOp[] = [];\n walk(data, [], ops);\n return ops;\n}\n\nfunction assertNoDeleteSentinelsInArrayValue(\n arr: readonly unknown[],\n arrayPath: readonly string[],\n): void {\n walkForDeleteSentinels(arr, arrayPath, { kind: 'root' }, ({ parent }) => {\n const arrayPathStr =\n arrayPath.length === 0 ? '<root>' : arrayPath.map((p) => JSON.stringify(p)).join(' > ');\n if (parent.kind === 'array') {\n throw new Error(\n `firegraph: deleteField() sentinel at index ${parent.index} inside an array at ` +\n `path ${arrayPathStr}. Arrays are ` +\n `terminal in update payloads (replaced as a unit), so the sentinel ` +\n `would be silently dropped by JSON serialization. To remove the ` +\n `field entirely, pass deleteField() in place of the whole array.`,\n );\n }\n throw new Error(\n `firegraph: deleteField() sentinel inside an array element at ` +\n `path ${arrayPathStr}. ` +\n `Arrays are terminal in update payloads — the sentinel would ` +\n `be silently dropped by JSON serialization.`,\n );\n });\n}\n\nfunction walk(node: unknown, path: string[], out: DataPathOp[]): void {\n // Caller guarantees the root is a plain object; this branch only\n // matters for recursion.\n if (node === undefined) return;\n if (isDeleteSentinel(node)) {\n if (path.length === 0) {\n throw new Error('firegraph: deleteField() cannot be the entire update payload.');\n }\n assertSafePath(path);\n out.push({ path: [...path], value: undefined, delete: true });\n return;\n }\n if (isTerminalValue(node)) {\n if (path.length === 0) {\n // `null` / array / primitive at the root is illegal — patches must\n // describe per-key changes.\n throw new Error(\n 'firegraph: update payload must be a plain object. Got ' +\n (node === null ? 'null' : Array.isArray(node) ? 'array' : typeof node) +\n '.',\n );\n }\n // A DELETE_FIELD sentinel embedded inside an array (which is terminal\n // and replaced as a unit) would silently disappear: JSON.stringify drops\n // Symbols, and Firestore's serializer does likewise. Reject loudly so\n // the divergence between \"user wrote a delete\" and \"field stayed put\"\n // can't happen.\n if (Array.isArray(node)) {\n assertNoDeleteSentinelsInArrayValue(node, path);\n }\n assertSafePath(path);\n out.push({ path: [...path], value: node, delete: false });\n return;\n }\n // Plain object: recurse into its own enumerable keys.\n const obj = node as Record<string, unknown>;\n const keys = Object.keys(obj);\n if (keys.length === 0) {\n // Empty object at non-root: emit terminal op so an empty object can\n // be written explicitly when the caller really wants one. Skip at\n // the root — no-op patches should produce no ops.\n if (path.length > 0) {\n assertSafePath(path);\n out.push({ path: [...path], value: {}, delete: false });\n }\n return;\n }\n for (const key of keys) {\n if (key === SERIALIZATION_TAG) {\n const where = path.length === 0 ? '<root>' : path.map((p) => JSON.stringify(p)).join(' > ');\n throw new Error(\n `firegraph: update payload contains a literal \\`${SERIALIZATION_TAG}\\` key at ` +\n `${where}. That key is reserved for firegraph's serialization envelope and ` +\n `cannot appear on a plain object in user data. Use a different field name, ` +\n `or pass a recognized tagged value through replaceNode/replaceEdge instead.`,\n );\n }\n walk(obj[key], [...path, key], out);\n }\n}\n","/**\n * Shared eager-validation helper for SQLite-style backends\n * (`internal/sqlite-sql.ts` and `cloudflare/sql.ts`).\n *\n * Both backends serialise `record.data` (and `update.replaceData`) as a raw\n * JSON blob via `JSON.stringify`. Two classes of value silently corrupt that\n * representation and the cross-backend contract:\n *\n * 1. **Firestore special types** (`Timestamp`, `GeoPoint`, `VectorValue`,\n * `DocumentReference`, `FieldValue`). They have non-enumerable accessors\n * or rely on class identity that JSON drops, so they round-trip as `{}`\n * or garbage. Callers must convert to primitives before writing.\n * 2. **`DELETE_FIELD` sentinel.** A `Symbol` is invisible to\n * `JSON.stringify`. If a caller embeds the sentinel in a `replaceNode`\n * payload or in a fresh-insert (no existing row), the field would\n * silently disappear instead of erroring loudly the way it does for the\n * `dataOps` path — so we reject it eagerly here.\n * 3. **Tagged serialization payloads** (`__firegraph_ser__`). These are the\n * sandbox migration boundary marshalling form. They are valid inside\n * Firestore (the Firestore backend re-hydrates them via\n * `deserializeFirestoreTypes`), but on SQLite they would persist as\n * opaque tagged objects that no downstream reader knows how to interpret.\n * Reject them at the boundary so the failure is loud.\n *\n * The Firestore backend does NOT call this — it accepts those types natively\n * and `deserializeFirestoreTypes` rebuilds tagged values into real Firestore\n * objects on its own write path.\n *\n * Detection avoids `instanceof` so this module stays free of\n * `@google-cloud/firestore`. Constructor-name + duck-type matches the\n * approach used by `bindValue`/`jsonBind` elsewhere in the SQLite compilers.\n */\n\nimport { FiregraphError } from '../errors.js';\nimport { SERIALIZATION_TAG } from './serialization-tag.js';\nimport { isDeleteSentinel } from './write-plan.js';\n\nconst FIRESTORE_TYPE_NAMES = new Set([\n 'Timestamp',\n 'GeoPoint',\n 'VectorValue',\n 'DocumentReference',\n 'FieldValue',\n]);\n\n/**\n * Walk `data` and throw on any value that the SQLite-style raw-JSON\n * persistence path can't faithfully serialise. `label` distinguishes the\n * caller in error messages (e.g. `'shared-table SQLite'` vs `'DO SQLite'`).\n *\n * Plain objects recurse. Arrays recurse element-wise. Primitives, `null`,\n * and `undefined` are accepted (mirroring how `flattenPatch` treats them\n * during the merge path).\n */\nexport function assertJsonSafePayload(data: unknown, label: string): void {\n walk(data, [], label);\n}\n\nfunction walk(node: unknown, path: readonly string[], label: string): void {\n if (node === null || node === undefined) return;\n if (isDeleteSentinel(node)) {\n throw new FiregraphError(\n `${label} backend cannot persist a deleteField() sentinel inside a ` +\n `full-data payload (replaceNode/replaceEdge or first-insert). The ` +\n `sentinel is only valid inside an updateNode/updateEdge dataOps patch. ` +\n `Path: ${formatPath(path)}.`,\n 'INVALID_ARGUMENT',\n );\n }\n const t = typeof node;\n if (t === 'symbol' || t === 'function') {\n throw new FiregraphError(\n `${label} backend cannot persist a value of type ${t}. ` +\n `JSON.stringify drops it silently. Path: ${formatPath(path)}.`,\n 'INVALID_ARGUMENT',\n );\n }\n if (t === 'bigint') {\n throw new FiregraphError(\n `${label} backend cannot persist a value of type bigint. ` +\n `JSON.stringify cannot serialize this type (throws TypeError). ` +\n `Path: ${formatPath(path)}.`,\n 'INVALID_ARGUMENT',\n );\n }\n if (t !== 'object') return;\n if (Array.isArray(node)) {\n for (let i = 0; i < node.length; i++) {\n walk(node[i], [...path, String(i)], label);\n }\n return;\n }\n // Reject any object carrying the firegraph serialization tag — both legit\n // tagged Firestore-type payloads (the migration-sandbox output that round-\n // trips through Firestore) and bogus user data that happens to put a\n // literal `__firegraph_ser__` key on a plain object. SQLite has no\n // Timestamp class to rebuild the tag into, and silently writing the\n // envelope would produce an unreadable column.\n const obj = node as Record<string, unknown>;\n if (Object.prototype.hasOwnProperty.call(obj, SERIALIZATION_TAG)) {\n const tagValue = obj[SERIALIZATION_TAG];\n throw new FiregraphError(\n `${label} backend cannot persist an object with a \\`${SERIALIZATION_TAG}\\` ` +\n `key (value: ${formatTagValue(tagValue)}). Recognised tags are valid only on ` +\n `the Firestore backend (migration-sandbox output); a literal ` +\n `\\`${SERIALIZATION_TAG}\\` field in user data is reserved and not allowed. ` +\n `Path: ${formatPath(path)}.`,\n 'INVALID_ARGUMENT',\n );\n }\n // Class instances: reject Firestore special types loudly; reject every\n // other class instance generically (Map, Set, Date are caller's\n // responsibility to convert — Date is allowed in filter binds via\n // `bindValue` but not as a stored payload value because JSON.stringify\n // produces a string, not a real Date).\n const proto = Object.getPrototypeOf(node);\n if (proto !== null && proto !== Object.prototype) {\n const ctor = (node as { constructor?: { name?: string } }).constructor;\n const ctorName = ctor && typeof ctor.name === 'string' ? ctor.name : '<anonymous>';\n if (FIRESTORE_TYPE_NAMES.has(ctorName)) {\n throw new FiregraphError(\n `${label} backend cannot persist a Firestore ${ctorName} value. ` +\n `Convert to a primitive before writing (e.g. \\`ts.toMillis()\\` for ` +\n `Timestamp, \\`{lat,lng}\\` for GeoPoint). Path: ${formatPath(path)}.`,\n 'INVALID_ARGUMENT',\n );\n }\n // Accept Date as an alias for its epoch-ms — it round-trips as an ISO\n // string via JSON.stringify, which the caller chose; not our place to\n // reject. Same for Buffer / typed arrays — they'll JSON-serialize as\n // best they can. Reject only opaque exotic instances that JSON drops.\n if (node instanceof Date) return;\n throw new FiregraphError(\n `${label} backend cannot persist a class instance of type ${ctorName}. ` +\n `Only plain objects, arrays, and primitives round-trip safely through ` +\n `JSON storage. Path: ${formatPath(path)}.`,\n 'INVALID_ARGUMENT',\n );\n }\n for (const key of Object.keys(obj)) {\n walk(obj[key], [...path, key], label);\n }\n}\n\nfunction formatPath(path: readonly string[]): string {\n return path.length === 0 ? '<root>' : path.map((p) => JSON.stringify(p)).join(' > ');\n}\n\nfunction formatTagValue(value: unknown): string {\n if (value === null) return 'null';\n if (value === undefined) return 'undefined';\n if (typeof value === 'string') return JSON.stringify(value);\n if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {\n return String(value);\n }\n return typeof value;\n}\n","/**\n * SQL compilation for SQLite-shaped firegraph backends.\n *\n * One table holds exactly one graph's triples — there is no `scope` column\n * and no scope discriminator on any statement. Subgraph isolation is\n * physical: the shared SQLite backend (`src/sqlite/`) maps each graph to\n * its own table, and the Cloudflare DO backend (`src/cloudflare/`) maps\n * each graph to its own Durable Object. Every compiler in this module is\n * parameterized by the target table name.\n *\n * Filter compilation, JSON-path validation, and value binding are shared so\n * the query planner (`src/query.ts`) emits the same `QueryFilter[]` shape\n * regardless of backend.\n */\n\nimport { FiregraphError } from '../errors.js';\nimport type { GraphTimestamp } from '../timestamp.js';\nimport { GraphTimestampImpl } from '../timestamp.js';\nimport type {\n AggregateSpec,\n ExpandParams,\n QueryFilter,\n QueryOptions,\n StoredGraphRecord,\n} from '../types.js';\nimport type { UpdatePayload, WritableRecord, WriteMode } from './backend.js';\nimport { NODE_RELATION } from './constants.js';\nimport {\n compileDataOpsExpr,\n isFirestoreSpecialType,\n validateJsonPathKey,\n} from './sqlite-data-ops.js';\nimport { assertJsonSafePayload } from './sqlite-payload-guard.js';\nimport { FIELD_TO_COLUMN, quoteColumnAlias, quoteIdent } from './sqlite-schema.js';\nimport { assertUpdatePayloadExclusive, flattenPatch } from './write-plan.js';\n\nconst BACKEND_LABEL = 'SQLite';\nconst BACKEND_ERR_LABEL = 'SQLite backend';\n\nexport interface CompiledStatement {\n sql: string;\n params: unknown[];\n}\n\n// ---------------------------------------------------------------------------\n// Filter compilation\n// ---------------------------------------------------------------------------\n\n/**\n * Translate a firegraph filter field to either a column reference or a\n * `json_extract(\"data\", '$.<path>')` expression. Built-in fields go\n * straight to their column; `data.<key>[.<key>…]` and bare `data` are\n * projected through `json_extract` with the JSON path **inlined as a\n * string literal** — not parametrized.\n *\n * Inlining matters: SQLite's query planner matches an expression index\n * (`CREATE INDEX … ON tbl(json_extract(\"data\", '$.status'))`) against\n * *textually identical* expressions in the WHERE clause. `json_extract(\n * \"data\", ?)` parametrizes the path and would never hit the index, even\n * though it evaluates to the same value. Inlining here makes the\n * expression literal in the SQL, which is what the index builder in\n * `sqlite-index-ddl.ts` also emits.\n *\n * Inlining is safe: each path component is validated against\n * `JSON_PATH_KEY_RE` (`/^[A-Za-z_][A-Za-z0-9_-]*$/`) before it reaches\n * this function — the pattern excludes every character SQLite would\n * treat as syntax (quote, backslash, dot, bracket, whitespace), so\n * string concatenation can't produce injection.\n */\nfunction compileFieldRef(field: string): { expr: string } {\n const column = FIELD_TO_COLUMN[field];\n if (column) {\n return { expr: quoteIdent(column) };\n }\n if (field.startsWith('data.')) {\n const suffix = field.slice(5);\n for (const part of suffix.split('.')) {\n validateJsonPathKey(part, BACKEND_ERR_LABEL);\n }\n return { expr: `json_extract(\"data\", '$.${suffix}')` };\n }\n if (field === 'data') {\n return { expr: `json_extract(\"data\", '$')` };\n }\n throw new FiregraphError(`SQLite backend cannot resolve filter field: ${field}`, 'INVALID_QUERY');\n}\n\n/**\n * Coerce a JS filter/update value into a SQLite-bindable primitive. Firestore\n * special types are rejected loudly because `JSON.stringify` would emit\n * garbage that silently fails to match any stored row. Callers should\n * project to a primitive (e.g. `ts.toMillis()`) before passing in.\n */\nfunction bindValue(value: unknown): unknown {\n if (value === null || value === undefined) return null;\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'bigint' ||\n typeof value === 'boolean'\n ) {\n return value;\n }\n if (value instanceof Date) return value.getTime();\n if (typeof value === 'object') {\n const firestoreType = isFirestoreSpecialType(value);\n if (firestoreType) {\n throw new FiregraphError(\n `SQLite backend cannot bind a Firestore ${firestoreType} value — JSON serialization ` +\n `would silently drop fields and the resulting bind would never match a stored row. ` +\n `Convert to a primitive (e.g. \\`ts.toMillis()\\` for Timestamp) before filtering or updating.`,\n 'INVALID_QUERY',\n );\n }\n return JSON.stringify(value);\n }\n return String(value);\n}\n\nfunction compileFilter(filter: QueryFilter, params: unknown[]): string {\n const { expr } = compileFieldRef(filter.field);\n\n switch (filter.op) {\n case '==':\n params.push(bindValue(filter.value));\n return `${expr} = ?`;\n case '!=':\n params.push(bindValue(filter.value));\n return `${expr} != ?`;\n case '<':\n params.push(bindValue(filter.value));\n return `${expr} < ?`;\n case '<=':\n params.push(bindValue(filter.value));\n return `${expr} <= ?`;\n case '>':\n params.push(bindValue(filter.value));\n return `${expr} > ?`;\n case '>=':\n params.push(bindValue(filter.value));\n return `${expr} >= ?`;\n case 'in': {\n const values = asArray(filter.value, 'in');\n const placeholders = values.map(() => '?').join(', ');\n for (const v of values) params.push(bindValue(v));\n return `${expr} IN (${placeholders})`;\n }\n case 'not-in': {\n const values = asArray(filter.value, 'not-in');\n const placeholders = values.map(() => '?').join(', ');\n for (const v of values) params.push(bindValue(v));\n return `${expr} NOT IN (${placeholders})`;\n }\n case 'array-contains': {\n params.push(bindValue(filter.value));\n return `EXISTS (SELECT 1 FROM json_each(${expr}) WHERE value = ?)`;\n }\n case 'array-contains-any': {\n const values = asArray(filter.value, 'array-contains-any');\n const placeholders = values.map(() => '?').join(', ');\n for (const v of values) params.push(bindValue(v));\n return `EXISTS (SELECT 1 FROM json_each(${expr}) WHERE value IN (${placeholders}))`;\n }\n default:\n throw new FiregraphError(\n `SQLite backend does not support filter operator: ${String(filter.op)}`,\n 'INVALID_QUERY',\n );\n }\n}\n\nfunction asArray(value: unknown, op: string): unknown[] {\n if (!Array.isArray(value) || value.length === 0) {\n throw new FiregraphError(`Operator \"${op}\" requires a non-empty array value`, 'INVALID_QUERY');\n }\n return value;\n}\n\nfunction compileOrderBy(options: QueryOptions | undefined, _params: unknown[]): string {\n if (!options?.orderBy) return '';\n const { field, direction } = options.orderBy;\n const { expr } = compileFieldRef(field);\n const dir = direction === 'desc' ? 'DESC' : 'ASC';\n return ` ORDER BY ${expr} ${dir}`;\n}\n\nfunction compileLimit(options: QueryOptions | undefined, params: unknown[]): string {\n if (options?.limit === undefined) return '';\n params.push(options.limit);\n return ` LIMIT ?`;\n}\n\n// ---------------------------------------------------------------------------\n// Statement compilation\n// ---------------------------------------------------------------------------\n\n/**\n * SELECT rows matching `filters`. No scope predicate — every row in the\n * table belongs to the same graph.\n */\nexport function compileSelect(\n table: string,\n filters: QueryFilter[],\n options?: QueryOptions,\n): CompiledStatement {\n const params: unknown[] = [];\n const conditions: string[] = [];\n\n for (const f of filters) {\n conditions.push(compileFilter(f, params));\n }\n\n const where = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '';\n let sql = `SELECT * FROM ${quoteIdent(table)}${where}`;\n sql += compileOrderBy(options, params);\n sql += compileLimit(options, params);\n\n return { sql, params };\n}\n\n/**\n * Compile an `expand()` fan-out into one SELECT statement.\n *\n * Forward direction:\n *\n * SELECT * FROM <table>\n * WHERE \"axb_type\" = ? AND \"a_uid\" IN (?, ?, …)\n * [AND \"a_type\" = ?] [AND \"b_type\" = ?]\n * [ORDER BY …]\n * [LIMIT ?]\n *\n * Reverse swaps the IN-predicate to `\"b_uid\"`. Per-source LIMIT enforcement\n * is approximated by `sources.length * limitPerSource` (see\n * `ExpandParams.limitPerSource`); window-function partitioning is out of\n * scope for the round-trip-collapse goal.\n *\n * Empty `params.sources` is rejected at the compiler — `IN ()` is invalid\n * SQL. Backends short-circuit empty input before reaching here.\n */\nexport function compileExpand(table: string, params: ExpandParams): CompiledStatement {\n if (params.sources.length === 0) {\n throw new FiregraphError(\n 'compileExpand requires a non-empty sources list — empty IN () is invalid SQL.',\n 'INVALID_QUERY',\n );\n }\n const direction = params.direction ?? 'forward';\n // Resolve every column reference through `compileFieldRef` so the\n // emitted SQL uses the on-disk snake_case names (`a_uid`, `axb_type`,\n // `b_uid`, …) defined by `FIELD_TO_COLUMN` in `sqlite-schema.ts`.\n const aUidCol = compileFieldRef('aUid').expr;\n const bUidCol = compileFieldRef('bUid').expr;\n const aTypeCol = compileFieldRef('aType').expr;\n const bTypeCol = compileFieldRef('bType').expr;\n const axbTypeCol = compileFieldRef('axbType').expr;\n const sourceColumn = direction === 'forward' ? aUidCol : bUidCol;\n\n const sqlParams: unknown[] = [params.axbType];\n const conditions: string[] = [`${axbTypeCol} = ?`];\n\n const placeholders = params.sources.map(() => '?').join(', ');\n conditions.push(`${sourceColumn} IN (${placeholders})`);\n for (const uid of params.sources) sqlParams.push(uid);\n\n if (params.aType !== undefined) {\n conditions.push(`${aTypeCol} = ?`);\n sqlParams.push(params.aType);\n }\n if (params.bType !== undefined) {\n conditions.push(`${bTypeCol} = ?`);\n sqlParams.push(params.bType);\n }\n\n // Self-loop guard for the corner case where the caller asks for the\n // node-relation as the hop type — without it, every node row would\n // come back as a degenerate \"edge\" from itself to itself.\n if (params.axbType === NODE_RELATION) {\n conditions.push(`${aUidCol} != ${bUidCol}`);\n }\n\n let sql = `SELECT * FROM ${quoteIdent(table)} WHERE ${conditions.join(' AND ')}`;\n\n if (params.orderBy) {\n sql += compileOrderBy({ orderBy: params.orderBy }, sqlParams);\n }\n if (params.limitPerSource !== undefined) {\n const totalLimit = params.sources.length * params.limitPerSource;\n sql += ` LIMIT ?`;\n sqlParams.push(totalLimit);\n }\n return { sql, params: sqlParams };\n}\n\n/**\n * Hydration-pass for `expand({ hydrate: true })`. Pulls every node row\n * whose `bUid` is in the supplied target list (one statement). The caller\n * stitches alignment in JS via a `Map<bUid, row>`.\n */\nexport function compileExpandHydrate(table: string, targetUids: string[]): CompiledStatement {\n if (targetUids.length === 0) {\n throw new FiregraphError(\n 'compileExpandHydrate requires a non-empty target list — empty IN () is invalid SQL.',\n 'INVALID_QUERY',\n );\n }\n const placeholders = targetUids.map(() => '?').join(', ');\n const sqlParams: unknown[] = [NODE_RELATION];\n for (const uid of targetUids) sqlParams.push(uid);\n\n // Resolve column refs via `compileFieldRef` — see `compileExpand`\n // for the schema-rename rationale.\n const aUidCol = compileFieldRef('aUid').expr;\n const bUidCol = compileFieldRef('bUid').expr;\n const axbTypeCol = compileFieldRef('axbType').expr;\n\n return {\n sql:\n `SELECT * FROM ${quoteIdent(table)} ` +\n `WHERE ${axbTypeCol} = ? AND ${aUidCol} = ${bUidCol} AND ${bUidCol} IN (${placeholders})`,\n params: sqlParams,\n };\n}\n\n/**\n * SELECT a single row by doc_id. `doc_id` is the PK so this is an O(1)\n * index lookup.\n */\nexport function compileSelectByDocId(table: string, docId: string): CompiledStatement {\n return {\n sql: `SELECT * FROM ${quoteIdent(table)} WHERE \"doc_id\" = ? LIMIT 1`,\n params: [docId],\n };\n}\n\n/**\n * Discriminator for one projected column. The decoder uses this to recover\n * the JS-shape of the requested field.\n */\nexport type ProjectedColumnKind =\n | 'builtin-text'\n | 'builtin-int'\n | 'builtin-timestamp'\n | 'data'\n | 'json';\n\n/** Per-column metadata returned alongside the compiled projection statement. */\nexport interface ProjectedColumnSpec {\n /** Original caller-supplied field name. Used as the alias in the SQL\n * projection list AND as the key in the returned JS row. */\n field: string;\n /** Kind discriminator — see `ProjectedColumnKind`. */\n kind: ProjectedColumnKind;\n /**\n * For `kind === 'json'` only: alias of the paired `json_type` companion\n * column. Uses a positional sentinel (`__fg_t_<idx>`) keyed by the\n * field's position in the unique projection list rather than the\n * historical `<field>__t` suffix, which would collide if the caller\n * projected both `'foo'` and `'foo__t'` (both legal user input).\n */\n typeAlias?: string;\n}\n\n/**\n * Normalize a projection field name to the canonical form `compileFieldRef`\n * understands: built-ins stay as-is, `data` and `data.*` stay as-is, and a\n * bare `name` is rewritten to `data.name`.\n */\nfunction normalizeProjectionField(field: string): string {\n if (field in FIELD_TO_COLUMN) return field;\n if (field === 'data' || field.startsWith('data.')) return field;\n return `data.${field}`;\n}\n\n/**\n * Compile a `findEdgesProjected({ select })` call into a single SELECT.\n *\n * Shape:\n *\n * SELECT\n * <expr-1> AS \"<field-1>\", [json_type(...) AS \"__fg_t_<idx>\",]\n * ...\n * FROM <table>\n * [WHERE <filters>]\n * [ORDER BY ...]\n * [LIMIT ?]\n *\n * For `data.*` projections the compiler also emits a paired `json_type`\n * column so the decoder can recover JSON-encoded objects/arrays without a\n * second round trip. Built-in field projections are passthrough columns.\n * The companion alias uses a positional sentinel `__fg_t_<idx>` rather\n * than `<field>__t` to avoid colliding with a user-projected field\n * literally named `<x>__t`.\n *\n * Empty `select` is rejected at the compiler — the client wrapper enforces\n * this too. Duplicates collapse at compile time, preserving first-occurrence\n * order.\n */\nexport function compileFindEdgesProjected(\n table: string,\n select: ReadonlyArray<string>,\n filters: QueryFilter[],\n options?: QueryOptions,\n): { stmt: CompiledStatement; columns: ProjectedColumnSpec[] } {\n if (select.length === 0) {\n throw new FiregraphError(\n 'compileFindEdgesProjected requires a non-empty select list — ' +\n 'an empty projection has no SQL representation distinct from `findEdges`.',\n 'INVALID_QUERY',\n );\n }\n\n const seen = new Set<string>();\n const uniqueFields: string[] = [];\n for (const f of select) {\n if (!seen.has(f)) {\n seen.add(f);\n uniqueFields.push(f);\n }\n }\n\n const projections: string[] = [];\n const columns: ProjectedColumnSpec[] = [];\n for (let idx = 0; idx < uniqueFields.length; idx++) {\n const field = uniqueFields[idx]!;\n const canonical = normalizeProjectionField(field);\n const { expr } = compileFieldRef(canonical);\n // Alias is the caller-supplied field name verbatim — relaxed quoting\n // accepts dotted paths like `data.detail.region`. The decoder reads\n // back via `row[c.field]`, so the alias must equal the original\n // field string.\n const alias = quoteColumnAlias(field);\n projections.push(`${expr} AS ${alias}`);\n\n let kind: ProjectedColumnKind;\n let typeAliasName: string | undefined;\n if (canonical === 'data') {\n kind = 'data';\n } else if (canonical.startsWith('data.')) {\n kind = 'json';\n // Positional sentinel — `<field>__t` would collide if the caller\n // projected both `'foo'` and `'foo__t'` (both legal user input).\n typeAliasName = `__fg_t_${idx}`;\n const typeAlias = quoteColumnAlias(typeAliasName);\n projections.push(`json_type(\"data\", '$.${canonical.slice(5)}') AS ${typeAlias}`);\n } else {\n if (canonical === 'v') kind = 'builtin-int';\n else if (canonical === 'createdAt' || canonical === 'updatedAt') kind = 'builtin-timestamp';\n else kind = 'builtin-text';\n }\n columns.push({ field, kind, typeAlias: typeAliasName });\n }\n\n const params: unknown[] = [];\n const conditions: string[] = [];\n for (const f of filters) {\n conditions.push(compileFilter(f, params));\n }\n\n const where = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '';\n let sql = `SELECT ${projections.join(', ')} FROM ${quoteIdent(table)}${where}`;\n sql += compileOrderBy(options, params);\n sql += compileLimit(options, params);\n\n return { stmt: { sql, params }, columns };\n}\n\n/**\n * Decode one SQL row into the projected JS shape: built-in TEXT/INTEGER\n * columns pass through with light coercion, timestamps wrap in\n * `GraphTimestampImpl`, and `data.*` JSON projections use the paired\n * `json_type` column to recover JSON-encoded objects/arrays.\n */\nexport function decodeProjectedRow(\n row: Record<string, unknown>,\n columns: ProjectedColumnSpec[],\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const c of columns) {\n const raw = row[c.field];\n switch (c.kind) {\n case 'builtin-text':\n out[c.field] = raw === null || raw === undefined ? null : String(raw);\n break;\n case 'builtin-int':\n if (raw === null || raw === undefined) {\n out[c.field] = null;\n } else if (typeof raw === 'bigint') {\n out[c.field] = Number(raw);\n } else if (typeof raw === 'number') {\n out[c.field] = raw;\n } else {\n out[c.field] = Number(raw);\n }\n break;\n case 'builtin-timestamp': {\n const ms = rowTimestampToMillis(raw);\n out[c.field] = GraphTimestampImpl.fromMillis(ms) as unknown as GraphTimestamp;\n break;\n }\n case 'data':\n if (raw === null || raw === undefined || raw === '') {\n out[c.field] = {};\n } else {\n out[c.field] = JSON.parse(raw as string);\n }\n break;\n case 'json': {\n // Read the paired `json_type` companion column via the positional\n // sentinel recorded at compile time — see `ProjectedColumnSpec.typeAlias`.\n const t = row[c.typeAlias!] as string | null | undefined;\n if (raw === null || raw === undefined) {\n out[c.field] = null;\n } else if (t === 'object' || t === 'array') {\n out[c.field] = typeof raw === 'string' ? JSON.parse(raw) : raw;\n } else if (t === 'integer' && typeof raw === 'bigint') {\n out[c.field] = Number(raw);\n } else {\n out[c.field] = raw;\n }\n break;\n }\n }\n }\n return out;\n}\n\n/**\n * Compile an aggregate query.\n *\n * SUM/AVG/MIN/MAX cast the JSON-extracted value through\n * `CAST(... AS REAL)` for numeric semantics; without the cast,\n * comparisons would be lexicographic on the underlying string storage.\n *\n * The returned tuple includes the alias list so the JS-side caller can\n * rehydrate the result columns in spec order without reflecting on the\n * raw row keys (which the SQL layer doesn't guarantee a stable order for).\n */\nexport function compileAggregate(\n table: string,\n spec: AggregateSpec,\n filters: QueryFilter[],\n): { stmt: CompiledStatement; aliases: string[] } {\n const aliases = Object.keys(spec);\n if (aliases.length === 0) {\n throw new FiregraphError(\n 'aggregate() requires at least one aggregation in the `aggregates` map.',\n 'INVALID_QUERY',\n );\n }\n\n const projections: string[] = [];\n for (const alias of aliases) {\n const { op, field } = spec[alias];\n // Aliases are inlined into the SQL (SQL aliases can't be bound\n // parameters). Validate against the same JSON-path-key charset rule\n // used everywhere else so caller-supplied aliases can't inject SQL.\n validateJsonPathKey(alias, BACKEND_ERR_LABEL);\n if (op === 'count') {\n // Reject a stray field — see `AggregateField` JSDoc for rationale.\n if (field !== undefined) {\n throw new FiregraphError(\n `Aggregate '${alias}' op 'count' must not specify a field — ` +\n `count operates on rows, not a column expression.`,\n 'INVALID_QUERY',\n );\n }\n projections.push(`COUNT(*) AS ${quoteIdent(alias)}`);\n continue;\n }\n if (!field) {\n throw new FiregraphError(\n `Aggregate '${alias}' op '${op}' requires a field.`,\n 'INVALID_QUERY',\n );\n }\n const { expr } = compileFieldRef(field);\n const numeric = `CAST(${expr} AS REAL)`;\n if (op === 'sum') projections.push(`SUM(${numeric}) AS ${quoteIdent(alias)}`);\n else if (op === 'avg') projections.push(`AVG(${numeric}) AS ${quoteIdent(alias)}`);\n else if (op === 'min') projections.push(`MIN(${numeric}) AS ${quoteIdent(alias)}`);\n else if (op === 'max') projections.push(`MAX(${numeric}) AS ${quoteIdent(alias)}`);\n else\n throw new FiregraphError(\n `SQLite backend does not support aggregate op: ${String(op)}`,\n 'INVALID_QUERY',\n );\n }\n\n const params: unknown[] = [];\n const conditions: string[] = [];\n for (const f of filters) {\n conditions.push(compileFilter(f, params));\n }\n const where = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '';\n const sql = `SELECT ${projections.join(', ')} FROM ${quoteIdent(table)}${where}`;\n return { stmt: { sql, params }, aliases };\n}\n\n/**\n * Compile a `setDoc(record, mode)` call into a single statement.\n *\n * `mode === 'replace'` issues `INSERT OR REPLACE` (full row replacement).\n * `mode === 'merge'` issues `INSERT … ON CONFLICT(doc_id) DO UPDATE SET …`,\n * deep-merging the incoming `data` into the existing JSON via the chained\n * `json_set` / `json_remove` expression produced by `compileDataOpsExpr`.\n * Sibling keys at every depth survive; arrays are terminal (replaced).\n *\n * `created_at` is re-stamped on every put for both modes (matches the\n * cross-backend contract today).\n */\nexport function compileSet(\n table: string,\n docId: string,\n record: WritableRecord,\n nowMillis: number,\n mode: WriteMode,\n): CompiledStatement {\n // Eager validation so the first-insert path can't silently corrupt\n // Firestore special types or drop a DELETE_FIELD sentinel that\n // JSON.stringify would erase.\n assertJsonSafePayload(record.data, BACKEND_LABEL);\n if (mode === 'replace') {\n const sql = `INSERT OR REPLACE INTO ${quoteIdent(table)} (\n doc_id, a_type, a_uid, axb_type, b_type, b_uid, data, v, created_at, updated_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;\n const params: unknown[] = [\n docId,\n record.aType,\n record.aUid,\n record.axbType,\n record.bType,\n record.bUid,\n JSON.stringify(record.data ?? {}),\n record.v ?? null,\n nowMillis,\n nowMillis,\n ];\n return { sql, params };\n }\n\n const insertParams: unknown[] = [\n docId,\n record.aType,\n record.aUid,\n record.axbType,\n record.bType,\n record.bUid,\n JSON.stringify(record.data ?? {}),\n record.v ?? null,\n nowMillis,\n nowMillis,\n ];\n\n const ops = flattenPatch(record.data ?? {});\n const updateParams: unknown[] = [];\n const dataExpr =\n compileDataOpsExpr(ops, `COALESCE(\"data\", '{}')`, updateParams, BACKEND_ERR_LABEL) ??\n `COALESCE(\"data\", '{}')`;\n\n // COALESCE preserves pre-existing `v` when the incoming record has none,\n // matching Firestore's \"undefined leaves the stored field alone\" merge\n // semantic for the version stamp.\n const sql = `INSERT INTO ${quoteIdent(table)} (\n doc_id, a_type, a_uid, axb_type, b_type, b_uid, data, v, created_at, updated_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(doc_id) DO UPDATE SET\n \"a_type\" = excluded.\"a_type\",\n \"a_uid\" = excluded.\"a_uid\",\n \"axb_type\" = excluded.\"axb_type\",\n \"b_type\" = excluded.\"b_type\",\n \"b_uid\" = excluded.\"b_uid\",\n \"data\" = ${dataExpr},\n \"v\" = COALESCE(excluded.\"v\", \"v\"),\n \"created_at\" = excluded.\"created_at\",\n \"updated_at\" = excluded.\"updated_at\"`;\n\n return { sql, params: [...insertParams, ...updateParams] };\n}\n\n/**\n * Compile an `UpdatePayload` into a single UPDATE statement.\n *\n * - `replaceData` overwrites the whole `data` JSON.\n * - `dataOps` applies a deep-path patch via chained `json_remove` /\n * `json_set` — sibling keys at every depth survive; arrays are terminal;\n * delete-ops use `json_remove`.\n * - `v` is set when provided.\n * - `updated_at` is always stamped.\n */\nexport function compileUpdate(\n table: string,\n docId: string,\n update: UpdatePayload,\n nowMillis: number,\n): CompiledStatement {\n assertUpdatePayloadExclusive(update);\n const setClauses: string[] = [];\n const params: unknown[] = [];\n\n if (update.replaceData) {\n assertJsonSafePayload(update.replaceData, BACKEND_LABEL);\n setClauses.push(`\"data\" = ?`);\n params.push(JSON.stringify(update.replaceData));\n } else if (update.dataOps && update.dataOps.length > 0) {\n for (const op of update.dataOps) {\n if (!op.delete) assertJsonSafePayload(op.value, BACKEND_LABEL);\n }\n const expr = compileDataOpsExpr(\n update.dataOps,\n `COALESCE(\"data\", '{}')`,\n params,\n BACKEND_ERR_LABEL,\n );\n if (expr !== null) {\n setClauses.push(`\"data\" = ${expr}`);\n }\n }\n\n if (update.v !== undefined) {\n setClauses.push(`\"v\" = ?`);\n params.push(update.v);\n }\n\n setClauses.push(`\"updated_at\" = ?`);\n params.push(nowMillis);\n\n params.push(docId);\n\n return {\n sql: `UPDATE ${quoteIdent(table)} SET ${setClauses.join(', ')} WHERE \"doc_id\" = ?`,\n params,\n };\n}\n\nexport function compileDelete(table: string, docId: string): CompiledStatement {\n return {\n sql: `DELETE FROM ${quoteIdent(table)} WHERE \"doc_id\" = ?`,\n params: [docId],\n };\n}\n\n/**\n * Compile a server-side bulk DELETE.\n *\n * The compiler accepts an empty filter list (would emit\n * `DELETE FROM <table>`); callers that must never wipe a whole graph\n * reject that shape at their own boundary (the DO RPC layer does, as\n * defense-in-depth against a misconfigured stub).\n */\nexport function compileBulkDelete(table: string, filters: QueryFilter[]): CompiledStatement {\n const params: unknown[] = [];\n const conditions: string[] = [];\n for (const f of filters) {\n conditions.push(compileFilter(f, params));\n }\n const where = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '';\n return {\n sql: `DELETE FROM ${quoteIdent(table)}${where}`,\n params,\n };\n}\n\n/**\n * Compile a server-side bulk UPDATE.\n *\n * The `patch.data` payload is deep-merged into each matching row's `data`\n * field via the same `flattenPatch` → `compileDataOpsExpr` pipeline that\n * `compileUpdate` (single-row) uses. Identifying columns are read-only.\n *\n * Empty patches are rejected — an empty patch would only rewrite\n * `updated_at`, which is almost certainly a caller bug.\n */\nexport function compileBulkUpdate(\n table: string,\n filters: QueryFilter[],\n patchData: Record<string, unknown>,\n nowMillis: number,\n): CompiledStatement {\n const dataOps = flattenPatch(patchData);\n if (dataOps.length === 0) {\n throw new FiregraphError(\n 'bulkUpdate() patch.data must contain at least one leaf — an empty patch ' +\n 'would only rewrite `updated_at`, which is almost certainly a bug. ' +\n 'Use `setDoc` with merge mode if you want to stamp without editing data.',\n 'INVALID_QUERY',\n );\n }\n for (const op of dataOps) {\n if (!op.delete) assertJsonSafePayload(op.value, BACKEND_LABEL);\n }\n const setParams: unknown[] = [];\n const expr = compileDataOpsExpr(dataOps, `COALESCE(\"data\", '{}')`, setParams, BACKEND_ERR_LABEL);\n if (expr === null) {\n throw new FiregraphError(\n 'bulkUpdate() patch produced no SQL operations — internal invariant violated.',\n 'INVALID_ARGUMENT',\n );\n }\n const setClauses: string[] = [`\"data\" = ${expr}`, `\"updated_at\" = ?`];\n setParams.push(nowMillis);\n\n const whereParams: unknown[] = [];\n const conditions: string[] = [];\n for (const f of filters) {\n conditions.push(compileFilter(f, whereParams));\n }\n const where = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '';\n\n return {\n sql: `UPDATE ${quoteIdent(table)} SET ${setClauses.join(', ')}${where}`,\n params: [...setParams, ...whereParams],\n };\n}\n\n/**\n * DELETE every row in the table. Used when tearing down an entire graph —\n * the caller discovers the set of graphs to wipe (registry topology for the\n * DO backend, the graph catalog for the shared SQLite backend) and clears\n * each one.\n */\nexport function compileDeleteAll(table: string): CompiledStatement {\n return {\n sql: `DELETE FROM ${quoteIdent(table)}`,\n params: [],\n };\n}\n\n// ---------------------------------------------------------------------------\n// Row -> record\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a SQLite row into a full `StoredGraphRecord`, wrapping the\n * millisecond timestamp columns in `GraphTimestampImpl`.\n */\nexport function rowToRecord(row: Record<string, unknown>): StoredGraphRecord {\n const dataString = row.data as string | null;\n const data = dataString ? (JSON.parse(dataString) as Record<string, unknown>) : {};\n\n const createdMs = rowTimestampToMillis(row.created_at);\n const updatedMs = rowTimestampToMillis(row.updated_at);\n\n const record: Record<string, unknown> = {\n aType: row.a_type as string,\n aUid: row.a_uid as string,\n axbType: row.axb_type as string,\n bType: row.b_type as string,\n bUid: row.b_uid as string,\n data,\n createdAt: GraphTimestampImpl.fromMillis(createdMs) as unknown as GraphTimestamp,\n updatedAt: GraphTimestampImpl.fromMillis(updatedMs) as unknown as GraphTimestamp,\n };\n\n if (row.v !== null && row.v !== undefined) {\n record.v = Number(row.v);\n }\n return record as unknown as StoredGraphRecord;\n}\n\n/**\n * Coerce a timestamp column value to a plain millis number. The schema types\n * `created_at` / `updated_at` as `INTEGER NOT NULL` so the column should\n * always arrive as a number (or possibly a bigint on some SQLite bindings),\n * but a string row value from SQLite (e.g. BigInt.toString fallback) is also\n * accepted. Anything else indicates a corrupt row — throw loudly rather than\n * silently returning 0, which would quietly mask the bug on every read.\n */\nexport function rowTimestampToMillis(value: unknown): number {\n if (typeof value === 'number') return value;\n if (typeof value === 'bigint') return Number(value);\n if (typeof value === 'string') {\n const n = Number(value);\n if (Number.isFinite(n)) return n;\n }\n throw new FiregraphError(\n `SQLite row has non-numeric timestamp column: ${typeof value} (${String(value)})`,\n 'INVALID_QUERY',\n );\n}\n","/**\n * Graph catalog for the table-per-graph SQLite edition.\n *\n * Each graph (the root, and every subgraph reached via chained\n * `subgraph()` calls) lives in its own SQLite table. The catalog is a\n * small bookkeeping table — `<rootTable>_graphs` — mapping each graph's\n * storage scope (the interleaved `parentUid/name` path that uniquely\n * identifies a subgraph position) to its physical table name and its\n * logical scope path (chained subgraph names, used for `allowedIn`\n * matching).\n *\n * The catalog is what makes registry-free cascade possible: deleting a\n * node with subgraphs prefix-matches descendant storage scopes in the\n * catalog and drops each listed table, with no need to know the subgraph\n * topology from a schema registry.\n */\n\nimport { quoteIdent, validateTableName } from '../internal/sqlite-schema.js';\nimport type { CompiledStatement } from '../internal/sqlite-sql.js';\n\n/** Name of the catalog table that tracks every graph under `rootTable`. */\nexport function catalogTableName(rootTable: string): string {\n validateTableName(rootTable);\n return `${rootTable}_graphs`;\n}\n\n/**\n * Deterministically mangle a storage scope into a SQL-identifier-safe\n * table-name suffix. The encoding is injective so two distinct scopes can\n * never collide on the same table:\n *\n * - `[A-Za-z0-9]` pass through\n * - `_` → `__` (escape char, doubled)\n * - `-` → `_h` (nanoid alphabet includes `-`)\n * - `/` → `_s` (scope segment separator)\n * - anything else → `_u<hex codepoint>_`\n *\n * Every escape sequence starts with `_` and no passthrough character is\n * `_`, so decoding is unambiguous (not that we ever decode — the catalog\n * stores the original scope alongside the table name).\n */\nexport function mangleStorageScope(scope: string): string {\n let out = '';\n for (const ch of scope) {\n if (/[A-Za-z0-9]/.test(ch)) out += ch;\n else if (ch === '_') out += '__';\n else if (ch === '-') out += '_h';\n else if (ch === '/') out += '_s';\n else out += `_u${ch.codePointAt(0)!.toString(16)}_`;\n }\n return out;\n}\n\n/**\n * Resolve the physical table for a graph position. The root graph\n * (`storageScope === ''`) uses `rootTable` itself; subgraphs get\n * `<rootTable>_g_<mangled scope>`.\n *\n * The `_g_` infix keeps subgraph tables disjoint from both the root table\n * and the catalog (`<rootTable>_graphs` — `_graphs` is never `_g_<x>`).\n */\nexport function tableForScope(rootTable: string, storageScope: string): string {\n validateTableName(rootTable);\n if (storageScope === '') return rootTable;\n return `${rootTable}_g_${mangleStorageScope(storageScope)}`;\n}\n\n/**\n * Escape LIKE wildcards in a literal prefix. The nanoid alphabet includes\n * `_` (a single-char LIKE wildcard), so prefix matches against storage\n * scopes MUST escape and carry an `ESCAPE '\\'` clause — otherwise a scope\n * containing `_` would match unrelated siblings.\n */\nexport function escapeLikePrefix(prefix: string): string {\n return prefix.replace(/[\\\\%_]/g, (c) => `\\\\${c}`);\n}\n\n/** DDL for the catalog table. Idempotent. */\nexport function buildCatalogDDL(rootTable: string): string {\n const t = quoteIdent(catalogTableName(rootTable));\n return `CREATE TABLE IF NOT EXISTS ${t} (\n storage_scope TEXT NOT NULL PRIMARY KEY,\n table_name TEXT NOT NULL UNIQUE,\n scope_path TEXT NOT NULL\n )`;\n}\n\n/** Register a graph in the catalog. Idempotent (`INSERT OR IGNORE`). */\nexport function compileCatalogRegister(\n rootTable: string,\n storageScope: string,\n tableName: string,\n scopePath: string,\n): CompiledStatement {\n const t = quoteIdent(catalogTableName(rootTable));\n return {\n sql: `INSERT OR IGNORE INTO ${t} (storage_scope, table_name, scope_path) VALUES (?, ?, ?)`,\n params: [storageScope, tableName, scopePath],\n };\n}\n\n/**\n * List every descendant graph whose storage scope starts with\n * `scopePrefix + '/'`. Used by cascade delete to discover which tables to\n * drop. Ordered by scope for deterministic statement order.\n */\nexport function compileCatalogDescendants(\n rootTable: string,\n scopePrefix: string,\n): CompiledStatement {\n const t = quoteIdent(catalogTableName(rootTable));\n return {\n sql:\n `SELECT storage_scope, table_name FROM ${t} ` +\n `WHERE storage_scope LIKE ? ESCAPE '\\\\' ORDER BY storage_scope`,\n params: [`${escapeLikePrefix(scopePrefix)}/%`],\n };\n}\n\n/** Remove one graph's catalog row (paired with its DROP TABLE). */\nexport function compileCatalogDelete(rootTable: string, storageScope: string): CompiledStatement {\n const t = quoteIdent(catalogTableName(rootTable));\n return {\n sql: `DELETE FROM ${t} WHERE storage_scope = ?`,\n params: [storageScope],\n };\n}\n","/**\n * SQLite implementation of `StorageBackend`.\n *\n * Table-per-graph design: the root graph lives in `tableName`, and each\n * subgraph lives in its own physical table (`<tableName>_g_<mangled scope>`,\n * see `tableForScope`). There is no `scope` column — the table a row lives\n * in *is* its scope, exactly like the Cloudflare DO edition where each\n * subgraph is its own Durable Object.\n *\n * A small catalog table (`<tableName>_graphs`) records every graph's\n * storage scope → table mapping. Cascade delete prefix-matches descendant\n * scopes in the catalog and drops each listed table — no registry topology\n * required.\n *\n * Schema is ensured lazily: the first operation on a backend instance runs\n * `CREATE TABLE IF NOT EXISTS` for the graph's table, its indexes, and the\n * catalog, then registers the graph in the catalog. Callers no longer\n * pre-create tables.\n */\n\nimport { computeEdgeDocId, computeNodeDocId } from '../docid.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 { NODE_RELATION } from '../internal/constants.js';\nimport type { SqliteExecutor, SqliteTxExecutor } from '../internal/sqlite-executor.js';\nimport { buildSchemaStatements, quoteIdent, validateTableName } from '../internal/sqlite-schema.js';\nimport type { CompiledStatement } from '../internal/sqlite-sql.js';\nimport {\n compileAggregate,\n compileBulkDelete,\n compileBulkUpdate,\n compileDelete,\n compileExpand,\n compileExpandHydrate,\n compileFindEdgesProjected,\n compileSelect,\n compileSelectByDocId,\n compileSet,\n compileUpdate,\n decodeProjectedRow,\n rowToRecord,\n} from '../internal/sqlite-sql.js';\nimport type {\n AggregateSpec,\n BulkBatchError,\n BulkOptions,\n BulkResult,\n BulkUpdatePatch,\n CascadeResult,\n ExpandParams,\n ExpandResult,\n FindEdgesParams,\n GraphReader,\n GraphRegistry,\n IndexSpec,\n QueryFilter,\n QueryOptions,\n StoredGraphRecord,\n} from '../types.js';\nimport {\n buildCatalogDDL,\n compileCatalogDelete,\n compileCatalogDescendants,\n compileCatalogRegister,\n tableForScope,\n} from './catalog.js';\n\nexport interface SqliteBackendOptions {\n /** Logical scope path (chained subgraph names) — used for `allowedIn` matching. */\n scopePath?: string;\n /**\n * Internal storage scope (interleaved parent-uid/name path). Determines\n * which physical table this backend reads and writes — `''` (the default)\n * is the root graph in `tableName` itself.\n *\n * @internal Used by `subgraph()` to derive child backends. Setting it\n * directly bypasses catalog registration consistency checks (the graph\n * still self-registers, but ancestors are not validated) — always derive\n * subgraph backends via `subgraph()` instead.\n */\n storageScope?: string;\n /**\n * Registry contributing per-entry `indexes` declarations, applied to\n * every graph table this backend (and its subgraphs) lazily creates.\n */\n registry?: GraphRegistry;\n /**\n * Replaces the built-in core index preset for lazily created tables.\n * Pass `[]` to disable core indexes entirely.\n */\n coreIndexes?: IndexSpec[];\n}\n\n/**\n * Default per-chunk retry budget for bulk/cascade operations. Mirrors the\n * Firestore bulk path (`src/bulk.ts`) so behaviour is consistent across\n * backends. Callers override via `BulkOptions.maxRetries`.\n */\nconst DEFAULT_MAX_RETRIES = 3;\nconst BASE_RETRY_DELAY_MS = 200;\n/**\n * Upper bound for the exponential backoff between chunk retries. Without\n * this cap, `maxRetries: 10` would push the final wait past 100s; legitimate\n * transient errors recover well within a few seconds, and longer waits just\n * delay the surfacing of permanent failures.\n */\nconst MAX_RETRY_DELAY_MS = 5000;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Return the smaller of two optional positive numbers, treating `undefined`\n * as \"no cap.\" Used to combine caller-supplied `BulkOptions.batchSize` with\n * the driver's own `maxBatchSize` so the more restrictive cap wins.\n */\nfunction minDefined(a: number | undefined, b: number | undefined): number | undefined {\n if (a === undefined) return b;\n if (b === undefined) return a;\n return Math.min(a, b);\n}\n\n/**\n * Split `statements` into chunks that respect both a per-batch statement\n * count cap (`maxStatements`) and a per-batch total bound-parameter cap\n * (`maxParams`). When neither cap is provided the entire list is returned\n * as a single chunk (preserves cross-batch atomicity for drivers like\n * DO SQLite that have no caps).\n *\n * Single-statement edge case: if a single statement's parameter count\n * already exceeds `maxParams`, it's emitted as its own chunk anyway. The\n * driver will reject it, which is the correct behavior — silently\n * dropping it would be worse.\n */\nfunction chunkStatements<T extends { params: unknown[] }>(\n statements: T[],\n maxStatements: number | undefined,\n maxParams: number | undefined,\n): T[][] {\n const stmtCap =\n maxStatements && maxStatements > 0 && Number.isFinite(maxStatements)\n ? Math.floor(maxStatements)\n : Infinity;\n const paramCap =\n maxParams && maxParams > 0 && Number.isFinite(maxParams) ? Math.floor(maxParams) : Infinity;\n\n if (stmtCap === Infinity && paramCap === Infinity) {\n return [statements];\n }\n\n const chunks: T[][] = [];\n let current: T[] = [];\n let currentParamCount = 0;\n for (const stmt of statements) {\n const stmtParams = stmt.params.length;\n const wouldExceedStmt = current.length + 1 > stmtCap;\n const wouldExceedParam = currentParamCount + stmtParams > paramCap;\n if (current.length > 0 && (wouldExceedStmt || wouldExceedParam)) {\n chunks.push(current);\n current = [];\n currentParamCount = 0;\n }\n current.push(stmt);\n currentParamCount += stmtParams;\n }\n if (current.length > 0) chunks.push(current);\n return chunks;\n}\n\nclass SqliteTransactionBackendImpl implements TransactionBackend {\n constructor(\n private readonly tx: SqliteTxExecutor,\n private readonly tableName: string,\n ) {}\n\n async getDoc(docId: string): Promise<StoredGraphRecord | null> {\n const stmt = compileSelectByDocId(this.tableName, docId);\n const rows = await this.tx.all(stmt.sql, stmt.params);\n return rows.length === 0 ? null : rowToRecord(rows[0]);\n }\n\n async query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]> {\n const stmt = compileSelect(this.tableName, filters, options);\n const rows = await this.tx.all(stmt.sql, stmt.params);\n return rows.map(rowToRecord);\n }\n\n async setDoc(docId: string, record: WritableRecord, mode: WriteMode): Promise<void> {\n const stmt = compileSet(this.tableName, docId, record, Date.now(), mode);\n await this.tx.run(stmt.sql, stmt.params);\n }\n\n async updateDoc(docId: string, update: UpdatePayload): Promise<void> {\n const stmt = compileUpdate(this.tableName, docId, update, Date.now());\n // RETURNING + `all()` for parity with Firestore — see SqliteBackendImpl.updateDoc.\n const sqlWithReturning = `${stmt.sql} RETURNING \"doc_id\"`;\n const rows = await this.tx.all(sqlWithReturning, stmt.params);\n if (rows.length === 0) {\n throw new FiregraphError(\n `updateDoc: no document found for doc_id=${docId} (table=${this.tableName})`,\n 'NOT_FOUND',\n );\n }\n }\n\n async deleteDoc(docId: string): Promise<void> {\n const stmt = compileDelete(this.tableName, docId);\n await this.tx.run(stmt.sql, stmt.params);\n }\n}\n\nclass SqliteBatchBackendImpl implements BatchBackend {\n private readonly statements: CompiledStatement[] = [];\n\n constructor(\n private readonly executor: SqliteExecutor,\n private readonly tableName: string,\n private readonly ensureSchema: () => Promise<void>,\n ) {}\n\n setDoc(docId: string, record: WritableRecord, mode: WriteMode): void {\n this.statements.push(compileSet(this.tableName, docId, record, Date.now(), mode));\n }\n\n updateDoc(docId: string, update: UpdatePayload): void {\n this.statements.push(compileUpdate(this.tableName, docId, update, Date.now()));\n }\n\n deleteDoc(docId: string): void {\n this.statements.push(compileDelete(this.tableName, docId));\n }\n\n async commit(): Promise<void> {\n if (this.statements.length === 0) return;\n await this.ensureSchema();\n await this.executor.batch(this.statements);\n this.statements.length = 0;\n }\n}\n\n/**\n * Capability union declared by the SQLite-backed `StorageBackend`.\n *\n * `core.transactions` is part of the static union because `runTransaction`\n * is always present as a method on the class. The runtime cap-set determines\n * whether that method is *functional*: D1 leaves `executor.transaction`\n * undefined and the call throws `UNSUPPORTED_OPERATION`; DO SQLite and\n * better-sqlite3 wire the executor and the call works. The static type\n * therefore promises only that the method exists — callers that care about\n * portability check `client.capabilities.has('core.transactions')` before\n * opening a tx, and code that runs against an unknown driver can rely on the\n * runtime guard inside `runTransaction`.\n *\n * The `query.*` extension capabilities follow the same conservative\n * declaration rule as the cap descriptor itself — only land in the union\n * when the corresponding method is actually wired up. Today that's\n * `query.aggregate` (Phase 4), `query.dml` (Phase 5), `query.join`\n * (Phase 6 — fan-out via `IN (…)` in one statement), and `query.select`\n * (Phase 7 — server-side projection via `json_extract`).\n */\nexport type SqliteCapability =\n | 'core.read'\n | 'core.write'\n | 'core.transactions'\n | 'core.batch'\n | 'core.subgraph'\n | 'query.aggregate'\n | 'query.dml'\n | 'query.join'\n | 'query.select'\n | 'raw.sql';\n\nconst SQLITE_CORE_CAPS: ReadonlyArray<SqliteCapability> = [\n 'core.read',\n 'core.write',\n 'core.batch',\n 'core.subgraph',\n 'query.aggregate',\n 'query.dml',\n 'query.join',\n 'query.select',\n 'raw.sql',\n];\n\nclass SqliteBackendImpl implements StorageBackend<SqliteCapability> {\n readonly capabilities: BackendCapabilities<SqliteCapability>;\n /** Physical table holding this graph's triples. */\n readonly collectionPath: string;\n readonly scopePath: string;\n /** Storage scope (interleaved parent UIDs + subgraph names) — `''` at root. */\n private readonly storageScope: string;\n /** Root graph's table name — prefix for subgraph tables and the catalog. */\n private readonly rootTable: string;\n private readonly registry: GraphRegistry | undefined;\n private readonly coreIndexes: IndexSpec[] | undefined;\n private ensured: Promise<void> | null = null;\n\n constructor(\n private readonly executor: SqliteExecutor,\n rootTable: string,\n storageScope: string,\n scopePath: string,\n registry: GraphRegistry | undefined,\n coreIndexes: IndexSpec[] | undefined,\n ) {\n validateTableName(rootTable);\n this.rootTable = rootTable;\n this.collectionPath = tableForScope(rootTable, storageScope);\n this.storageScope = storageScope;\n this.scopePath = scopePath;\n this.registry = registry;\n this.coreIndexes = coreIndexes;\n const caps = new Set<SqliteCapability>(SQLITE_CORE_CAPS);\n if (typeof executor.transaction === 'function') {\n caps.add('core.transactions');\n }\n this.capabilities = createCapabilities(caps);\n }\n\n /**\n * Lazily create this graph's table + indexes + the catalog, and register\n * the graph in the catalog. Runs once per backend instance; the DDL is\n * all `IF NOT EXISTS` / `INSERT OR IGNORE`, so concurrent instances over\n * the same database converge safely.\n */\n private ensureSchema(): Promise<void> {\n if (!this.ensured) {\n this.ensured = this.doEnsureSchema().catch((err) => {\n // Allow retry on the next operation rather than caching the failure.\n this.ensured = null;\n throw err;\n });\n }\n return this.ensured;\n }\n\n private async doEnsureSchema(): Promise<void> {\n const ddl = [\n ...buildSchemaStatements(this.collectionPath, {\n coreIndexes: this.coreIndexes,\n registry: this.registry,\n }),\n buildCatalogDDL(this.rootTable),\n ];\n const statements: CompiledStatement[] = ddl.map((sql) => ({ sql, params: [] }));\n statements.push(\n compileCatalogRegister(\n this.rootTable,\n this.storageScope,\n this.collectionPath,\n this.scopePath,\n ),\n );\n // Respect the driver's batch caps (D1 ≈ 100 statements / ≈ 1000 params) —\n // a registry with many index declarations could push the DDL list past\n // them. Every statement is IF NOT EXISTS / INSERT OR IGNORE, so losing\n // cross-chunk atomicity here is harmless: a partial bootstrap converges\n // on the next attempt.\n const chunks = chunkStatements(\n statements,\n this.executor.maxBatchSize,\n this.executor.maxBatchParams,\n );\n for (const chunk of chunks) {\n await this.executor.batch(chunk);\n }\n }\n\n /**\n * Run `op` with the schema bootstrap applied, self-healing when this\n * graph's table was dropped out from under the instance — a parent's\n * cascade delete DROPs descendant tables, but subgraph handles created\n * before the cascade still point at this (now missing) table with a\n * resolved bootstrap cache. On a \"no such table: <own table>\" error the\n * cache resets, the empty graph is recreated, and the op retries once.\n * This matches Firestore semantics, where a deleted subcollection reads\n * as empty and writes recreate it.\n */\n private async withSchema<T>(op: () => Promise<T>): Promise<T> {\n await this.ensureSchema();\n try {\n return await op();\n } catch (err) {\n if (!this.isMissingOwnTable(err)) throw err;\n this.ensured = null;\n await this.ensureSchema();\n return op();\n }\n }\n\n /** True when `err` is SQLite's missing-table error naming OUR table. */\n private isMissingOwnTable(err: unknown): boolean {\n const message = err instanceof Error ? err.message : String(err);\n return message.includes(`no such table: ${this.collectionPath}`);\n }\n\n // --- Reads ---\n\n async getDoc(docId: string): Promise<StoredGraphRecord | null> {\n return this.withSchema(async () => {\n const stmt = compileSelectByDocId(this.collectionPath, docId);\n const rows = await this.executor.all(stmt.sql, stmt.params);\n return rows.length === 0 ? null : rowToRecord(rows[0]);\n });\n }\n\n async query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]> {\n return this.withSchema(async () => {\n const stmt = compileSelect(this.collectionPath, filters, options);\n const rows = await this.executor.all(stmt.sql, stmt.params);\n return rows.map(rowToRecord);\n });\n }\n\n // --- Writes ---\n\n async setDoc(docId: string, record: WritableRecord, mode: WriteMode): Promise<void> {\n return this.withSchema(async () => {\n const stmt = compileSet(this.collectionPath, docId, record, Date.now(), mode);\n await this.executor.run(stmt.sql, stmt.params);\n });\n }\n\n async updateDoc(docId: string, update: UpdatePayload): Promise<void> {\n return this.withSchema(async () => {\n const stmt = compileUpdate(this.collectionPath, docId, update, Date.now());\n // Use RETURNING + `all()` so missing rows surface as an error, matching\n // Firestore's `update()` semantics (NOT_FOUND when the doc doesn't exist).\n // SQLite ≥3.35 supports UPDATE … RETURNING; better-sqlite3, D1, and DO\n // SQLite all run on a recent enough engine.\n const sqlWithReturning = `${stmt.sql} RETURNING \"doc_id\"`;\n const rows = await this.executor.all(sqlWithReturning, stmt.params);\n if (rows.length === 0) {\n throw new FiregraphError(\n `updateDoc: no document found for doc_id=${docId} (table=${this.collectionPath})`,\n 'NOT_FOUND',\n );\n }\n });\n }\n\n async deleteDoc(docId: string): Promise<void> {\n return this.withSchema(async () => {\n const stmt = compileDelete(this.collectionPath, docId);\n await this.executor.run(stmt.sql, stmt.params);\n });\n }\n\n // --- Transactions / Batches ---\n\n async runTransaction<T>(fn: (tx: TransactionBackend) => Promise<T>): Promise<T> {\n if (!this.executor.transaction) {\n throw new FiregraphError(\n 'Interactive transactions are not supported by this SQLite driver. ' +\n 'D1 in particular has no read-then-conditional-write transactions; ' +\n 'use a Durable Object SQLite client instead, or rewrite the code path ' +\n 'as a batch().',\n 'UNSUPPORTED_OPERATION',\n );\n }\n // Schema must exist before the tx opens — DDL inside an interactive\n // transaction would commit with it, complicating rollback semantics.\n await this.ensureSchema();\n return this.executor.transaction(async (tx) => {\n const txBackend = new SqliteTransactionBackendImpl(tx, this.collectionPath);\n return fn(txBackend);\n });\n }\n\n createBatch(): BatchBackend {\n return new SqliteBatchBackendImpl(this.executor, this.collectionPath, () =>\n this.ensureSchema(),\n );\n }\n\n // --- Subgraphs ---\n\n subgraph(parentNodeUid: string, name: string): StorageBackend {\n // Defense-in-depth: the public `GraphClient.subgraph()` also validates,\n // but backend users (traversal, cross-graph hops, custom integrations)\n // reach this method directly. A bad UID or a name containing '/' would\n // corrupt the materialized-path scope encoding — reject loudly.\n if (!parentNodeUid || parentNodeUid.includes('/')) {\n throw new FiregraphError(\n `Invalid parentNodeUid for subgraph: \"${parentNodeUid}\". ` +\n 'Must be a non-empty string without \"/\".',\n 'INVALID_SUBGRAPH',\n );\n }\n if (!name || name.includes('/')) {\n throw new FiregraphError(\n `Subgraph name must not contain \"/\" and must be non-empty: got \"${name}\". ` +\n 'Use chained .subgraph() calls for nested subgraphs.',\n 'INVALID_SUBGRAPH',\n );\n }\n const newStorageScope = this.storageScope\n ? `${this.storageScope}/${parentNodeUid}/${name}`\n : `${parentNodeUid}/${name}`;\n const newScope = this.scopePath ? `${this.scopePath}/${name}` : name;\n return new SqliteBackendImpl(\n this.executor,\n this.rootTable,\n newStorageScope,\n newScope,\n this.registry,\n this.coreIndexes,\n );\n }\n\n // --- Cascade & bulk ---\n\n async removeNodeCascade(\n uid: string,\n reader: GraphReader,\n options?: BulkOptions,\n ): Promise<CascadeResult> {\n await this.ensureSchema();\n // Collect all edges touching the node in the current graph (excluding self-loop).\n const [outgoingRaw, incomingRaw] = await Promise.all([\n reader.findEdges({ aUid: uid, allowCollectionScan: true, limit: 0 }),\n reader.findEdges({ bUid: uid, allowCollectionScan: true, limit: 0 }),\n ]);\n\n const seen = new Set<string>();\n const edgeDocIds: string[] = [];\n for (const edge of [...outgoingRaw, ...incomingRaw]) {\n if (edge.axbType === NODE_RELATION) continue;\n const docId = computeEdgeDocId(edge.aUid, edge.axbType, edge.bUid);\n if (!seen.has(docId)) {\n seen.add(docId);\n edgeDocIds.push(docId);\n }\n }\n\n const nodeDocId = computeNodeDocId(uid);\n const shouldDeleteSubgraphs = options?.deleteSubcollections !== false;\n\n // Discover descendant graphs via the catalog — every subgraph under this\n // node has a storage scope starting with `<scope>/<uid>/`. Pre-count\n // each table's rows so the returned `deleted` total reflects actual\n // records removed by the DROPs, not bookkeeping statements.\n const descendants: Array<{ storageScope: string; tableName: string }> = [];\n let subgraphRowCount = 0;\n if (shouldDeleteSubgraphs) {\n const prefix = this.storageScope ? `${this.storageScope}/${uid}` : uid;\n const descStmt = compileCatalogDescendants(this.rootTable, prefix);\n const rows = await this.executor.all(descStmt.sql, descStmt.params);\n for (const row of rows) {\n const tableName = String(row.table_name);\n // Catalog rows are written exclusively by `doEnsureSchema` with\n // mangled names, but validate anyway — these get interpolated into\n // DROP TABLE statements.\n validateTableName(tableName);\n descendants.push({ storageScope: String(row.storage_scope), tableName });\n }\n for (const d of descendants) {\n const countRows = await this.executor.all(\n `SELECT COUNT(*) AS n FROM ${quoteIdent(d.tableName)}`,\n [],\n );\n const n = (countRows[0] as Record<string, unknown> | undefined)?.n;\n subgraphRowCount += typeof n === 'bigint' ? Number(n) : Number(n ?? 0);\n }\n }\n\n // Build the full statement list. Order: edges → node → per-descendant\n // (DROP TABLE + catalog row delete). When the executor's `batch()` is\n // fully atomic the chunking loop below collapses to a single batch and\n // the operation is atomic. When the executor caps batches (D1, ~100\n // statements) we lose cross-batch atomicity, but `removeNodeCascade` is\n // idempotent so a caller can retry after a partial failure.\n const writeStatements: CompiledStatement[] = edgeDocIds.map((id) =>\n compileDelete(this.collectionPath, id),\n );\n writeStatements.push(compileDelete(this.collectionPath, nodeDocId));\n for (const d of descendants) {\n writeStatements.push({ sql: `DROP TABLE IF EXISTS ${quoteIdent(d.tableName)}`, params: [] });\n writeStatements.push(compileCatalogDelete(this.rootTable, d.storageScope));\n }\n\n const {\n deleted: stmtDeleted,\n batches,\n errors,\n } = await this.executeChunkedBatches(writeStatements, options);\n\n // `nodeDeleted` / `edgesDeleted` reflect best-effort completion: a\n // chunk failure leaves us unable to know which sub-batch contained the\n // node-row delete, so we conservatively flag both as incomplete when\n // any batch fails. The caller can retry — cascade is idempotent.\n const allOk = errors.length === 0;\n const edgesDeleted = allOk ? edgeDocIds.length : 0;\n // `nodeDeleted: true` means \"the node doc is gone\" — deletes are\n // idempotent, so a nonexistent node still reports true. This matches the\n // Firestore edition and the cross-backend contract pinned by\n // tests/integration/bulk.test.ts (\"handles nonexistent node gracefully\").\n const nodeDeleted = allOk;\n\n // `stmtDeleted` counts committed *statements*. Replace the per-descendant\n // bookkeeping statements' contribution (DROP + catalog delete = 2 each)\n // with the pre-computed row totals so callers see a true record count.\n // Only credit subgraph rows when every chunk succeeded — partial failure\n // means we can't be sure the chunks containing the DROPs committed.\n const bookkeepingContribution = allOk ? descendants.length * 2 : 0;\n const deleted = stmtDeleted - bookkeepingContribution + (allOk ? subgraphRowCount : 0);\n\n return { deleted, batches, errors, edgesDeleted, nodeDeleted };\n }\n\n async bulkRemoveEdges(\n params: FindEdgesParams,\n reader: GraphReader,\n options?: BulkOptions,\n ): Promise<BulkResult> {\n await this.ensureSchema();\n // Override default query limit for bulk deletion — we need all matching edges.\n // limit: 0 bypasses DEFAULT_QUERY_LIMIT; an explicit user limit is preserved.\n // allowCollectionScan: true — bulk deletion inherently implies scanning.\n const effectiveParams =\n params.limit !== undefined\n ? { ...params, allowCollectionScan: params.allowCollectionScan ?? true }\n : { ...params, limit: 0, allowCollectionScan: params.allowCollectionScan ?? true };\n const edges = await reader.findEdges(effectiveParams);\n const docIds = edges.map((e) => computeEdgeDocId(e.aUid, e.axbType, e.bUid));\n\n if (docIds.length === 0) {\n return { deleted: 0, batches: 0, errors: [] };\n }\n\n const statements = docIds.map((id) => compileDelete(this.collectionPath, id));\n\n return this.executeChunkedBatches(statements, options);\n }\n\n /**\n * Submit `statements` to the executor as one or more `batch()` calls,\n * chunking by `executor.maxBatchSize` (e.g. D1's ~100-statement cap).\n * Drivers that don't advertise a cap submit everything in one batch,\n * preserving cross-batch atomicity.\n *\n * Each chunk is retried with exponential backoff up to `maxRetries`\n * (default 3) before being recorded in `errors`. The loop continues past\n * a permanently failed chunk so the caller still gets partial progress\n * visibility — to halt on first failure, set `maxRetries: 0` and check\n * `result.errors.length` after the call.\n *\n * Returns `BulkResult`-shaped fields. `deleted` reflects only the\n * statement count of *successfully committed* batches — a DROP TABLE\n * statement contributes 1 to that total even though it may remove many\n * rows; `removeNodeCascade` patches that up with pre-counted row totals.\n *\n * **Atomicity caveat (D1):** when chunking kicks in, atomicity is lost\n * across chunk boundaries — one chunk may commit while a later one fails.\n * `removeNodeCascade` is idempotent (deleting the same docs again is a\n * no-op) so a caller can simply retry on partial failure. `bulkRemoveEdges`\n * is also idempotent for the same reason. DO SQLite leaves `maxBatchSize`\n * unset, so everything funnels through one atomic `transactionSync` and\n * this caveat does not apply.\n */\n private async executeChunkedBatches(\n statements: CompiledStatement[],\n options?: BulkOptions,\n ): Promise<{ deleted: number; batches: number; errors: BulkBatchError[] }> {\n if (statements.length === 0) {\n return { deleted: 0, batches: 0, errors: [] };\n }\n const maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES;\n\n // Split `statements` into chunks up front. Chunking honors the smallest\n // of: caller-supplied `batchSize` (used by callers who want progress\n // granularity), the driver's statement-count cap (`maxBatchSize`, D1 ≈\n // 100), and the driver's total bound-parameter cap (`maxBatchParams`,\n // D1 ≈ 1000). Most cascade/bulk statements are 1-param DELETEs so the\n // param cap rarely triggers, but we respect it defensively. Drivers with\n // no declared caps and no caller cap submit everything in one batch (DO\n // SQLite's atomic `transactionSync`).\n const callerBatchSize = options?.batchSize;\n const stmtCap = minDefined(callerBatchSize, this.executor.maxBatchSize);\n const chunks = chunkStatements(statements, stmtCap, this.executor.maxBatchParams);\n\n const errors: BulkBatchError[] = [];\n let deleted = 0;\n let batches = 0;\n const totalBatches = chunks.length;\n\n const driverParamCap = this.executor.maxBatchParams;\n\n for (let batchIndex = 0; batchIndex < chunks.length; batchIndex++) {\n const chunk = chunks[batchIndex];\n\n // A chunk that's a single statement whose param count already exceeds\n // the driver's per-batch param cap will be rejected on every attempt —\n // retrying just adds latency before surfacing the failure. `chunkStatements`\n // intentionally emits such statements as their own chunk (failing loudly\n // beats silently dropping); fast-fail here closes the loop.\n const isUnretriableOversize =\n chunk.length === 1 &&\n driverParamCap !== undefined &&\n chunk[0].params.length > driverParamCap;\n\n let committed = false;\n let lastError: Error | null = null;\n const effectiveRetries = isUnretriableOversize ? 0 : maxRetries;\n for (let attempt = 0; attempt <= effectiveRetries; attempt++) {\n try {\n await this.executor.batch(chunk);\n committed = true;\n break;\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n if (attempt < effectiveRetries) {\n const delay = Math.min(BASE_RETRY_DELAY_MS * Math.pow(2, attempt), MAX_RETRY_DELAY_MS);\n await sleep(delay);\n }\n }\n }\n\n if (committed) {\n deleted += chunk.length;\n batches += 1;\n } else if (lastError) {\n errors.push({\n batchIndex,\n error: lastError,\n operationCount: chunk.length,\n });\n }\n\n if (options?.onProgress) {\n options.onProgress({\n completedBatches: batches,\n totalBatches,\n deletedSoFar: deleted,\n });\n }\n }\n\n return { deleted, batches, errors };\n }\n\n // `findEdgesGlobal` is deliberately NOT defined on this class. Each graph\n // is its own table, so a \"collection group\" query would mean scanning every\n // table listed in the catalog — an unbounded fan-out the cross-backend\n // contract treats as unsupported (the Cloudflare DO edition makes the same\n // call: no cross-DO index, no `findEdgesGlobal`). The client surfaces\n // `UNSUPPORTED_OPERATION` when the method is absent.\n\n // --- Aggregate ---\n\n /**\n * Run an aggregate query in a single SQL statement. Supports the full\n * count/sum/avg/min/max set — the SQLite engine evaluates each aggregate\n * function over the filtered row set and the executor returns one row\n * with one column per alias. SUM/MIN/MAX of an empty set returns 0\n * (SQLite's `SUM(NULL) = NULL` is mapped to a clean number for the\n * cross-backend contract); AVG returns NaN, matching the mathematical\n * convention and the Firestore Standard helper.\n */\n async aggregate(spec: AggregateSpec, filters: QueryFilter[]): Promise<Record<string, number>> {\n const { stmt, aliases } = compileAggregate(this.collectionPath, spec, filters);\n const rows = await this.withSchema(() => this.executor.all(stmt.sql, stmt.params));\n const row = rows[0] ?? {};\n const out: Record<string, number> = {};\n for (const alias of aliases) {\n const v = row[alias];\n if (v === null || v === undefined) {\n // SQLite returns NULL for SUM/MIN/MAX over an empty set. Resolve\n // to 0 for SUM/MIN/MAX (well-defined) and NaN for AVG (empty-set\n // average is undefined). COUNT(*) is never null.\n const op = spec[alias].op;\n out[alias] = op === 'avg' ? Number.NaN : 0;\n } else if (typeof v === 'bigint') {\n out[alias] = Number(v);\n } else if (typeof v === 'number') {\n out[alias] = v;\n } else {\n // Some drivers return strings for very large or precise numerics.\n // Coerce defensively — the contract is `number`.\n out[alias] = Number(v);\n }\n }\n return out;\n }\n\n // --- Server-side DML ---\n\n /**\n * Delete every row matching `filters` in a single SQL DELETE statement.\n *\n * Uses `RETURNING \"doc_id\"` to count rows touched — the SQLite executor's\n * `run` returns void, so RETURNING + `all()` is the portable way to learn\n * how many rows the engine actually deleted. SQLite ≥ 3.35 supports\n * `DELETE … RETURNING`; better-sqlite3, D1, and DO SQLite all run on a\n * recent enough engine.\n *\n * Single-statement DML doesn't chunk: the engine handles N rows in one\n * shot, so `BulkOptions.batchSize` is intentionally ignored. The retry\n * loop here exists only for transient driver errors (e.g. D1 surface\n * congestion); a permanent failure is surfaced via the `errors` array\n * with `batchIndex: 0` so callers see the same shape as `bulkRemoveEdges`.\n *\n * Subgraph isolation is physical — the statement only ever touches this\n * graph's table, so no scoping predicate is needed.\n */\n async bulkDelete(filters: QueryFilter[], options?: BulkOptions): Promise<BulkResult> {\n await this.ensureSchema();\n const stmt = compileBulkDelete(this.collectionPath, filters);\n return this.executeDmlWithReturning(stmt, options);\n }\n\n /**\n * Update every row matching `filters` with `patch.data` in a single SQL\n * UPDATE statement. The patch is deep-merged into each row's `data`\n * column via the same `flattenPatch` → `compileDataOpsExpr` pipeline that\n * `compileUpdate` (single-row) uses.\n *\n * Same contract notes as `bulkDelete` apply: single-statement, no\n * chunking, `RETURNING \"doc_id\"` for the affected count, retry loop for\n * transient driver errors.\n */\n async bulkUpdate(\n filters: QueryFilter[],\n patch: BulkUpdatePatch,\n options?: BulkOptions,\n ): Promise<BulkResult> {\n await this.ensureSchema();\n const stmt = compileBulkUpdate(this.collectionPath, filters, patch.data, Date.now());\n return this.executeDmlWithReturning(stmt, options);\n }\n\n /**\n * Multi-source fan-out — `query.join` capability.\n *\n * Issues a single `SELECT … WHERE \"aUid\" IN (?, ?, …)` statement that\n * matches every edge from every source UID in one round trip. When\n * `params.hydrate === true`, follows up with a second statement that\n * fetches the target node rows; both queries hit the same table so\n * the executor amortises connection / parsing cost across them.\n *\n * Empty `params.sources` short-circuits to an empty result without\n * touching the executor — `IN ()` is not valid SQL.\n *\n * Per-source ordering / strict per-source LIMIT enforcement is NOT\n * implemented here; see the `ExpandParams.limitPerSource` JSDoc and\n * `compileExpand` for the cap semantics. Strict per-source caps would\n * require window functions and were judged out of scope for the\n * round-trip-collapse goal.\n */\n async expand(params: ExpandParams): Promise<ExpandResult> {\n if (params.sources.length === 0) {\n return params.hydrate ? { edges: [], targets: [] } : { edges: [] };\n }\n const stmt = compileExpand(this.collectionPath, params);\n const rows = await this.withSchema(() => this.executor.all(stmt.sql, stmt.params));\n const edges = rows.map(rowToRecord);\n if (!params.hydrate) {\n return { edges };\n }\n // Hydration: fetch target nodes for every edge in one IN-clause statement.\n // The \"target\" side depends on direction — forward hops point at `bUid`,\n // reverse hops point at `aUid`.\n const direction = params.direction ?? 'forward';\n const targetUids = edges.map((e) => (direction === 'forward' ? e.bUid : e.aUid));\n const uniqueTargets = [...new Set(targetUids)];\n if (uniqueTargets.length === 0) {\n return { edges, targets: [] };\n }\n const hydrateStmt = compileExpandHydrate(this.collectionPath, uniqueTargets);\n const hydrateRows = await this.executor.all(hydrateStmt.sql, hydrateStmt.params);\n const byUid = new Map<string, StoredGraphRecord>();\n for (const row of hydrateRows) {\n const node = rowToRecord(row);\n // Node UID is `bUid` (== `aUid` for self-loop) by convention. Key the\n // map by `bUid` so the alignment loop below indexes correctly.\n byUid.set(node.bUid, node);\n }\n const targets = targetUids.map((uid) => byUid.get(uid) ?? null);\n return { edges, targets };\n }\n\n /**\n * Server-side projection — `query.select` capability.\n *\n * Issues a single `SELECT json_extract(data, '$.f1'), …` statement that\n * returns only the requested fields. The compiler emits one column per\n * unique field plus a paired `json_type` column for `data.*` projections\n * so the decoder can recover JSON-encoded objects/arrays without a\n * second round trip. Migrations are NOT applied — the caller asked for\n * a partial shape, and rehydrating that into the migration pipeline\n * would require synthesising every absent field.\n *\n * The wire-payload reduction is the entire reason this method exists:\n * a list view that only needs `title` / `date` no longer drags the\n * full `data` JSON across the network. Callers that need the full\n * record should use `findEdges` (with migration support).\n */\n async findEdgesProjected(\n select: ReadonlyArray<string>,\n filters: QueryFilter[],\n options?: QueryOptions,\n ): Promise<Array<Record<string, unknown>>> {\n const { stmt, columns } = compileFindEdgesProjected(\n this.collectionPath,\n select,\n filters,\n options,\n );\n const rows = await this.withSchema(() => this.executor.all(stmt.sql, stmt.params));\n return rows.map((row) => decodeProjectedRow(row, columns));\n }\n\n /**\n * Run a DML statement with `RETURNING \"doc_id\"` so we can count the\n * rows the engine touched, with the same retry/backoff contract as\n * `executeChunkedBatches`. Single statement, single batch.\n */\n private async executeDmlWithReturning(\n stmt: CompiledStatement,\n options?: BulkOptions,\n ): Promise<BulkResult> {\n const sqlWithReturning = `${stmt.sql} RETURNING \"doc_id\"`;\n const maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES;\n let lastError: Error | null = null;\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const rows = await this.executor.all(sqlWithReturning, stmt.params);\n const deleted = rows.length;\n if (options?.onProgress) {\n options.onProgress({\n completedBatches: 1,\n totalBatches: 1,\n deletedSoFar: deleted,\n });\n }\n return { deleted, batches: 1, errors: [] };\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n if (attempt < maxRetries) {\n const delay = Math.min(BASE_RETRY_DELAY_MS * Math.pow(2, attempt), MAX_RETRY_DELAY_MS);\n await sleep(delay);\n }\n }\n }\n // `operationCount` is genuinely unknown for a server-side DML — we\n // don't know how many rows the failed statement would have touched.\n // Report 0 as the lower bound; callers concerned about partial state\n // should re-query and reconcile.\n return {\n deleted: 0,\n batches: 0,\n errors: [\n {\n batchIndex: 0,\n error: lastError ?? new Error('bulk DML failed for unknown reason'),\n operationCount: 0,\n },\n ],\n };\n }\n}\n\n/**\n * Create a SQLite-backed `StorageBackend`.\n *\n * `tableName` is the root graph's table; subgraphs get their own tables\n * derived from it (see `tableForScope`). Schema (tables, indexes, and the\n * graph catalog) is created lazily on first use — no manual DDL step.\n * Pass `options.registry` so per-entry `indexes` declarations land in\n * every lazily created table.\n */\nexport function createSqliteBackend(\n executor: SqliteExecutor,\n tableName: string,\n options: SqliteBackendOptions = {},\n): StorageBackend<SqliteCapability> {\n const storageScope = options.storageScope ?? '';\n const scopePath = options.scopePath ?? '';\n return new SqliteBackendImpl(\n executor,\n tableName,\n storageScope,\n scopePath,\n options.registry,\n options.coreIndexes,\n );\n}\n"],"mappings":"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","names":["quoteIdent","JSON_PATH_KEY_RE","FIRESTORE_TYPE_NAMES","walk","quoteIdent","sql","quoteIdent","quoteIdent"]}
1
+ {"version":3,"sources":["../../src/sqlite/local.ts","../../src/errors.ts","../../src/internal/backend.ts","../../src/default-indexes.ts","../../src/internal/sqlite-index-ddl.ts","../../src/internal/sqlite-schema.ts","../../src/internal/sqlite-data-ops.ts","../../src/timestamp.ts","../../src/internal/constants.ts","../../src/internal/serialization-tag.ts","../../src/internal/write-plan.ts","../../src/internal/sqlite-payload-guard.ts","../../src/internal/sqlite-sql.ts","../../src/internal/sqlite-search.ts","../../src/docid.ts","../../src/sqlite/catalog.ts","../../src/sqlite/backend.ts"],"sourcesContent":["/**\n * Local SQLite backend over `better-sqlite3`.\n *\n * This entry point is published as `firegraph/sqlite-local` and is the only\n * module in the library that references `better-sqlite3` — keep it out of\n * `firegraph/sqlite` so that D1 / workerd bundles never see the native\n * dependency. `better-sqlite3` is loaded via dynamic `import()` at factory\n * call time, so merely importing this module stays side-effect free.\n *\n * The factory accepts either a database file path (`':memory:'` works) or an\n * already-open `better-sqlite3` Database. Path-opened databases get\n * `journal_mode = WAL` and a `busy_timeout` applied; caller-provided\n * databases are used as-is (only `busy_timeout` is set) since the caller\n * owns their pragma configuration.\n *\n * ## Search capabilities\n *\n * On top of the shared SQLite capability set, the local backend declares\n * `search.fullText` and `search.vector` (see `src/internal/sqlite-search.ts`\n * for the mechanics):\n *\n * - **Full-text search** is backed by one FTS5 table per graph table,\n * kept in sync by pure-SQL triggers installed with the table's DDL.\n * Because the triggers live in the database file, writes from ANY\n * process or connection stay indexed. The trade-off is a per-write\n * overhead (text extraction via `json_tree` + an FTS index update) on\n * every insert/update/delete.\n * - **Vector search** is a brute-force scan scored by a deterministic\n * scalar UDF registered on this connection. UDFs are connection-local:\n * `findNearest` only works through a backend created by this factory\n * (other connections to the same file can read/write normally — only\n * vector *search* needs the UDF).\n */\n\nimport type { Database as BetterSqliteDb, default as BetterSqliteDatabase } from 'better-sqlite3';\n\nimport { FiregraphError } from '../errors.js';\nimport type { StorageBackend } from '../internal/backend.js';\nimport { createCapabilities } from '../internal/backend.js';\nimport type { SqliteExecutor, SqliteTxExecutor } from '../internal/sqlite-executor.js';\nimport { quoteIdent, validateTableName } from '../internal/sqlite-schema.js';\nimport {\n buildLocalSearchDDL,\n compileFindNearest,\n compileFullTextSearch,\n computeVectorDistance,\n DISTANCE_ALIAS,\n findOrphanedFtsTables,\n ftsMapTableName,\n ftsTableName,\n isFts5QueryError,\n setDataPath,\n VECTOR_DISTANCE_UDF,\n} from '../internal/sqlite-search.js';\nimport { rowToRecord } from '../internal/sqlite-sql.js';\nimport type { FindNearestParams, FullTextSearchParams, StoredGraphRecord } from '../types.js';\nimport type { SqliteBackendOptions, SqliteCapability, SqliteStorageBackend } from './backend.js';\nimport { createSqliteBackend } from './backend.js';\nimport { catalogTableName } from './catalog.js';\n\n/**\n * Capability union for the local better-sqlite3 backend: everything the\n * shared SQLite edition declares, plus native FTS5 full-text search and\n * brute-force vector search. `search.geo` stays out — there is no geo\n * index in stock SQLite, and a UDF-scored scan without a haversine\n * contract pinned by Firestore parity tests would be guesswork.\n */\nexport type LocalSqliteCapability = SqliteCapability | 'search.fullText' | 'search.vector';\n\nexport interface LocalSqliteBackendOptions extends SqliteBackendOptions {\n /** Root graph table name. Defaults to `'firegraph'`. */\n tableName?: string;\n /**\n * `PRAGMA busy_timeout` in milliseconds — how long a connection waits on a\n * lock held by another process before erroring. Defaults to 5000.\n */\n busyTimeoutMs?: number;\n /**\n * Extra pragmas applied after the defaults, e.g.\n * `{ synchronous: 'NORMAL', cache_size: -64000 }`. Applied in object\n * order via `PRAGMA <key> = <value>`.\n */\n pragmas?: Record<string, string | number>;\n /**\n * When opening by path: throw if the file does not already exist instead\n * of creating it. Defaults to false.\n */\n fileMustExist?: boolean;\n}\n\nexport interface LocalSqliteBackend {\n /** The graph storage backend — pass to `createGraphClient`. */\n backend: StorageBackend<LocalSqliteCapability>;\n /** The underlying better-sqlite3 database, for raw access. */\n db: BetterSqliteDb;\n /**\n * Close the database. No-op when the factory was given an already-open\n * Database (the caller owns its lifecycle).\n */\n close(): void;\n}\n\n/**\n * Build a transaction-capable `SqliteExecutor` over a better-sqlite3\n * Database. Interactive transactions use manual `BEGIN IMMEDIATE` /\n * `COMMIT` / `ROLLBACK` because `db.transaction()` requires a synchronous\n * callback while `SqliteExecutor.transaction` callbacks are async.\n *\n * Exported for callers that want to wire `createSqliteBackend` directly\n * (e.g. to share one executor across several root tables).\n */\nexport function createBetterSqliteExecutor(db: BetterSqliteDb): SqliteExecutor {\n return {\n async all(sql: string, params: unknown[]): Promise<Record<string, unknown>[]> {\n return db.prepare(sql).all(...params) as Record<string, unknown>[];\n },\n async run(sql: string, params: unknown[]): Promise<void> {\n db.prepare(sql).run(...params);\n },\n async batch(statements): Promise<void> {\n const tx = db.transaction((stmts: typeof statements) => {\n for (const s of stmts) {\n db.prepare(s.sql).run(...s.params);\n }\n });\n tx(statements);\n },\n async transaction<T>(fn: (tx: SqliteTxExecutor) => Promise<T>): Promise<T> {\n db.exec('BEGIN IMMEDIATE');\n try {\n const result = await fn({\n async all(sql: string, params: unknown[]) {\n return db.prepare(sql).all(...params) as Record<string, unknown>[];\n },\n async run(sql: string, params: unknown[]) {\n db.prepare(sql).run(...params);\n },\n });\n db.exec('COMMIT');\n return result;\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n },\n };\n}\n\nfunction isDatabase(value: unknown): value is BetterSqliteDb {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { prepare?: unknown }).prepare === 'function' &&\n typeof (value as { exec?: unknown }).exec === 'function'\n );\n}\n\nconst PRAGMA_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;\n// Pragma values are identifiers (WAL, NORMAL) or integers — never compound\n// expressions, so anything else is rejected rather than interpolated.\nconst PRAGMA_VALUE_PATTERN = /^-?[A-Za-z0-9_]+$/;\n\nfunction applyPragmas(db: BetterSqliteDb, pragmas: Record<string, string | number>): void {\n for (const [key, value] of Object.entries(pragmas)) {\n if (!PRAGMA_KEY_PATTERN.test(key)) {\n throw new FiregraphError(`Invalid pragma name: ${JSON.stringify(key)}`, 'INVALID_ARGUMENT');\n }\n if (\n !PRAGMA_VALUE_PATTERN.test(String(value)) ||\n (typeof value === 'number' && !Number.isFinite(value))\n ) {\n throw new FiregraphError(\n `Invalid pragma value for ${key}: ${JSON.stringify(value)}`,\n 'INVALID_ARGUMENT',\n );\n }\n db.pragma(`${key} = ${value}`);\n }\n}\n\n/**\n * Register the vector-distance UDF on a connection. Idempotent across\n * multiple factory calls over the same caller-provided Database —\n * better-sqlite3 raises on duplicate registration, which we swallow since\n * re-registering the identical pure function changes nothing.\n */\nfunction registerVectorUdf(db: BetterSqliteDb): void {\n try {\n db.function(VECTOR_DISTANCE_UDF, { deterministic: true }, (stored, query, measure) =>\n computeVectorDistance(stored, query, measure),\n );\n } catch {\n // Already registered on this connection.\n }\n}\n\n/**\n * After a cascade DROPs descendant graph tables, their FTS artifacts\n * (`<t>_fts`, `<t>_fts_map`) survive — triggers die with the base table\n * but separate tables do not. Sweep and drop any artifact whose base\n * graph table is gone. Stale rows in a *recreated* subgraph are handled\n * independently by the bootstrap reconciliation pass\n * (`buildFtsSyncStatements`); this sweep is what reclaims the space for\n * graphs that never come back.\n */\nasync function sweepOrphanedFtsArtifacts(\n executor: SqliteExecutor,\n rootTable: string,\n): Promise<void> {\n const tableRows = await executor.all(\n `SELECT \"name\" FROM sqlite_master WHERE \"type\" = 'table'`,\n [],\n );\n const allTables = tableRows.map((r) => String(r.name));\n const catalogRows = await executor.all(\n `SELECT \"table_name\" FROM ${quoteIdent(catalogTableName(rootTable))}`,\n [],\n );\n const catalogTables = catalogRows.map((r) => String(r.table_name));\n for (const name of findOrphanedFtsTables(allTables, catalogTables, rootTable)) {\n validateTableName(name);\n await executor.run(`DROP TABLE IF EXISTS ${quoteIdent(name)}`, []);\n }\n}\n\n/**\n * Wrap the shared SQLite backend with the two search capabilities. Every\n * core method delegates to the inner backend unchanged; `subgraph()`\n * re-wraps so children search too, and `removeNodeCascade` follows the\n * inner cascade with the orphaned-FTS sweep.\n */\nfunction wrapLocalSearchBackend(\n inner: SqliteStorageBackend,\n executor: SqliteExecutor,\n rootTable: string,\n): StorageBackend<LocalSqliteCapability> {\n const caps = new Set<LocalSqliteCapability>([\n ...(inner.capabilities.values() as IterableIterator<SqliteCapability>),\n 'search.fullText',\n 'search.vector',\n ]);\n\n // Same self-heal contract as SqliteBackendImpl.withSchema: a stale handle\n // whose table — or whose FTS artifacts, which bootstrap alongside it — was\n // dropped by a parent cascade recreates the empty graph and retries once.\n // The missing table name is matched exactly (not by prefix) so an unrelated\n // table that merely shares the prefix never triggers a re-bootstrap.\n const healableTables = new Set([\n inner.collectionPath,\n ftsTableName(inner.collectionPath),\n ftsMapTableName(inner.collectionPath),\n ]);\n const runWithSchema = async <T>(op: () => Promise<T>): Promise<T> => {\n await inner.ensureReady();\n try {\n return await op();\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const missing = /no such table: (\\S+)/.exec(message)?.[1];\n if (missing === undefined || !healableTables.has(missing)) throw err;\n await inner.ensureReady(true);\n return op();\n }\n };\n\n const wrapper: StorageBackend<LocalSqliteCapability> = {\n capabilities: createCapabilities(caps),\n collectionPath: inner.collectionPath,\n scopePath: inner.scopePath,\n\n getDoc: (docId) => inner.getDoc(docId),\n query: (filters, options) => inner.query(filters, options),\n setDoc: (docId, record, mode) => inner.setDoc(docId, record, mode),\n updateDoc: (docId, update) => inner.updateDoc(docId, update),\n deleteDoc: (docId) => inner.deleteDoc(docId),\n runTransaction: (fn) => inner.runTransaction(fn),\n createBatch: () => inner.createBatch(),\n\n subgraph: (parentNodeUid, name) =>\n wrapLocalSearchBackend(inner.subgraph(parentNodeUid, name), executor, rootTable),\n\n removeNodeCascade: async (uid, reader, options) => {\n const result = await inner.removeNodeCascade(uid, reader, options);\n if (result.errors.length === 0) {\n await sweepOrphanedFtsArtifacts(executor, rootTable);\n }\n return result;\n },\n bulkRemoveEdges: (params, reader, options) => inner.bulkRemoveEdges(params, reader, options),\n\n aggregate: (spec, filters) => inner.aggregate!(spec, filters),\n bulkDelete: (filters, options) => inner.bulkDelete!(filters, options),\n bulkUpdate: (filters, patch, options) => inner.bulkUpdate!(filters, patch, options),\n expand: (params) => inner.expand!(params),\n findEdgesProjected: (select, filters, options) =>\n inner.findEdgesProjected!(select, filters, options),\n\n // `findEdgesGlobal` stays absent, same as the inner backend — each graph\n // is its own table; there is no cross-table index.\n\n async findNearest(params: FindNearestParams): Promise<StoredGraphRecord[]> {\n const { stmt, distancePath } = compileFindNearest(inner.collectionPath, params);\n const rows = await runWithSchema(() => executor.all(stmt.sql, stmt.params));\n return rows.map((row) => {\n const record = rowToRecord(row);\n if (distancePath) {\n const distance = row[DISTANCE_ALIAS];\n setDataPath(\n record.data as Record<string, unknown>,\n distancePath,\n typeof distance === 'number' ? distance : Number(distance),\n );\n }\n return record;\n });\n },\n\n async fullTextSearch(params: FullTextSearchParams): Promise<StoredGraphRecord[]> {\n const stmt = compileFullTextSearch(inner.collectionPath, params);\n let rows: Record<string, unknown>[];\n try {\n rows = await runWithSchema(() => executor.all(stmt.sql, stmt.params));\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n // FTS5 reports a malformed MATCH expression at query time as a generic\n // SQLITE_ERROR (e.g. \"unterminated string\", \"fts5: syntax error\",\n // \"unknown special query\"); surface those as INVALID_QUERY to match the\n // documented Firestore-parity contract. Genuine storage errors (disk\n // I/O, corruption, a non-healable missing table) carry different\n // messages and propagate unchanged.\n if (isFts5QueryError(message)) {\n throw new FiregraphError(\n `fullTextSearch(): invalid FTS5 query syntax — ${message}`,\n 'INVALID_QUERY',\n );\n }\n throw err;\n }\n return rows.map(rowToRecord);\n },\n };\n return wrapper;\n}\n\n/**\n * Open (or wrap) a local SQLite database and return a graph storage backend\n * over it.\n *\n * ```typescript\n * import { createLocalSqliteBackend } from 'firegraph/sqlite-local';\n * import { createGraphClient } from 'firegraph/sqlite';\n *\n * const { backend, close } = await createLocalSqliteBackend('./graph.db');\n * const client = createGraphClient(backend);\n * // ... use the client — including fullTextSearch() and findNearest() ...\n * close();\n * ```\n *\n * Requires `better-sqlite3` to be installed (declared as an optional peer\n * dependency). The factory is async because the driver is loaded via\n * dynamic `import()`.\n */\nexport async function createLocalSqliteBackend(\n pathOrDb: string | BetterSqliteDb,\n options: LocalSqliteBackendOptions = {},\n): Promise<LocalSqliteBackend> {\n const {\n tableName = 'firegraph',\n busyTimeoutMs = 5000,\n pragmas,\n fileMustExist,\n ...backendOptions\n } = options;\n\n let db: BetterSqliteDb;\n let ownsDb: boolean;\n if (typeof pathOrDb === 'string') {\n let Database: typeof BetterSqliteDatabase;\n try {\n Database = (await import('better-sqlite3')).default;\n } catch (err) {\n throw new FiregraphError(\n `createLocalSqliteBackend requires the optional peer dependency 'better-sqlite3' — install it to use the local SQLite backend (${\n err instanceof Error ? err.message : String(err)\n })`,\n 'MISSING_DEPENDENCY',\n );\n }\n db = new Database(pathOrDb, fileMustExist ? { fileMustExist: true } : {});\n ownsDb = true;\n // WAL lets concurrent readers coexist with a writer — the right default\n // for a long-lived local graph file. On ':memory:' databases SQLite\n // reports 'memory' and ignores the request, which is fine.\n db.pragma('journal_mode = WAL');\n } else if (isDatabase(pathOrDb)) {\n db = pathOrDb;\n ownsDb = false;\n } else {\n throw new FiregraphError(\n 'createLocalSqliteBackend expects a file path or an open better-sqlite3 Database',\n 'INVALID_ARGUMENT',\n );\n }\n\n db.pragma(`busy_timeout = ${Math.max(0, Math.floor(busyTimeoutMs))}`);\n if (pragmas) {\n applyPragmas(db, pragmas);\n }\n registerVectorUdf(db);\n\n // Compose the FTS DDL into the lazy bootstrap so every graph table —\n // root, lazily created subgraphs, and self-heal recreations — gets its\n // FTS infrastructure the moment the table exists.\n const userExtraDDL = backendOptions.extraTableDDL;\n const optionsWithSearch: SqliteBackendOptions = {\n ...backendOptions,\n extraTableDDL: (table) => [\n ...(userExtraDDL ? userExtraDDL(table) : []),\n ...buildLocalSearchDDL(table),\n ],\n };\n\n const executor = createBetterSqliteExecutor(db);\n const inner = createSqliteBackend(executor, tableName, optionsWithSearch);\n const backend = wrapLocalSearchBackend(inner, executor, tableName);\n let closed = false;\n return {\n backend,\n db,\n close(): void {\n if (closed || !ownsDb) return;\n closed = true;\n db.close();\n },\n };\n}\n","export class FiregraphError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n ) {\n super(message);\n this.name = 'FiregraphError';\n }\n}\n\nexport class NodeNotFoundError extends FiregraphError {\n constructor(uid: string) {\n super(`Node not found: ${uid}`, 'NODE_NOT_FOUND');\n this.name = 'NodeNotFoundError';\n }\n}\n\nexport class EdgeNotFoundError extends FiregraphError {\n constructor(aUid: string, axbType: string, bUid: string) {\n super(`Edge not found: ${aUid} -[${axbType}]-> ${bUid}`, 'EDGE_NOT_FOUND');\n this.name = 'EdgeNotFoundError';\n }\n}\n\nexport class ValidationError extends FiregraphError {\n constructor(\n message: string,\n public readonly details?: unknown,\n ) {\n super(message, 'VALIDATION_ERROR');\n this.name = 'ValidationError';\n }\n}\n\nexport class RegistryViolationError extends FiregraphError {\n constructor(aType: string, axbType: string, bType: string) {\n super(`Unregistered triple: (${aType}) -[${axbType}]-> (${bType})`, 'REGISTRY_VIOLATION');\n this.name = 'RegistryViolationError';\n }\n}\n\nexport class InvalidQueryError extends FiregraphError {\n constructor(message: string) {\n super(message, 'INVALID_QUERY');\n this.name = 'InvalidQueryError';\n }\n}\n\nexport class TraversalError extends FiregraphError {\n constructor(message: string) {\n super(message, 'TRAVERSAL_ERROR');\n this.name = 'TraversalError';\n }\n}\n\nexport class DynamicRegistryError extends FiregraphError {\n constructor(message: string) {\n super(message, 'DYNAMIC_REGISTRY_ERROR');\n this.name = 'DynamicRegistryError';\n }\n}\n\nexport class QuerySafetyError extends FiregraphError {\n constructor(message: string) {\n super(message, 'QUERY_SAFETY');\n this.name = 'QuerySafetyError';\n }\n}\n\nexport class RegistryScopeError extends FiregraphError {\n constructor(\n aType: string,\n axbType: string,\n bType: string,\n scopePath: string,\n allowedIn: string[],\n ) {\n super(\n `Type (${aType}) -[${axbType}]-> (${bType}) is not allowed at scope \"${scopePath || 'root'}\". ` +\n `Allowed in: [${allowedIn.join(', ')}]`,\n 'REGISTRY_SCOPE',\n );\n this.name = 'RegistryScopeError';\n }\n}\n\nexport class MigrationError extends FiregraphError {\n constructor(message: string) {\n super(message, 'MIGRATION_ERROR');\n this.name = 'MigrationError';\n }\n}\n\n/**\n * Thrown when a caller tries to perform an operation that would require\n * atomicity across two physical storage backends — e.g. opening a routed\n * subgraph client from inside a transaction callback. Cross-backend\n * atomicity cannot be honoured by real-world storage engines (Firestore,\n * SQLite drivers over D1/DO/better-sqlite3, etc.), so firegraph surfaces\n * this as a typed error instead of silently confining the write to the\n * base backend.\n *\n * Normally `TransactionBackend` and `BatchBackend` don't expose `subgraph()`\n * at the type level, so this error is unreachable through well-typed code.\n * It exists as a public catchable type for app code that needs to tolerate\n * this case deliberately (e.g. dynamic code paths that bypass the type\n * system) and as future-proofing if the interface ever grows a way to\n * request a sub-scope inside a transaction.\n */\nexport class CrossBackendTransactionError extends FiregraphError {\n constructor(message: string) {\n super(message, 'CROSS_BACKEND_TRANSACTION');\n this.name = 'CrossBackendTransactionError';\n }\n}\n\n/**\n * Thrown when a caller invokes a capability-gated operation on a backend\n * that does not declare the required capability. Capability gating is\n * primarily a compile-time concern (see `BackendCapabilities` and the\n * type-level extension surfaces in `GraphClient<C>`), but this runtime\n * error covers the cases where the type system is bypassed — dynamic\n * registries, `as any` casts, or callers explicitly downcasting through\n * the generic-erased `StorageBackend` shape.\n *\n * The error code is `CAPABILITY_NOT_SUPPORTED`. The message names the\n * missing capability and the backend that was asked, so app code can\n * diagnose without inspecting the cap set itself.\n */\nexport class CapabilityNotSupportedError extends FiregraphError {\n constructor(\n public readonly capability: string,\n backendDescription: string,\n ) {\n super(\n `Capability \"${capability}\" is not supported by ${backendDescription}.`,\n 'CAPABILITY_NOT_SUPPORTED',\n );\n this.name = 'CapabilityNotSupportedError';\n }\n}\n","/**\n * Backend abstraction for firegraph.\n *\n * `StorageBackend` is the single interface every storage driver implements.\n * The Firestore backend wraps `@google-cloud/firestore`; the SQLite backend\n * (shared by D1 and Durable Object SQLite) uses a parameterized SQL executor.\n *\n * `GraphClientImpl` and friends depend only on this interface — they have\n * no direct knowledge of Firestore or SQLite.\n */\n\nimport type {\n AggregateSpec,\n BulkOptions,\n BulkResult,\n BulkUpdatePatch,\n Capability,\n CascadeResult,\n EngineTraversalParams,\n EngineTraversalResult,\n ExpandParams,\n ExpandResult,\n FindEdgesParams,\n FindNearestParams,\n FullTextSearchParams,\n GeoSearchParams,\n GraphReader,\n QueryFilter,\n QueryOptions,\n StoredGraphRecord,\n} from '../types.js';\nimport type { DataPathOp } from './write-plan.js';\n\n/**\n * Runtime descriptor of which `Capability`s a `StorageBackend` actually\n * implements. Static for the lifetime of a backend instance; declared at\n * construction. The phantom `_phantom` field is a type-level marker\n * (never read at runtime) that lets the type parameter `C` flow through\n * the descriptor for use by `GraphClient<C>` conditional gating.\n *\n * Use `createCapabilities` to construct one. Use `.has(c)` to check\n * membership at runtime; the type system gates extension methods on the\n * client level (see `.claude/backend-capabilities.md`).\n */\nexport interface BackendCapabilities<C extends Capability = Capability> {\n /** Runtime membership check. */\n has(capability: Capability): boolean;\n /** Iterate declared capabilities (diagnostics, error messages). */\n values(): IterableIterator<Capability>;\n /** Type-level marker. Never read at runtime. */\n readonly _phantom?: C;\n}\n\n/**\n * Construct a `BackendCapabilities<C>` from an explicit set. The set is\n * captured by reference; callers should treat it as readonly after passing\n * it in. The runtime cost of `has()` is one Set lookup.\n */\nexport function createCapabilities<C extends Capability>(\n caps: ReadonlySet<C>,\n): BackendCapabilities<C> {\n return {\n has: (capability: Capability): boolean => caps.has(capability as C),\n values: () => caps.values() as IterableIterator<Capability>,\n };\n}\n\n/**\n * Intersect multiple capability sets. Used by `RoutingStorageBackend` to\n * derive the capability set of a composite backend: a routed graph can\n * only honour a capability if every wrapped backend honours it.\n */\nexport function intersectCapabilities(\n parts: ReadonlyArray<BackendCapabilities>,\n): BackendCapabilities {\n if (parts.length === 0) return createCapabilities(new Set<Capability>());\n const sets = parts.map((p) => new Set<Capability>(p.values()));\n const [first, ...rest] = sets;\n const intersection = new Set<Capability>();\n for (const c of first) {\n if (rest.every((s) => s.has(c))) intersection.add(c);\n }\n return createCapabilities(intersection);\n}\n\n/**\n * Per-record write payload — backend-agnostic. Timestamps are not present;\n * the backend supplies them via `serverTimestamp()` placeholders that it\n * itself resolves at commit time.\n */\nexport interface WritableRecord {\n aType: string;\n aUid: string;\n axbType: string;\n bType: string;\n bUid: string;\n data: Record<string, unknown>;\n /** Schema version (set by the writer when registry has migrations). */\n v?: number;\n}\n\n/**\n * Write semantics for `setDoc`.\n *\n * - `'merge'` — the new contract (0.12+). Existing fields not mentioned\n * in the new data survive; nested objects are recursively merged;\n * arrays are replaced as a unit. This is the default for\n * `putNode` / `putEdge`.\n * - `'replace'` — the document is replaced wholesale, dropping any\n * fields not present in the payload. This is the explicit escape\n * hatch surfaced as `replaceNode` / `replaceEdge` and used by\n * migration write-back.\n */\nexport type WriteMode = 'merge' | 'replace';\n\n/**\n * Patch shape for `updateDoc`.\n *\n * - `dataOps`: list of deep-path terminal ops produced by\n * `flattenPatch()` (one op per leaf — arrays / primitives / Firestore\n * special types are terminal). Used by `updateNode` / `updateEdge`.\n * Sibling keys at every depth are preserved.\n * - `replaceData`: full `data` replacement. Used only by the migration\n * write-back path, which has already produced a complete migrated\n * document.\n * - `v`: optional schema-version stamp.\n *\n * `updatedAt` is always set by the backend.\n */\nexport interface UpdatePayload {\n dataOps?: DataPathOp[];\n replaceData?: Record<string, unknown>;\n v?: number;\n}\n\n/**\n * Read/write transaction adapter. Mirrors Firestore's transaction semantics:\n * reads are snapshot-consistent; writes are issued inside the transaction\n * and a rejection from any write aborts the surrounding `runTransaction`.\n *\n * Writes return `Promise<void>` so SQL drivers can surface row-level errors\n * (constraint violations, malformed JSON paths) rather than swallowing them.\n * Firestore implementations can resolve synchronously since the underlying\n * `Transaction.set/update/delete` calls are themselves synchronous buffers.\n */\nexport interface TransactionBackend {\n getDoc(docId: string): Promise<StoredGraphRecord | null>;\n query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]>;\n setDoc(docId: string, record: WritableRecord, mode: WriteMode): Promise<void>;\n updateDoc(docId: string, update: UpdatePayload): Promise<void>;\n deleteDoc(docId: string): Promise<void>;\n}\n\n/**\n * Atomic multi-write batch.\n */\nexport interface BatchBackend {\n setDoc(docId: string, record: WritableRecord, mode: WriteMode): void;\n updateDoc(docId: string, update: UpdatePayload): void;\n deleteDoc(docId: string): void;\n commit(): Promise<void>;\n}\n\n/**\n * The single storage abstraction.\n *\n * Each backend instance is scoped to a \"graph location\" — for Firestore\n * that's a collection path; for SQLite it's a (table, scopePath) pair.\n * `subgraph()` returns a child backend bound to a nested location.\n */\nexport interface StorageBackend<C extends Capability = Capability> {\n /** Capabilities this backend instance declares. Static for the lifetime of the backend. */\n readonly capabilities: BackendCapabilities<C>;\n /** Backend-internal location identifier (collection path or table name). */\n readonly collectionPath: string;\n /** Subgraph scope (empty string for root). */\n readonly scopePath: string;\n\n // --- Reads ---\n getDoc(docId: string): Promise<StoredGraphRecord | null>;\n query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]>;\n\n // --- Writes ---\n setDoc(docId: string, record: WritableRecord, mode: WriteMode): Promise<void>;\n updateDoc(docId: string, update: UpdatePayload): Promise<void>;\n deleteDoc(docId: string): Promise<void>;\n\n // --- Transactions & batches ---\n runTransaction<T>(fn: (tx: TransactionBackend) => Promise<T>): Promise<T>;\n createBatch(): BatchBackend;\n\n // --- Subgraphs ---\n subgraph(parentNodeUid: string, name: string): StorageBackend;\n\n // --- Cascade & bulk ---\n removeNodeCascade(\n uid: string,\n reader: GraphReader,\n options?: BulkOptions,\n ): Promise<CascadeResult>;\n bulkRemoveEdges(\n params: FindEdgesParams,\n reader: GraphReader,\n options?: BulkOptions,\n ): Promise<BulkResult>;\n\n // --- Cross-collection queries ---\n /**\n * Find edges across all subgraphs sharing a given collection name.\n * Optional — backends that can't support this should throw a clear error.\n */\n findEdgesGlobal?(params: FindEdgesParams, collectionName?: string): Promise<StoredGraphRecord[]>;\n\n // --- Aggregations ---\n /**\n * Run an aggregate query (count/sum/avg/min/max). Present only on backends\n * that declare `query.aggregate`. The map's keys are caller-defined aliases\n * matching `AggregateSpec`; values are the resolved numeric results.\n *\n * Backends that can't satisfy a particular op throw `FiregraphError` with\n * code `UNSUPPORTED_AGGREGATE` (e.g. Firestore Standard rejects min/max).\n */\n aggregate?(spec: AggregateSpec, filters: QueryFilter[]): Promise<Record<string, number>>;\n\n // --- Server-side DML ---\n /**\n * Delete every row matching `filters` in one server-side statement.\n * Present only on backends that declare `query.dml`. The default cascade\n * implementation in `bulk.ts` uses this when available; backends without\n * the cap (e.g. Firestore Standard) fall back to a fetch-then-delete\n * loop driven by `findEdges` + per-row `deleteDoc`.\n *\n * The contract matches `findEdges`: scope predicates are honoured\n * automatically by the backend's own internal scope tracking. Callers\n * supply only the filter list — the same shape produced by\n * `buildEdgeQueryPlan`.\n */\n bulkDelete?(filters: QueryFilter[], options?: BulkOptions): Promise<BulkResult>;\n /**\n * Update every row matching `filters` with `patch` in one server-side\n * statement. The patch is deep-merged into each row's `data` field, the\n * same flatten-then-merge pipeline `updateDoc` uses. Identifying columns\n * (`aType`, `axbType`, `aUid`, `bType`, `bUid`, `v`) are not writable\n * through this path.\n */\n bulkUpdate?(\n filters: QueryFilter[],\n patch: BulkUpdatePatch,\n options?: BulkOptions,\n ): Promise<BulkResult>;\n\n // --- Server-side multi-source fan-out ---\n /**\n * Fan out from `params.sources` over a single edge type in one server-side\n * round trip. Present only on backends that declare `query.join`. The\n * traversal layer (`traverse.ts`) calls `expand` once per hop when the\n * backend declares the cap; otherwise it falls back to the per-source\n * `findEdges` loop.\n *\n * Cross-graph hops are never dispatched through `expand` — each source\n * UID resolves to a distinct subgraph location, which can't be fanned\n * out as a single statement. The traversal layer enforces that\n * boundary; `expand` itself does not need to inspect `targetGraph`.\n */\n expand?(params: ExpandParams): Promise<ExpandResult>;\n\n // --- Engine-level multi-hop traversal ---\n /**\n * Compile a multi-hop traversal spec into one server-side query and\n * dispatch a single round trip. Present only on backends that declare\n * `traversal.serverSide` (Firestore Enterprise today, via nested\n * Pipelines that combine `define`, `addFields`, and\n * `toArrayExpression`).\n *\n * The traversal layer (`traverse.ts`) compiles a `TraversalBuilder`\n * spec into `EngineTraversalParams` only when the spec is eligible\n * (no cross-graph hops, no JS filters, depth ≤ `MAX_PIPELINE_DEPTH`,\n * `Π(limitPerSource_i × N_i) ≤ maxReads`, `limitPerSource` set on\n * every hop). Ineligible specs fall back to the per-hop `expand()`\n * loop without invoking this method.\n *\n * The result collapses the nested-pipeline tree into per-hop edge\n * arrays so the traversal layer can fold the result into the same\n * `HopResult[]` shape it produces from the per-hop loop.\n */\n runEngineTraversal?(params: EngineTraversalParams): Promise<EngineTraversalResult>;\n\n // --- Server-side projection ---\n /**\n * Run a projecting query — return only the listed fields per row. Present\n * only on backends that declare `query.select`. The cap-less fallback is\n * `findEdges` followed by a JS-side projection in user code; firegraph\n * does not auto-fall-back because the wire-payload reduction is the only\n * reason to call this method.\n *\n * `select` is the explicit field list; `filters` and `options` mirror the\n * `query()` shape. The returned rows have one slot per unique entry in\n * `select`. Field-name interpretation is the backend's responsibility:\n * built-in fields resolve to columns / Firestore field names, bare names\n * resolve to `data.<name>`, and dotted paths resolve verbatim. See\n * `FindEdgesProjectedParams` for the user-facing contract.\n *\n * Migrations are not applied to the result — the caller asked for a\n * specific projection shape, and rehydrating a partial record into the\n * migration pipeline would require synthesising every absent field.\n */\n findEdgesProjected?(\n select: ReadonlyArray<string>,\n filters: QueryFilter[],\n options?: QueryOptions,\n ): Promise<Array<Record<string, unknown>>>;\n\n // --- Native vector / nearest-neighbour search ---\n /**\n * Run a vector / nearest-neighbour query. Present only on backends that\n * declare `search.vector`. There is no client-side fallback — backends\n * without the cap throw `UNSUPPORTED_OPERATION` from the client wrapper.\n * In-tree: both Firestore editions (native ANN via `Query.findNearest`)\n * and the local better-sqlite3 backend (`firegraph/sqlite-local`, a\n * brute-force UDF-scored scan — exact, not approximate). The D1 and\n * Cloudflare DO editions stay without the cap: no UDF registration\n * surface, and a JS-side k-NN sweep over `findEdges()` would scale\n * catastrophically.\n *\n * `params` carries the user-facing shape (vector field path, query\n * vector, distance metric, optional threshold and result-field). The\n * client wrapper has already run scan-protection on the identifying\n * / `where` filter list before dispatching.\n *\n * Path normalisation is the backend's responsibility: rewriting bare\n * `vectorField` / `distanceResultField` names to `data.<name>` and\n * rejecting envelope fields (`aType`, `axbType`, `bType`, `aUid`,\n * `bUid`, `v`, etc.) with `INVALID_QUERY` happens inside the\n * backend, not the client wrapper. The two in-tree Firestore-edition\n * backends share `runFirestoreFindNearest` (see\n * `src/internal/firestore-vector.ts`) for this; third-party backends\n * declaring `search.vector` must apply equivalent normalisation\n * before calling their underlying SDK.\n *\n * The backend is also responsible for translating to the underlying\n * SDK call (`Query.findNearest` on Firestore today) and decoding the\n * result snapshot into `StoredGraphRecord[]`.\n *\n * Migrations are not applied to the result. The vector index walks the\n * raw stored shape; rehydrating into the migration pipeline before\n * returning would change the candidate set the index already chose.\n */\n findNearest?(params: FindNearestParams): Promise<StoredGraphRecord[]>;\n\n // --- Native full-text search ---\n /**\n * Run a full-text search query. Present only on backends that declare\n * `search.fullText`. There is no client-side fallback — backends\n * without the cap throw `UNSUPPORTED_OPERATION` from the client\n * wrapper. In-tree: Firestore Enterprise (via Pipeline\n * `search({ query: documentMatches(...) })`) and the local\n * better-sqlite3 backend (`firegraph/sqlite-local`, via a\n * trigger-synced FTS5 index ranked by `bm25()`). Firestore Standard\n * never gets it (Enterprise-only product feature); D1 and the\n * Cloudflare DO edition don't ship FTS5 trigger infrastructure.\n *\n * The backend is responsible for path normalisation (rewriting\n * bare `fields` entries to `data.<name>`, rejecting envelope fields\n * with `INVALID_QUERY`), translating to the underlying SDK call,\n * and decoding the result into `StoredGraphRecord[]`.\n *\n * Migrations are not applied to the result. The search index walked\n * the raw stored shape; rehydrating into the migration pipeline\n * would change the candidate set the index already scored.\n */\n fullTextSearch?(params: FullTextSearchParams): Promise<StoredGraphRecord[]>;\n\n // --- Native geospatial distance search ---\n /**\n * Run a geospatial distance search. Present only on backends that\n * declare `search.geo`. There is no client-side fallback — only\n * Firestore Enterprise has a native geo index (translated via\n * Pipeline `search({ query: geoDistance(...).lessThanOrEqual(...) })`).\n * Backends without the cap throw `UNSUPPORTED_OPERATION` from the\n * client wrapper.\n *\n * The backend is responsible for `geoField` path normalisation,\n * translating `point` to a Firestore `GeoPoint`, applying the\n * radius cap inside the search query, and (when\n * `orderByDistance` is true / unset) emitting the\n * `geoDistance(...).ascending()` ordering inside the search stage.\n *\n * Migrations are not applied to the result.\n */\n geoSearch?(params: GeoSearchParams): Promise<StoredGraphRecord[]>;\n}\n","/**\n * Default core index preset.\n *\n * This set covers the query patterns firegraph's query planner emits for\n * built-in operations — `findNodes`, `findEdges`, cascade delete, traversal,\n * and the DO/SQLite path compilers. Apps that need additional indexes\n * (descending timestamps, `data.*` filters, composite fields unique to\n * their query shapes) declare them on `RegistryEntry.indexes` or override\n * this preset wholesale via the backend-specific `coreIndexes` option —\n * `FiregraphDOOptions.coreIndexes` for the DO backend,\n * `BuildSchemaOptions.coreIndexes` for the legacy SQLite backend, and\n * `GenerateIndexOptions.coreIndexes` for the Firestore CLI generator.\n *\n * ## Ownership model\n *\n * This list is firegraph's *recommendation* — not non-negotiable policy.\n * Consumers can:\n *\n * 1. Accept the preset as-is (default).\n * 2. Extend it: `coreIndexes: [...DEFAULT_CORE_INDEXES, ...more]`.\n * 3. Replace it entirely with a tailored set.\n * 4. Disable it (`coreIndexes: []`) and take full responsibility for\n * index coverage — only do this if you're provisioning a complete\n * custom set.\n *\n * ## Per-backend emission\n *\n * The Firestore generator skips single-field entries (Firestore implicitly\n * indexes every field) and emits one composite index per multi-field spec.\n * The SQLite-flavored generators (DO, legacy) emit every spec as-is.\n *\n * ## Why these specific indexes\n *\n * - `aUid` / `bUid` — required for `_fgRemoveNodeCascade`, which scans by\n * each UID side independently. A composite `(aUid, axbType)` also\n * satisfies `aUid`-alone via leading-column prefix, but the single-field\n * form is cheaper for the common case.\n * - `aType` / `bType` — `findNodes({ aType })` and cross-type enumeration.\n * - `(aUid, axbType)` — forward edge lookup (`findEdges({ aUid, axbType })`)\n * and the `get` strategy fallback when only two of three triple fields\n * are present.\n * - `(axbType, bUid)` — reverse edge traversal.\n * - `(aType, axbType)` — type-scoped edge scans (e.g., `findEdges({ aType, axbType })`).\n * - `(axbType, bType)` — scope edges of one relation to a target type.\n */\n\nimport type { IndexSpec } from './types.js';\n\nexport const DEFAULT_CORE_INDEXES: ReadonlyArray<IndexSpec> = Object.freeze([\n { fields: ['aUid'] },\n { fields: ['bUid'] },\n { fields: ['aType'] },\n { fields: ['bType'] },\n { fields: ['aUid', 'axbType'] },\n { fields: ['axbType', 'bUid'] },\n { fields: ['aType', 'axbType'] },\n { fields: ['axbType', 'bType'] },\n]);\n","/**\n * Translator from `IndexSpec` to SQLite `CREATE INDEX` DDL.\n *\n * Shared by every SQLite-shaped backend (the table-per-graph edition in\n * `src/sqlite/` and the Cloudflare DO edition) via the common schema module\n * (`src/internal/sqlite-schema.ts`). Both use the same scope-free row shape,\n * so the only knob is the `fieldToColumn` mapping.\n *\n * ## JSON path expression indexes\n *\n * Data-field specs (`data.foo`, `data.nested.bar`) compile to\n * `json_extract(\"data\", '$.foo')` expression indexes. The JSON path\n * literal is inlined — not parametrized — so the SQLite query planner can\n * match the index against the expression emitted by the query compiler\n * (which also inlines the literal after this PR). Path components are\n * validated against a safe identifier pattern so inlining is not an\n * injection risk.\n *\n * ## Index naming\n *\n * Names are `{table}_idx_{hash}` where `hash` is a short FNV-1a of a\n * canonicalized spec. This keeps names stable across runs (so\n * `CREATE INDEX IF NOT EXISTS` is idempotent) and prevents collisions\n * between similar specs. The hash includes the field list, per-field\n * direction, and the `where` predicate.\n */\n\nimport { FiregraphError } from '../errors.js';\nimport type { IndexFieldSpec, IndexSpec } from '../types.js';\n\n/**\n * Valid SQLite identifier pattern — used for table and column names.\n * Mirrors the validation in `sqlite-schema.ts` / `cloudflare/schema.ts` so\n * this module doesn't need to import one over the other.\n */\nconst IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * Safe JSON path component. Must match `JSON_PATH_KEY_RE` in the SQLite\n * query compilers — an index is only useful if the query emits an\n * identical `json_extract` expression.\n */\nconst JSON_PATH_KEY_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;\n\nfunction quoteIdent(name: string): string {\n if (!IDENT_RE.test(name)) {\n throw new FiregraphError(\n `Invalid SQL identifier in index DDL: ${name}. Must match /^[A-Za-z_][A-Za-z0-9_]*$/.`,\n 'INVALID_INDEX',\n );\n }\n return `\"${name}\"`;\n}\n\n/**\n * FNV-1a 32-bit hash, returned as 8-char hex. Non-cryptographic;\n * used only to produce short, stable index names.\n */\nfunction fnv1a32(str: string): string {\n let h = 0x811c9dc5;\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return (h >>> 0).toString(16).padStart(8, '0');\n}\n\nfunction normalizeFields(\n fields: Array<string | IndexFieldSpec>,\n): Array<{ path: string; desc: boolean }> {\n return fields.map((f) => {\n if (typeof f === 'string') return { path: f, desc: false };\n if (!f.path || typeof f.path !== 'string') {\n throw new FiregraphError(\n `IndexSpec field must be a string or { path: string, desc?: boolean }; got ${JSON.stringify(f)}`,\n 'INVALID_INDEX',\n );\n }\n return { path: f.path, desc: !!f.desc };\n });\n}\n\nfunction specFingerprint(spec: IndexSpec): string {\n // Canonical form: JSON of normalized fields + where. The `lead` key is\n // kept (always empty now) so fingerprints — and therefore index names —\n // stay stable across the removal of the legacy scope leading column.\n const normalized = {\n lead: [] as string[],\n fields: normalizeFields(spec.fields),\n where: spec.where ?? '',\n };\n return fnv1a32(JSON.stringify(normalized));\n}\n\n/**\n * Compile one field path to its SQLite column expression.\n *\n * - Firegraph top-level fields (`aType`, `createdAt`, …) → mapped column.\n * - `data.foo` / `data.foo.bar` → `json_extract(\"data\", '$.foo.bar')`.\n * - `data` alone → `json_extract(\"data\", '$')`.\n */\nfunction compileFieldExpr(path: string, fieldToColumn: Record<string, string>): string {\n const col = fieldToColumn[path];\n if (col) return quoteIdent(col);\n\n if (path === 'data') {\n return `json_extract(\"data\", '$')`;\n }\n if (path.startsWith('data.')) {\n const suffix = path.slice(5);\n const parts = suffix.split('.');\n for (const part of parts) {\n if (!JSON_PATH_KEY_RE.test(part)) {\n throw new FiregraphError(\n `IndexSpec data path \"${path}\" has invalid component \"${part}\". ` +\n `Each component must match /^[A-Za-z_][A-Za-z0-9_-]*$/.`,\n 'INVALID_INDEX',\n );\n }\n }\n // Inline the path literal (no parameter). Validated components above\n // are safe to embed — no quote or escape characters.\n return `json_extract(\"data\", '$.${suffix}')`;\n }\n\n throw new FiregraphError(\n `IndexSpec field \"${path}\" is not a known firegraph field. ` +\n `Use a top-level field (aType, aUid, axbType, bType, bUid, createdAt, updatedAt, v) ` +\n `or a dotted data path like 'data.status'.`,\n 'INVALID_INDEX',\n );\n}\n\nexport interface SqliteIndexDDLOptions {\n /** Target table. */\n table: string;\n /** Map from firegraph field name to SQLite column name. */\n fieldToColumn: Record<string, string>;\n}\n\n/**\n * Emit the `CREATE INDEX IF NOT EXISTS` DDL for one `IndexSpec`.\n *\n * Returns a single SQL string. Name is deterministic (same spec → same\n * name across runs), so re-running the bootstrap is idempotent.\n */\nexport function buildIndexDDL(spec: IndexSpec, options: SqliteIndexDDLOptions): string {\n const { table, fieldToColumn } = options;\n\n if (!spec.fields || spec.fields.length === 0) {\n throw new FiregraphError('IndexSpec.fields must be a non-empty array', 'INVALID_INDEX');\n }\n\n const normalized = normalizeFields(spec.fields);\n const hash = specFingerprint(spec);\n const indexName = `${table}_idx_${hash}`;\n\n const cols: string[] = [];\n for (const f of normalized) {\n const expr = compileFieldExpr(f.path, fieldToColumn);\n cols.push(f.desc ? `${expr} DESC` : expr);\n }\n\n let ddl = `CREATE INDEX IF NOT EXISTS ${quoteIdent(indexName)} ON ${quoteIdent(table)}(${cols.join(', ')})`;\n\n if (spec.where) {\n // The predicate is inlined verbatim. It comes from library/app\n // configuration — never from user data — so we don't attempt to\n // parse, rewrite, or validate it. Callers authoring partial indexes\n // are responsible for writing a valid SQLite WHERE clause.\n ddl += ` WHERE ${spec.where}`;\n }\n\n return ddl;\n}\n\n/**\n * Deduplicate index specs by their deterministic fingerprint. Same spec\n * declared twice (e.g., by core preset + registry entry) collapses to a\n * single DDL statement.\n */\nexport function dedupeIndexSpecs(specs: ReadonlyArray<IndexSpec>): IndexSpec[] {\n const seen = new Set<string>();\n const out: IndexSpec[] = [];\n for (const spec of specs) {\n const fp = specFingerprint(spec);\n if (seen.has(fp)) continue;\n seen.add(fp);\n out.push(spec);\n }\n return out;\n}\n","/**\n * SQLite schema for firegraph triples.\n *\n * Single-table design — both nodes (self-loops with `axbType = 'is'`) and\n * edges share one row shape. Each table holds exactly one graph's triples:\n * subgraph isolation is physical (one table per graph, or one Durable\n * Object per graph on Cloudflare), so there is no `scope` discriminator\n * column. The table a row lives in *is* its scope.\n *\n * `data` is a JSON string. Built-in fields are projected to typed columns so\n * the query planner can use indexes without going through `json_extract`.\n *\n * ## Indexes\n *\n * Index specs come from the core preset (overridable via\n * `BuildSchemaOptions.coreIndexes`) plus per-entry `indexes` declared on\n * registry entries. Specs are deduplicated by canonical fingerprint before\n * emission.\n */\n\nimport { DEFAULT_CORE_INDEXES } from '../default-indexes.js';\nimport type { GraphRegistry, IndexSpec } from '../types.js';\nimport { buildIndexDDL, dedupeIndexSpecs } from './sqlite-index-ddl.js';\n\nexport const SQLITE_COLUMNS = [\n 'doc_id',\n 'a_type',\n 'a_uid',\n 'axb_type',\n 'b_type',\n 'b_uid',\n 'data',\n 'v',\n 'created_at',\n 'updated_at',\n] as const;\n\nexport type SqliteColumn = (typeof SQLITE_COLUMNS)[number];\n\n/**\n * Map firegraph field names (as they appear in `QueryFilter.field` and the\n * record envelope) to SQLite column names.\n */\nexport const FIELD_TO_COLUMN: Record<string, SqliteColumn> = {\n aType: 'a_type',\n aUid: 'a_uid',\n axbType: 'axb_type',\n bType: 'b_type',\n bUid: 'b_uid',\n v: 'v',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n};\n\n/**\n * Options controlling DDL emission for `buildSchemaStatements`.\n */\nexport interface BuildSchemaOptions {\n /**\n * Replaces the built-in core preset. Defaults to `DEFAULT_CORE_INDEXES`.\n * Pass `[]` to disable core indexes entirely.\n */\n coreIndexes?: IndexSpec[];\n /**\n * Registry contributing per-triple `indexes` declarations.\n */\n registry?: GraphRegistry;\n}\n\n/**\n * Build the DDL statements that create one graph's triple table and its\n * indexes. Returned as separate statements because some drivers (D1, DO\n * SQLite's `exec()`) require one statement per call.\n *\n * The CREATE TABLE statement is always first; index statements follow in\n * deterministic order. Same specs across runs produce the same statements,\n * so `CREATE … IF NOT EXISTS` is idempotent.\n */\nexport function buildSchemaStatements(table: string, options: BuildSchemaOptions = {}): string[] {\n const t = quoteIdent(table);\n const statements: string[] = [\n `CREATE TABLE IF NOT EXISTS ${t} (\n doc_id TEXT NOT NULL PRIMARY KEY,\n a_type TEXT NOT NULL,\n a_uid TEXT NOT NULL,\n axb_type TEXT NOT NULL,\n b_type TEXT NOT NULL,\n b_uid TEXT NOT NULL,\n data TEXT NOT NULL,\n v INTEGER,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n )`,\n ];\n\n const core = options.coreIndexes ?? [...DEFAULT_CORE_INDEXES];\n const fromRegistry = options.registry?.entries().flatMap((e) => e.indexes ?? []) ?? [];\n\n const deduped = dedupeIndexSpecs([...core, ...fromRegistry]);\n for (const spec of deduped) {\n statements.push(buildIndexDDL(spec, { table, fieldToColumn: FIELD_TO_COLUMN }));\n }\n return statements;\n}\n\n/**\n * Quote a SQL identifier with double quotes, escaping any embedded quotes.\n *\n * Identifier names (table, column, index) come from configuration and\n * static code in this module — never from user data — but quoting still\n * protects against accidental keyword collisions.\n */\nexport function quoteIdent(name: string): string {\n validateTableName(name);\n return `\"${name}\"`;\n}\n\n/**\n * Validate a SQLite identifier (table name, column name) against the\n * allowed character set. Exposed so factory functions can fail fast on\n * an invalid `options.table` rather than waiting until first SQL.\n */\nexport function validateTableName(name: string): void {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {\n throw new Error(`Invalid SQL identifier: ${name}. Must match /^[A-Za-z_][A-Za-z0-9_]*$/.`);\n }\n}\n\n/**\n * Quote a SQL column-alias label. Unlike `quoteIdent` (which validates the\n * input as a SQL identifier and is used for table/column names), this helper\n * accepts arbitrary text — projection aliases are pure labels we read back\n * out of the result row, never executed as identifiers, so they can carry\n * dots (e.g. `data.detail.region`) and other characters that\n * `validateTableName` rejects.\n *\n * Embedded double quotes are escaped per the SQL standard (`\"` → `\"\"`),\n * which is sufficient to prevent the alias text from terminating the quoted\n * label early. This is the only injection vector for an alias — even if\n * the input contained `\";--`, double-quote escaping would render it\n * `\"\"\";--` inside `\"...\"`, harmless.\n *\n * Used by `compileFindEdgesProjected` for the caller-supplied projection\n * field name; the underlying SQL expression (`json_extract(...)`, column\n * reference) still goes through the strict compiler with no caller input.\n */\nexport function quoteColumnAlias(label: string): string {\n return `\"${label.replace(/\"/g, '\"\"')}\"`;\n}\n","/**\n * Shared `dataOps` SQL compilation helpers used by both SQLite-style backends\n * (`internal/sqlite-sql.ts` for the shared-table backend and `cloudflare/sql.ts`\n * for the per-DO backend).\n *\n * The two backends differ in identifier quoting and scope handling, but the\n * `data` column lives in JSON in both, the deep-merge / replace contract is\n * identical, and the `json_set` / `json_remove` expression they emit for a\n * `DataPathOp[]` is byte-for-byte the same. Lifting the helpers here keeps\n * that shape in one place — the comment in `cloudflare/sql.ts` used to read\n * \"keep them in sync\"; this module is what they keep in sync against.\n *\n * The helpers take a `backendLabel` parameter so error messages still\n * distinguish `\"SQLite backend\"` (shared-table) from `\"DO SQLite backend\"`\n * (per-Durable-Object). Identifier quoting is the caller's job — the helpers\n * here only emit JSON-path expressions against an opaque `base` argument,\n * never bare column names.\n */\n\nimport { FiregraphError } from '../errors.js';\nimport type { DataPathOp } from './write-plan.js';\n\n/**\n * Constructor names of Firestore special types that don't survive a plain\n * `JSON.stringify` round-trip — they have non-enumerable accessors (e.g.\n * `Timestamp.seconds`) or class identity that JSON loses. Detection is by\n * `constructor.name` to keep this module dependency-free (importing\n * `@google-cloud/firestore` here would pollute the Cloudflare Workers bundle —\n * see tests/unit/bundle-pollution.test.ts).\n */\nexport const FIRESTORE_TYPE_NAMES = new Set([\n 'Timestamp',\n 'GeoPoint',\n 'VectorValue',\n 'DocumentReference',\n 'FieldValue',\n]);\n\nexport function isFirestoreSpecialType(value: object): string | null {\n const ctorName = (value as { constructor?: { name?: string } }).constructor?.name;\n if (ctorName && FIRESTORE_TYPE_NAMES.has(ctorName)) return ctorName;\n return null;\n}\n\n/**\n * Identifiers accepted in `data.<key>` paths and `dataOps` path segments.\n * The pattern (`/^[A-Za-z_][A-Za-z0-9_-]*$/`) covers code-style identifiers\n * (camel, snake, kebab). Silently quoting exotic keys would require symmetric\n * quoting at every read/write call site; any drift produces silent data\n * corruption. Failing loudly at compile time is safer — users with exotic\n * keys can use `replaceNode` / `replaceEdge` (full-data overwrite) instead.\n */\nexport const JSON_PATH_KEY_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;\n\nexport function validateJsonPathKey(key: string, backendLabel: string): void {\n if (key.length === 0) {\n throw new FiregraphError(\n `${backendLabel}: empty JSON path component is not allowed`,\n 'INVALID_QUERY',\n );\n }\n if (!JSON_PATH_KEY_RE.test(key)) {\n throw new FiregraphError(\n `${backendLabel}: data field path component \"${key}\" is not a safe JSON-path identifier. ` +\n `Allowed pattern: /^[A-Za-z_][A-Za-z0-9_-]*$/. Use replaceNode/replaceEdge (full-data overwrite) ` +\n `for keys with reserved characters (whitespace, dots, brackets, quotes, etc.).`,\n 'INVALID_QUERY',\n );\n }\n}\n\n/**\n * Build a SQLite JSON path (`$.\"a\".\"b\".\"c\"`) from `DataPathOp` segments.\n *\n * Each segment is wrapped as a double-quoted JSON-path label via\n * `JSON.stringify`, which quotes the key and backslash-escapes any embedded\n * double-quotes or backslashes — exactly the escaping SQLite's JSON path\n * parser accepts for quoted labels (verified on SQLite 3.53). Quoting every\n * segment means digit-leading keys (`4f9Kq_2bN`), hyphens, dots, brackets,\n * and whitespace all address the literal key rather than being reparsed as\n * path syntax. Dots stay inside the quotes, so `{ 'a.b': 1 }` writes the\n * single key `\"a.b\"` instead of a nested `a → b`.\n *\n * The result is always bound as a SQL parameter (never interpolated), so\n * there is no injection surface — the quoting here is purely about producing\n * a path string SQLite parses as the intended literal key.\n */\nexport function buildJsonPath(segments: readonly string[]): string {\n return '$' + segments.map((seg) => '.' + JSON.stringify(seg)).join('');\n}\n\n/**\n * Bind a value as a JSON-serializable string for `json(?)` placeholders in\n * the compiled `json_set` expression. `assertJsonSafePayload` already runs\n * eagerly at the write boundary, so the Firestore-special-type rejection\n * here is defense-in-depth — left in place per the team's preference for\n * symmetric guards across the SQLite compilers.\n */\nexport function jsonBind(value: unknown, backendLabel: string): string {\n if (value === undefined) return 'null';\n if (value !== null && typeof value === 'object') {\n const firestoreType = isFirestoreSpecialType(value);\n if (firestoreType) {\n throw new FiregraphError(\n `${backendLabel} cannot persist a Firestore ${firestoreType} value. ` +\n `Convert to a primitive before writing (e.g. \\`ts.toMillis()\\` for Timestamp).`,\n 'INVALID_ARGUMENT',\n );\n }\n }\n return JSON.stringify(value);\n}\n\n/**\n * Build the SQL expression that applies a list of `DataPathOp`s onto an\n * existing JSON column reference (e.g. `\"data\"` or `COALESCE(\"data\", '{}')`).\n *\n * Returns the full expression (already parenthesised where needed) and pushes\n * the bound parameters onto `params` in left-to-right order. Returns `null`\n * when there are no ops at all — the caller picks a fallback expression.\n *\n * Strategy:\n * 1. `json_remove(<base>, '$.a.b', '$.c', …)` strips delete-ops.\n * 2. `json_set(<#1>, '$.x.y', json(?), '$.z', json(?), …)` writes value-ops.\n * `json(?)` ensures non-string values bind as JSON (objects, arrays,\n * numbers, booleans, null).\n */\nexport function compileDataOpsExpr(\n ops: readonly DataPathOp[],\n base: string,\n params: unknown[],\n backendLabel: string,\n): string | null {\n if (ops.length === 0) return null;\n\n const deletes: DataPathOp[] = [];\n const sets: DataPathOp[] = [];\n for (const op of ops) (op.delete ? deletes : sets).push(op);\n\n let expr = base;\n\n if (deletes.length > 0) {\n const placeholders = deletes.map(() => '?').join(', ');\n expr = `json_remove(${expr}, ${placeholders})`;\n for (const op of deletes) {\n params.push(buildJsonPath(op.path));\n }\n }\n\n if (sets.length > 0) {\n const pieces = sets.map(() => '?, json(?)').join(', ');\n expr = `json_set(${expr}, ${pieces})`;\n for (const op of sets) {\n params.push(buildJsonPath(op.path));\n params.push(jsonBind(op.value, backendLabel));\n }\n }\n\n return expr;\n}\n","/**\n * Backend-agnostic timestamp.\n *\n * Structurally compatible with `@google-cloud/firestore`'s `Timestamp` so\n * that records returned by either the Firestore or SQLite backend can be\n * consumed through the same `StoredGraphRecord` shape.\n *\n * Firestore's native `Timestamp` already satisfies this interface, so\n * existing Firestore consumers see no behavior change. The SQLite backend\n * returns instances of `GraphTimestampImpl` which also satisfies it.\n */\n\nexport interface GraphTimestamp {\n readonly seconds: number;\n readonly nanoseconds: number;\n toDate(): Date;\n toMillis(): number;\n}\n\n/**\n * Concrete `GraphTimestamp` implementation used by non-Firestore backends.\n * Mirrors the surface of Firestore's `Timestamp` enough for typical use.\n */\nexport class GraphTimestampImpl implements GraphTimestamp {\n constructor(\n public readonly seconds: number,\n public readonly nanoseconds: number,\n ) {}\n\n toDate(): Date {\n return new Date(this.toMillis());\n }\n\n toMillis(): number {\n return this.seconds * 1000 + Math.floor(this.nanoseconds / 1e6);\n }\n\n toJSON(): { seconds: number; nanoseconds: number } {\n return { seconds: this.seconds, nanoseconds: this.nanoseconds };\n }\n\n static fromMillis(ms: number): GraphTimestampImpl {\n const seconds = Math.floor(ms / 1000);\n const nanoseconds = (ms - seconds * 1000) * 1e6;\n return new GraphTimestampImpl(seconds, nanoseconds);\n }\n\n static now(): GraphTimestampImpl {\n return GraphTimestampImpl.fromMillis(Date.now());\n }\n}\n\n/**\n * Sentinel returned by `StorageBackend.serverTimestamp()` when the backend\n * has no native server-time concept and just wants a placeholder that the\n * adapter resolves to a concrete time at write commit. SQLite backends\n * substitute the wall-clock millis at the moment of `setDoc`/`updateDoc`.\n */\nexport const SERVER_TIMESTAMP_SENTINEL = Symbol.for('firegraph.serverTimestamp');\nexport type ServerTimestampSentinel = typeof SERVER_TIMESTAMP_SENTINEL;\n\nexport function isServerTimestampSentinel(value: unknown): value is ServerTimestampSentinel {\n return value === SERVER_TIMESTAMP_SENTINEL;\n}\n","export const NODE_RELATION = 'is';\n\n/**\n * Default result limit applied to findEdges/findNodes queries\n * when no explicit limit is provided. Prevents unbounded result sets\n * that could be expensive on Enterprise Firestore.\n */\nexport const DEFAULT_QUERY_LIMIT = 500;\n\n/**\n * Fields that are part of the firegraph record structure (not user data).\n * Used by the query planner and safety analysis to distinguish builtin\n * fields from data.* fields.\n */\nexport const BUILTIN_FIELDS = new Set([\n 'aType',\n 'aUid',\n 'axbType',\n 'bType',\n 'bUid',\n 'createdAt',\n 'updatedAt',\n]);\n\nexport const SHARD_ALGORITHM = 'sha256';\nexport const SHARD_SEPARATOR = ':';\nexport const SHARD_BUCKETS = 16;\n","/**\n * Firegraph serialization tag — split from `src/serialization.ts` so it can\n * be imported from Workers-facing code without dragging in\n * `@google-cloud/firestore`.\n *\n * The full serialization module (with Timestamp/GeoPoint round-tripping)\n * lives one folder up because the sandbox migration pipeline needs it; the\n * write-plan helper only needs to recognise tagged objects to keep them\n * terminal during patch flattening, so it imports just the tag from here.\n */\n\n/** Sentinel key used to tag serialized Firestore types. */\nexport const SERIALIZATION_TAG = '__firegraph_ser__' as const;\n\nconst KNOWN_TYPES = new Set(['Timestamp', 'GeoPoint', 'VectorValue', 'DocumentReference']);\n\n/** Check if a value is a tagged serialized Firestore type. */\nexport function isTaggedValue(value: unknown): boolean {\n if (value === null || typeof value !== 'object') return false;\n const tag = (value as Record<string, unknown>)[SERIALIZATION_TAG];\n return typeof tag === 'string' && KNOWN_TYPES.has(tag);\n}\n","/**\n * Write-plan helper — flattens partial-update payloads into a list of\n * deep-path operations every backend can execute identically.\n *\n * Background: firegraph used to ship two write semantics that quietly\n * disagreed about depth.\n * - `putNode`/`putEdge` did a full document replace.\n * - `updateNode`/`updateEdge` did a one-level shallow merge: top-level\n * keys were preserved, but nested objects were replaced wholesale.\n *\n * Both behaviours dropped sibling keys silently. The 0.12 contract is that\n * `put*` and `update*` deep-merge by default (sibling keys at any depth\n * survive); `replace*` is the explicit escape hatch.\n *\n * `flattenPatch` walks a partial-update payload and emits one\n * {@link DataPathOp} per terminal value. Plain objects recurse; arrays,\n * primitives, Firestore special types, and tagged firegraph-serialization\n * objects are terminal (replaced as a unit). `undefined` values are\n * skipped; `null` is preserved as a real `null` write; the\n * {@link DELETE_FIELD} sentinel marks a field for removal.\n *\n * The output is deliberately backend-agnostic. Each backend translates ops\n * into its native dialect:\n * - Firestore: dotted field path → `data.a.b.c` for `update()`.\n * - SQLite / DO SQLite: `json_set(data, '$.a.b.c', ?)` /\n * `json_remove(data, '$.a.b.c')`.\n */\n\nimport { isTaggedValue, SERIALIZATION_TAG } from './serialization-tag.js';\n\n// ---------------------------------------------------------------------------\n// Public sentinel\n// ---------------------------------------------------------------------------\n\n/**\n * Sentinel returned by {@link deleteField}. Treated by all backends as\n * \"remove this field from the stored document\".\n *\n * Equivalent to Firestore's `FieldValue.delete()`, but works for SQLite\n * backends too. Use inside `updateNode`/`updateEdge` payloads.\n */\nexport const DELETE_FIELD: unique symbol = Symbol.for('firegraph.deleteField');\nexport type DeleteSentinel = typeof DELETE_FIELD;\n\n/**\n * Returns the firegraph delete sentinel. Place this anywhere in an\n * `updateNode`/`updateEdge` payload to remove the corresponding field.\n *\n * ```ts\n * await client.updateNode('tour', uid, {\n * attrs: { obsoleteFlag: deleteField() },\n * });\n * ```\n */\nexport function deleteField(): DeleteSentinel {\n return DELETE_FIELD;\n}\n\n/** Type guard for the delete sentinel. */\nexport function isDeleteSentinel(value: unknown): value is DeleteSentinel {\n return value === DELETE_FIELD;\n}\n\n// ---------------------------------------------------------------------------\n// Terminal-detection helpers\n// ---------------------------------------------------------------------------\n\nconst FIRESTORE_TERMINAL_CTOR = new Set([\n 'Timestamp',\n 'GeoPoint',\n 'VectorValue',\n 'DocumentReference',\n 'FieldValue',\n 'NumericIncrementTransform',\n 'ArrayUnionTransform',\n 'ArrayRemoveTransform',\n 'ServerTimestampTransform',\n 'DeleteTransform',\n]);\n\n/**\n * Should this value be written as a single terminal op (no recursion)?\n *\n * Plain JS objects (constructor === Object, or no prototype) are recursed.\n * Everything else — arrays, primitives, class instances, Firestore special\n * types, tagged serialization payloads — is terminal.\n */\nexport function isTerminalValue(value: unknown): boolean {\n if (value === null) return true;\n const t = typeof value;\n if (t !== 'object') return true;\n if (Array.isArray(value)) return true;\n // Tagged serialization payloads carry the SERIALIZATION_TAG sentinel and\n // should be persisted whole — never split into per-field ops.\n if (isTaggedValue(value)) return true;\n const proto = Object.getPrototypeOf(value);\n if (proto === null || proto === Object.prototype) return false;\n // Class instances — Firestore types or anything else exotic.\n const ctor = (value as { constructor?: { name?: string } }).constructor;\n if (ctor && typeof ctor.name === 'string' && FIRESTORE_TERMINAL_CTOR.has(ctor.name)) return true;\n // Unknown class instance: treat as terminal. Recursing into a class\n // instance is almost always wrong (Map, Set, Date, Buffer...).\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Core type\n// ---------------------------------------------------------------------------\n\n/**\n * Single terminal write operation produced by {@link flattenPatch}.\n *\n * `path` is a non-empty array of plain object keys. `value` is the value to\n * write; ignored when `delete` is `true`. Arrays / primitives / Firestore\n * special types appear here as whole terminal values.\n */\nexport interface DataPathOp {\n path: readonly string[];\n value: unknown;\n delete: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Path-segment validation\n// ---------------------------------------------------------------------------\n\n/**\n * Mutual-exclusion guard for {@link UpdatePayload}. The two branches of the\n * shape — `dataOps` (deep-merge) and `replaceData` (full replace) — are\n * structurally incompatible: combining them would tell the backend to\n * simultaneously merge AND wipe, and the three backends disagree on which\n * wins. This helper centralises the runtime check so all three backends\n * trip the same error.\n *\n * Imported as a runtime check from `firestore-backend`, `sqlite-sql`, and\n * `cloudflare/sql`. Backend authors implementing the public `StorageBackend`\n * contract should call it too.\n */\nexport function assertUpdatePayloadExclusive(update: {\n dataOps?: unknown;\n replaceData?: unknown;\n}): void {\n if (update.replaceData !== undefined && update.dataOps !== undefined) {\n throw new Error(\n 'firegraph: UpdatePayload cannot specify both `replaceData` and `dataOps`. ' +\n 'Use one or the other — `replaceData` is the migration-write-back form, ' +\n '`dataOps` is the standard partial-update form.',\n );\n }\n}\n\n/**\n * Reject `DELETE_FIELD` sentinels in payloads where field deletion isn't a\n * meaningful operation: full-document replace (`replaceNode`/`replaceEdge`)\n * and the merge-default put surface (`putNode`/`putEdge`).\n *\n * Why both:\n * - In **replace**, the entire `data` field is overwritten. A delete\n * sentinel in that payload either silently disappears (Firestore drops\n * the Symbol during `.set()` serialization) or produces an empty SQLite\n * `json_remove` no-op, depending on backend. Either way the caller's\n * intent — \"remove field X\" — is lost. Use `updateNode` instead.\n * - In **put** (merge mode), behaviour diverges across backends today:\n * SQLite's flattenPatch emits a real delete op, but Firestore's\n * `.set(..., {merge: true})` silently drops the Symbol. Until that's\n * fixed end-to-end, the safest contract is to reject sentinels at the\n * entry point and steer callers to `updateNode`.\n *\n * The walk mirrors `flattenPatch`: plain objects recurse, everything else\n * is terminal. Tagged serialization payloads short-circuit so we don't\n * recurse into the `__firegraph_ser__` envelope.\n */\nexport function assertNoDeleteSentinels(data: unknown, callerLabel: string): void {\n walkForDeleteSentinels(data, [], { kind: 'root' }, ({ path }) => {\n const where = path.length === 0 ? '<root>' : path.map((p) => JSON.stringify(p)).join(' > ');\n throw new Error(\n `firegraph: ${callerLabel} payload contains a deleteField() sentinel at ${where}. ` +\n `deleteField() is only valid inside updateNode/updateEdge — full-data ` +\n `writes (put*, replace*) cannot delete individual fields. Use updateNode ` +\n `with a deleteField() value, or omit the field from the replace payload.`,\n );\n });\n}\n\ntype SentinelParent = { kind: 'root' } | { kind: 'object' } | { kind: 'array'; index: number };\n\nfunction walkForDeleteSentinels(\n node: unknown,\n path: readonly string[],\n parent: SentinelParent,\n visit: (ctx: { path: readonly string[]; parent: SentinelParent }) => void,\n): void {\n if (node === null || node === undefined) return;\n if (isDeleteSentinel(node)) {\n visit({ path, parent });\n return;\n }\n if (typeof node !== 'object') return;\n if (isTaggedValue(node)) return;\n if (Array.isArray(node)) {\n for (let i = 0; i < node.length; i++) {\n walkForDeleteSentinels(node[i], [...path, String(i)], { kind: 'array', index: i }, visit);\n }\n return;\n }\n const proto = Object.getPrototypeOf(node);\n if (proto !== null && proto !== Object.prototype) return;\n const obj = node as Record<string, unknown>;\n for (const key of Object.keys(obj)) {\n walkForDeleteSentinels(obj[key], [...path, key], { kind: 'object' }, visit);\n }\n}\n\n/**\n * Throws if any path segment is an empty string.\n *\n * Empty keys are the one shape no backend can represent: Firestore's\n * `FieldPath` rejects empty segments, and a SQLite JSON-path label `$.\"\"`\n * is meaningless. Every other key — digits-first, hyphens, dots, brackets,\n * whitespace, non-ASCII — is escaped by the backends at path-construction\n * time (Firestore via `FieldPath` literal segments, SQLite via\n * `JSON.stringify`-quoted labels), so it is accepted here untouched.\n */\nexport function assertSafePath(path: readonly string[]): void {\n for (const seg of path) {\n if (seg === '') {\n throw new Error(\n `firegraph: empty object key at path ${path\n .map((p) => JSON.stringify(p))\n .join(' > ')}. Object keys in update payloads must be non-empty.`,\n );\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// flattenPatch\n// ---------------------------------------------------------------------------\n\n/**\n * Flatten a partial-update payload into a list of terminal {@link DataPathOp}s.\n *\n * Rules:\n * - Plain objects (no prototype or `Object.prototype`) recurse — each\n * key becomes another path segment.\n * - Arrays are terminal: writing `{tags: ['a']}` overwrites the whole\n * `tags` array. Element-wise array merging is intentionally NOT\n * supported — it's almost never what callers actually want, and\n * Firestore `arrayUnion`/`arrayRemove` give precise semantics when\n * they are.\n * - `undefined` values are skipped (no op generated). Use\n * {@link deleteField} if you actually want to remove a field.\n * - `null` is preserved verbatim — emits a terminal op with `value: null`.\n * - {@link DELETE_FIELD} produces an op with `delete: true`.\n * - Firestore special types and tagged serialization payloads are terminal.\n * - Class instances are terminal.\n *\n * Throws if any object key on the recursion path is unsafe (see\n * {@link assertSafePath}).\n */\nexport function flattenPatch(data: Record<string, unknown>): DataPathOp[] {\n const ops: DataPathOp[] = [];\n walk(data, [], ops);\n return ops;\n}\n\nfunction assertNoDeleteSentinelsInArrayValue(\n arr: readonly unknown[],\n arrayPath: readonly string[],\n): void {\n walkForDeleteSentinels(arr, arrayPath, { kind: 'root' }, ({ parent }) => {\n const arrayPathStr =\n arrayPath.length === 0 ? '<root>' : arrayPath.map((p) => JSON.stringify(p)).join(' > ');\n if (parent.kind === 'array') {\n throw new Error(\n `firegraph: deleteField() sentinel at index ${parent.index} inside an array at ` +\n `path ${arrayPathStr}. Arrays are ` +\n `terminal in update payloads (replaced as a unit), so the sentinel ` +\n `would be silently dropped by JSON serialization. To remove the ` +\n `field entirely, pass deleteField() in place of the whole array.`,\n );\n }\n throw new Error(\n `firegraph: deleteField() sentinel inside an array element at ` +\n `path ${arrayPathStr}. ` +\n `Arrays are terminal in update payloads — the sentinel would ` +\n `be silently dropped by JSON serialization.`,\n );\n });\n}\n\nfunction walk(node: unknown, path: string[], out: DataPathOp[]): void {\n // Caller guarantees the root is a plain object; this branch only\n // matters for recursion.\n if (node === undefined) return;\n if (isDeleteSentinel(node)) {\n if (path.length === 0) {\n throw new Error('firegraph: deleteField() cannot be the entire update payload.');\n }\n assertSafePath(path);\n out.push({ path: [...path], value: undefined, delete: true });\n return;\n }\n if (isTerminalValue(node)) {\n if (path.length === 0) {\n // `null` / array / primitive at the root is illegal — patches must\n // describe per-key changes.\n throw new Error(\n 'firegraph: update payload must be a plain object. Got ' +\n (node === null ? 'null' : Array.isArray(node) ? 'array' : typeof node) +\n '.',\n );\n }\n // A DELETE_FIELD sentinel embedded inside an array (which is terminal\n // and replaced as a unit) would silently disappear: JSON.stringify drops\n // Symbols, and Firestore's serializer does likewise. Reject loudly so\n // the divergence between \"user wrote a delete\" and \"field stayed put\"\n // can't happen.\n if (Array.isArray(node)) {\n assertNoDeleteSentinelsInArrayValue(node, path);\n }\n assertSafePath(path);\n out.push({ path: [...path], value: node, delete: false });\n return;\n }\n // Plain object: recurse into its own enumerable keys.\n const obj = node as Record<string, unknown>;\n const keys = Object.keys(obj);\n if (keys.length === 0) {\n // Empty object at non-root: emit terminal op so an empty object can\n // be written explicitly when the caller really wants one. Skip at\n // the root — no-op patches should produce no ops.\n if (path.length > 0) {\n assertSafePath(path);\n out.push({ path: [...path], value: {}, delete: false });\n }\n return;\n }\n for (const key of keys) {\n if (key === SERIALIZATION_TAG) {\n const where = path.length === 0 ? '<root>' : path.map((p) => JSON.stringify(p)).join(' > ');\n throw new Error(\n `firegraph: update payload contains a literal \\`${SERIALIZATION_TAG}\\` key at ` +\n `${where}. That key is reserved for firegraph's serialization envelope and ` +\n `cannot appear on a plain object in user data. Use a different field name, ` +\n `or pass a recognized tagged value through replaceNode/replaceEdge instead.`,\n );\n }\n walk(obj[key], [...path, key], out);\n }\n}\n","/**\n * Shared eager-validation helper for SQLite-style backends\n * (`internal/sqlite-sql.ts` and `cloudflare/sql.ts`).\n *\n * Both backends serialise `record.data` (and `update.replaceData`) as a raw\n * JSON blob via `JSON.stringify`. Two classes of value silently corrupt that\n * representation and the cross-backend contract:\n *\n * 1. **Firestore special types** (`Timestamp`, `GeoPoint`, `VectorValue`,\n * `DocumentReference`, `FieldValue`). They have non-enumerable accessors\n * or rely on class identity that JSON drops, so they round-trip as `{}`\n * or garbage. Callers must convert to primitives before writing.\n * 2. **`DELETE_FIELD` sentinel.** A `Symbol` is invisible to\n * `JSON.stringify`. If a caller embeds the sentinel in a `replaceNode`\n * payload or in a fresh-insert (no existing row), the field would\n * silently disappear instead of erroring loudly the way it does for the\n * `dataOps` path — so we reject it eagerly here.\n * 3. **Tagged serialization payloads** (`__firegraph_ser__`). These are the\n * sandbox migration boundary marshalling form. They are valid inside\n * Firestore (the Firestore backend re-hydrates them via\n * `deserializeFirestoreTypes`), but on SQLite they would persist as\n * opaque tagged objects that no downstream reader knows how to interpret.\n * Reject them at the boundary so the failure is loud.\n *\n * The Firestore backend does NOT call this — it accepts those types natively\n * and `deserializeFirestoreTypes` rebuilds tagged values into real Firestore\n * objects on its own write path.\n *\n * Detection avoids `instanceof` so this module stays free of\n * `@google-cloud/firestore`. Constructor-name + duck-type matches the\n * approach used by `bindValue`/`jsonBind` elsewhere in the SQLite compilers.\n */\n\nimport { FiregraphError } from '../errors.js';\nimport { SERIALIZATION_TAG } from './serialization-tag.js';\nimport { isDeleteSentinel } from './write-plan.js';\n\nconst FIRESTORE_TYPE_NAMES = new Set([\n 'Timestamp',\n 'GeoPoint',\n 'VectorValue',\n 'DocumentReference',\n 'FieldValue',\n]);\n\n/**\n * Walk `data` and throw on any value that the SQLite-style raw-JSON\n * persistence path can't faithfully serialise. `label` distinguishes the\n * caller in error messages (e.g. `'shared-table SQLite'` vs `'DO SQLite'`).\n *\n * Plain objects recurse. Arrays recurse element-wise. Primitives, `null`,\n * and `undefined` are accepted (mirroring how `flattenPatch` treats them\n * during the merge path).\n */\nexport function assertJsonSafePayload(data: unknown, label: string): void {\n walk(data, [], label);\n}\n\nfunction walk(node: unknown, path: readonly string[], label: string): void {\n if (node === null || node === undefined) return;\n if (isDeleteSentinel(node)) {\n throw new FiregraphError(\n `${label} backend cannot persist a deleteField() sentinel inside a ` +\n `full-data payload (replaceNode/replaceEdge or first-insert). The ` +\n `sentinel is only valid inside an updateNode/updateEdge dataOps patch. ` +\n `Path: ${formatPath(path)}.`,\n 'INVALID_ARGUMENT',\n );\n }\n const t = typeof node;\n if (t === 'symbol' || t === 'function') {\n throw new FiregraphError(\n `${label} backend cannot persist a value of type ${t}. ` +\n `JSON.stringify drops it silently. Path: ${formatPath(path)}.`,\n 'INVALID_ARGUMENT',\n );\n }\n if (t === 'bigint') {\n throw new FiregraphError(\n `${label} backend cannot persist a value of type bigint. ` +\n `JSON.stringify cannot serialize this type (throws TypeError). ` +\n `Path: ${formatPath(path)}.`,\n 'INVALID_ARGUMENT',\n );\n }\n if (t !== 'object') return;\n if (Array.isArray(node)) {\n for (let i = 0; i < node.length; i++) {\n walk(node[i], [...path, String(i)], label);\n }\n return;\n }\n // Reject any object carrying the firegraph serialization tag — both legit\n // tagged Firestore-type payloads (the migration-sandbox output that round-\n // trips through Firestore) and bogus user data that happens to put a\n // literal `__firegraph_ser__` key on a plain object. SQLite has no\n // Timestamp class to rebuild the tag into, and silently writing the\n // envelope would produce an unreadable column.\n const obj = node as Record<string, unknown>;\n if (Object.prototype.hasOwnProperty.call(obj, SERIALIZATION_TAG)) {\n const tagValue = obj[SERIALIZATION_TAG];\n throw new FiregraphError(\n `${label} backend cannot persist an object with a \\`${SERIALIZATION_TAG}\\` ` +\n `key (value: ${formatTagValue(tagValue)}). Recognised tags are valid only on ` +\n `the Firestore backend (migration-sandbox output); a literal ` +\n `\\`${SERIALIZATION_TAG}\\` field in user data is reserved and not allowed. ` +\n `Path: ${formatPath(path)}.`,\n 'INVALID_ARGUMENT',\n );\n }\n // Class instances: reject Firestore special types loudly; reject every\n // other class instance generically (Map, Set, Date are caller's\n // responsibility to convert — Date is allowed in filter binds via\n // `bindValue` but not as a stored payload value because JSON.stringify\n // produces a string, not a real Date).\n const proto = Object.getPrototypeOf(node);\n if (proto !== null && proto !== Object.prototype) {\n const ctor = (node as { constructor?: { name?: string } }).constructor;\n const ctorName = ctor && typeof ctor.name === 'string' ? ctor.name : '<anonymous>';\n if (FIRESTORE_TYPE_NAMES.has(ctorName)) {\n throw new FiregraphError(\n `${label} backend cannot persist a Firestore ${ctorName} value. ` +\n `Convert to a primitive before writing (e.g. \\`ts.toMillis()\\` for ` +\n `Timestamp, \\`{lat,lng}\\` for GeoPoint). Path: ${formatPath(path)}.`,\n 'INVALID_ARGUMENT',\n );\n }\n // Accept Date as an alias for its epoch-ms — it round-trips as an ISO\n // string via JSON.stringify, which the caller chose; not our place to\n // reject. Same for Buffer / typed arrays — they'll JSON-serialize as\n // best they can. Reject only opaque exotic instances that JSON drops.\n if (node instanceof Date) return;\n throw new FiregraphError(\n `${label} backend cannot persist a class instance of type ${ctorName}. ` +\n `Only plain objects, arrays, and primitives round-trip safely through ` +\n `JSON storage. Path: ${formatPath(path)}.`,\n 'INVALID_ARGUMENT',\n );\n }\n for (const key of Object.keys(obj)) {\n walk(obj[key], [...path, key], label);\n }\n}\n\nfunction formatPath(path: readonly string[]): string {\n return path.length === 0 ? '<root>' : path.map((p) => JSON.stringify(p)).join(' > ');\n}\n\nfunction formatTagValue(value: unknown): string {\n if (value === null) return 'null';\n if (value === undefined) return 'undefined';\n if (typeof value === 'string') return JSON.stringify(value);\n if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {\n return String(value);\n }\n return typeof value;\n}\n","/**\n * SQL compilation for SQLite-shaped firegraph backends.\n *\n * One table holds exactly one graph's triples — there is no `scope` column\n * and no scope discriminator on any statement. Subgraph isolation is\n * physical: the shared SQLite backend (`src/sqlite/`) maps each graph to\n * its own table, and the Cloudflare DO backend (`src/cloudflare/`) maps\n * each graph to its own Durable Object. Every compiler in this module is\n * parameterized by the target table name.\n *\n * Filter compilation, JSON-path validation, and value binding are shared so\n * the query planner (`src/query.ts`) emits the same `QueryFilter[]` shape\n * regardless of backend.\n */\n\nimport { FiregraphError } from '../errors.js';\nimport type { GraphTimestamp } from '../timestamp.js';\nimport { GraphTimestampImpl } from '../timestamp.js';\nimport type {\n AggregateSpec,\n ExpandParams,\n QueryFilter,\n QueryOptions,\n StoredGraphRecord,\n} from '../types.js';\nimport type { UpdatePayload, WritableRecord, WriteMode } from './backend.js';\nimport { NODE_RELATION } from './constants.js';\nimport {\n compileDataOpsExpr,\n isFirestoreSpecialType,\n validateJsonPathKey,\n} from './sqlite-data-ops.js';\nimport { assertJsonSafePayload } from './sqlite-payload-guard.js';\nimport { FIELD_TO_COLUMN, quoteColumnAlias, quoteIdent } from './sqlite-schema.js';\nimport { assertUpdatePayloadExclusive, flattenPatch } from './write-plan.js';\n\nconst BACKEND_LABEL = 'SQLite';\nconst BACKEND_ERR_LABEL = 'SQLite backend';\n\nexport interface CompiledStatement {\n sql: string;\n params: unknown[];\n}\n\n// ---------------------------------------------------------------------------\n// Filter compilation\n// ---------------------------------------------------------------------------\n\n/**\n * Translate a firegraph filter field to either a column reference or a\n * `json_extract(\"data\", '$.<path>')` expression. Built-in fields go\n * straight to their column; `data.<key>[.<key>…]` and bare `data` are\n * projected through `json_extract` with the JSON path **inlined as a\n * string literal** — not parametrized.\n *\n * Inlining matters: SQLite's query planner matches an expression index\n * (`CREATE INDEX … ON tbl(json_extract(\"data\", '$.status'))`) against\n * *textually identical* expressions in the WHERE clause. `json_extract(\n * \"data\", ?)` parametrizes the path and would never hit the index, even\n * though it evaluates to the same value. Inlining here makes the\n * expression literal in the SQL, which is what the index builder in\n * `sqlite-index-ddl.ts` also emits.\n *\n * Inlining is safe: each path component is validated against\n * `JSON_PATH_KEY_RE` (`/^[A-Za-z_][A-Za-z0-9_-]*$/`) before it reaches\n * this function — the pattern excludes every character SQLite would\n * treat as syntax (quote, backslash, dot, bracket, whitespace), so\n * string concatenation can't produce injection.\n */\nfunction compileFieldRef(field: string): { expr: string } {\n const column = FIELD_TO_COLUMN[field];\n if (column) {\n return { expr: quoteIdent(column) };\n }\n if (field.startsWith('data.')) {\n const suffix = field.slice(5);\n for (const part of suffix.split('.')) {\n validateJsonPathKey(part, BACKEND_ERR_LABEL);\n }\n return { expr: `json_extract(\"data\", '$.${suffix}')` };\n }\n if (field === 'data') {\n return { expr: `json_extract(\"data\", '$')` };\n }\n throw new FiregraphError(`SQLite backend cannot resolve filter field: ${field}`, 'INVALID_QUERY');\n}\n\n/**\n * Coerce a JS filter/update value into a SQLite-bindable primitive. Firestore\n * special types are rejected loudly because `JSON.stringify` would emit\n * garbage that silently fails to match any stored row. Callers should\n * project to a primitive (e.g. `ts.toMillis()`) before passing in.\n */\nfunction bindValue(value: unknown): unknown {\n if (value === null || value === undefined) return null;\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'bigint' ||\n typeof value === 'boolean'\n ) {\n return value;\n }\n if (value instanceof Date) return value.getTime();\n if (typeof value === 'object') {\n const firestoreType = isFirestoreSpecialType(value);\n if (firestoreType) {\n throw new FiregraphError(\n `SQLite backend cannot bind a Firestore ${firestoreType} value — JSON serialization ` +\n `would silently drop fields and the resulting bind would never match a stored row. ` +\n `Convert to a primitive (e.g. \\`ts.toMillis()\\` for Timestamp) before filtering or updating.`,\n 'INVALID_QUERY',\n );\n }\n return JSON.stringify(value);\n }\n return String(value);\n}\n\nfunction compileFilter(filter: QueryFilter, params: unknown[]): string {\n const { expr } = compileFieldRef(filter.field);\n\n switch (filter.op) {\n case '==':\n params.push(bindValue(filter.value));\n return `${expr} = ?`;\n case '!=':\n params.push(bindValue(filter.value));\n return `${expr} != ?`;\n case '<':\n params.push(bindValue(filter.value));\n return `${expr} < ?`;\n case '<=':\n params.push(bindValue(filter.value));\n return `${expr} <= ?`;\n case '>':\n params.push(bindValue(filter.value));\n return `${expr} > ?`;\n case '>=':\n params.push(bindValue(filter.value));\n return `${expr} >= ?`;\n case 'in': {\n const values = asArray(filter.value, 'in');\n const placeholders = values.map(() => '?').join(', ');\n for (const v of values) params.push(bindValue(v));\n return `${expr} IN (${placeholders})`;\n }\n case 'not-in': {\n const values = asArray(filter.value, 'not-in');\n const placeholders = values.map(() => '?').join(', ');\n for (const v of values) params.push(bindValue(v));\n return `${expr} NOT IN (${placeholders})`;\n }\n case 'array-contains': {\n params.push(bindValue(filter.value));\n return `EXISTS (SELECT 1 FROM json_each(${expr}) WHERE value = ?)`;\n }\n case 'array-contains-any': {\n const values = asArray(filter.value, 'array-contains-any');\n const placeholders = values.map(() => '?').join(', ');\n for (const v of values) params.push(bindValue(v));\n return `EXISTS (SELECT 1 FROM json_each(${expr}) WHERE value IN (${placeholders}))`;\n }\n default:\n throw new FiregraphError(\n `SQLite backend does not support filter operator: ${String(filter.op)}`,\n 'INVALID_QUERY',\n );\n }\n}\n\n/**\n * Compile a filter list into SQL condition fragments, pushing bound values\n * onto `params` in order. This is the same code path `compileSelect` runs,\n * exported so the local-SQLite search layer (`src/internal/sqlite-search.ts`)\n * compiles vector-search `where` / identifying filters with identical\n * operator support and `data.*` path handling.\n */\nexport function compileFilterConditions(filters: QueryFilter[], params: unknown[]): string[] {\n return filters.map((f) => compileFilter(f, params));\n}\n\nfunction asArray(value: unknown, op: string): unknown[] {\n if (!Array.isArray(value) || value.length === 0) {\n throw new FiregraphError(`Operator \"${op}\" requires a non-empty array value`, 'INVALID_QUERY');\n }\n return value;\n}\n\nfunction compileOrderBy(options: QueryOptions | undefined, _params: unknown[]): string {\n if (!options?.orderBy) return '';\n const { field, direction } = options.orderBy;\n const { expr } = compileFieldRef(field);\n const dir = direction === 'desc' ? 'DESC' : 'ASC';\n return ` ORDER BY ${expr} ${dir}`;\n}\n\nfunction compileLimit(options: QueryOptions | undefined, params: unknown[]): string {\n if (options?.limit === undefined) return '';\n params.push(options.limit);\n return ` LIMIT ?`;\n}\n\n// ---------------------------------------------------------------------------\n// Statement compilation\n// ---------------------------------------------------------------------------\n\n/**\n * SELECT rows matching `filters`. No scope predicate — every row in the\n * table belongs to the same graph.\n */\nexport function compileSelect(\n table: string,\n filters: QueryFilter[],\n options?: QueryOptions,\n): CompiledStatement {\n const params: unknown[] = [];\n const conditions: string[] = [];\n\n for (const f of filters) {\n conditions.push(compileFilter(f, params));\n }\n\n const where = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '';\n let sql = `SELECT * FROM ${quoteIdent(table)}${where}`;\n sql += compileOrderBy(options, params);\n sql += compileLimit(options, params);\n\n return { sql, params };\n}\n\n/**\n * Compile an `expand()` fan-out into one SELECT statement.\n *\n * Forward direction:\n *\n * SELECT * FROM <table>\n * WHERE \"axb_type\" = ? AND \"a_uid\" IN (?, ?, …)\n * [AND \"a_type\" = ?] [AND \"b_type\" = ?]\n * [ORDER BY …]\n * [LIMIT ?]\n *\n * Reverse swaps the IN-predicate to `\"b_uid\"`. Per-source LIMIT enforcement\n * is approximated by `sources.length * limitPerSource` (see\n * `ExpandParams.limitPerSource`); window-function partitioning is out of\n * scope for the round-trip-collapse goal.\n *\n * Empty `params.sources` is rejected at the compiler — `IN ()` is invalid\n * SQL. Backends short-circuit empty input before reaching here.\n */\nexport function compileExpand(table: string, params: ExpandParams): CompiledStatement {\n if (params.sources.length === 0) {\n throw new FiregraphError(\n 'compileExpand requires a non-empty sources list — empty IN () is invalid SQL.',\n 'INVALID_QUERY',\n );\n }\n const direction = params.direction ?? 'forward';\n // Resolve every column reference through `compileFieldRef` so the\n // emitted SQL uses the on-disk snake_case names (`a_uid`, `axb_type`,\n // `b_uid`, …) defined by `FIELD_TO_COLUMN` in `sqlite-schema.ts`.\n const aUidCol = compileFieldRef('aUid').expr;\n const bUidCol = compileFieldRef('bUid').expr;\n const aTypeCol = compileFieldRef('aType').expr;\n const bTypeCol = compileFieldRef('bType').expr;\n const axbTypeCol = compileFieldRef('axbType').expr;\n const sourceColumn = direction === 'forward' ? aUidCol : bUidCol;\n\n const sqlParams: unknown[] = [params.axbType];\n const conditions: string[] = [`${axbTypeCol} = ?`];\n\n const placeholders = params.sources.map(() => '?').join(', ');\n conditions.push(`${sourceColumn} IN (${placeholders})`);\n for (const uid of params.sources) sqlParams.push(uid);\n\n if (params.aType !== undefined) {\n conditions.push(`${aTypeCol} = ?`);\n sqlParams.push(params.aType);\n }\n if (params.bType !== undefined) {\n conditions.push(`${bTypeCol} = ?`);\n sqlParams.push(params.bType);\n }\n\n // Self-loop guard for the corner case where the caller asks for the\n // node-relation as the hop type — without it, every node row would\n // come back as a degenerate \"edge\" from itself to itself.\n if (params.axbType === NODE_RELATION) {\n conditions.push(`${aUidCol} != ${bUidCol}`);\n }\n\n let sql = `SELECT * FROM ${quoteIdent(table)} WHERE ${conditions.join(' AND ')}`;\n\n if (params.orderBy) {\n sql += compileOrderBy({ orderBy: params.orderBy }, sqlParams);\n }\n if (params.limitPerSource !== undefined) {\n const totalLimit = params.sources.length * params.limitPerSource;\n sql += ` LIMIT ?`;\n sqlParams.push(totalLimit);\n }\n return { sql, params: sqlParams };\n}\n\n/**\n * Hydration-pass for `expand({ hydrate: true })`. Pulls every node row\n * whose `bUid` is in the supplied target list (one statement). The caller\n * stitches alignment in JS via a `Map<bUid, row>`.\n */\nexport function compileExpandHydrate(table: string, targetUids: string[]): CompiledStatement {\n if (targetUids.length === 0) {\n throw new FiregraphError(\n 'compileExpandHydrate requires a non-empty target list — empty IN () is invalid SQL.',\n 'INVALID_QUERY',\n );\n }\n const placeholders = targetUids.map(() => '?').join(', ');\n const sqlParams: unknown[] = [NODE_RELATION];\n for (const uid of targetUids) sqlParams.push(uid);\n\n // Resolve column refs via `compileFieldRef` — see `compileExpand`\n // for the schema-rename rationale.\n const aUidCol = compileFieldRef('aUid').expr;\n const bUidCol = compileFieldRef('bUid').expr;\n const axbTypeCol = compileFieldRef('axbType').expr;\n\n return {\n sql:\n `SELECT * FROM ${quoteIdent(table)} ` +\n `WHERE ${axbTypeCol} = ? AND ${aUidCol} = ${bUidCol} AND ${bUidCol} IN (${placeholders})`,\n params: sqlParams,\n };\n}\n\n/**\n * SELECT a single row by doc_id. `doc_id` is the PK so this is an O(1)\n * index lookup.\n */\nexport function compileSelectByDocId(table: string, docId: string): CompiledStatement {\n return {\n sql: `SELECT * FROM ${quoteIdent(table)} WHERE \"doc_id\" = ? LIMIT 1`,\n params: [docId],\n };\n}\n\n/**\n * Discriminator for one projected column. The decoder uses this to recover\n * the JS-shape of the requested field.\n */\nexport type ProjectedColumnKind =\n | 'builtin-text'\n | 'builtin-int'\n | 'builtin-timestamp'\n | 'data'\n | 'json';\n\n/** Per-column metadata returned alongside the compiled projection statement. */\nexport interface ProjectedColumnSpec {\n /** Original caller-supplied field name. Used as the alias in the SQL\n * projection list AND as the key in the returned JS row. */\n field: string;\n /** Kind discriminator — see `ProjectedColumnKind`. */\n kind: ProjectedColumnKind;\n /**\n * For `kind === 'json'` only: alias of the paired `json_type` companion\n * column. Uses a positional sentinel (`__fg_t_<idx>`) keyed by the\n * field's position in the unique projection list rather than the\n * historical `<field>__t` suffix, which would collide if the caller\n * projected both `'foo'` and `'foo__t'` (both legal user input).\n */\n typeAlias?: string;\n}\n\n/**\n * Normalize a projection field name to the canonical form `compileFieldRef`\n * understands: built-ins stay as-is, `data` and `data.*` stay as-is, and a\n * bare `name` is rewritten to `data.name`.\n */\nfunction normalizeProjectionField(field: string): string {\n if (field in FIELD_TO_COLUMN) return field;\n if (field === 'data' || field.startsWith('data.')) return field;\n return `data.${field}`;\n}\n\n/**\n * Compile a `findEdgesProjected({ select })` call into a single SELECT.\n *\n * Shape:\n *\n * SELECT\n * <expr-1> AS \"<field-1>\", [json_type(...) AS \"__fg_t_<idx>\",]\n * ...\n * FROM <table>\n * [WHERE <filters>]\n * [ORDER BY ...]\n * [LIMIT ?]\n *\n * For `data.*` projections the compiler also emits a paired `json_type`\n * column so the decoder can recover JSON-encoded objects/arrays without a\n * second round trip. Built-in field projections are passthrough columns.\n * The companion alias uses a positional sentinel `__fg_t_<idx>` rather\n * than `<field>__t` to avoid colliding with a user-projected field\n * literally named `<x>__t`.\n *\n * Empty `select` is rejected at the compiler — the client wrapper enforces\n * this too. Duplicates collapse at compile time, preserving first-occurrence\n * order.\n */\nexport function compileFindEdgesProjected(\n table: string,\n select: ReadonlyArray<string>,\n filters: QueryFilter[],\n options?: QueryOptions,\n): { stmt: CompiledStatement; columns: ProjectedColumnSpec[] } {\n if (select.length === 0) {\n throw new FiregraphError(\n 'compileFindEdgesProjected requires a non-empty select list — ' +\n 'an empty projection has no SQL representation distinct from `findEdges`.',\n 'INVALID_QUERY',\n );\n }\n\n const seen = new Set<string>();\n const uniqueFields: string[] = [];\n for (const f of select) {\n if (!seen.has(f)) {\n seen.add(f);\n uniqueFields.push(f);\n }\n }\n\n const projections: string[] = [];\n const columns: ProjectedColumnSpec[] = [];\n for (let idx = 0; idx < uniqueFields.length; idx++) {\n const field = uniqueFields[idx]!;\n const canonical = normalizeProjectionField(field);\n const { expr } = compileFieldRef(canonical);\n // Alias is the caller-supplied field name verbatim — relaxed quoting\n // accepts dotted paths like `data.detail.region`. The decoder reads\n // back via `row[c.field]`, so the alias must equal the original\n // field string.\n const alias = quoteColumnAlias(field);\n projections.push(`${expr} AS ${alias}`);\n\n let kind: ProjectedColumnKind;\n let typeAliasName: string | undefined;\n if (canonical === 'data') {\n kind = 'data';\n } else if (canonical.startsWith('data.')) {\n kind = 'json';\n // Positional sentinel — `<field>__t` would collide if the caller\n // projected both `'foo'` and `'foo__t'` (both legal user input).\n typeAliasName = `__fg_t_${idx}`;\n const typeAlias = quoteColumnAlias(typeAliasName);\n projections.push(`json_type(\"data\", '$.${canonical.slice(5)}') AS ${typeAlias}`);\n } else {\n if (canonical === 'v') kind = 'builtin-int';\n else if (canonical === 'createdAt' || canonical === 'updatedAt') kind = 'builtin-timestamp';\n else kind = 'builtin-text';\n }\n columns.push({ field, kind, typeAlias: typeAliasName });\n }\n\n const params: unknown[] = [];\n const conditions: string[] = [];\n for (const f of filters) {\n conditions.push(compileFilter(f, params));\n }\n\n const where = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '';\n let sql = `SELECT ${projections.join(', ')} FROM ${quoteIdent(table)}${where}`;\n sql += compileOrderBy(options, params);\n sql += compileLimit(options, params);\n\n return { stmt: { sql, params }, columns };\n}\n\n/**\n * Decode one SQL row into the projected JS shape: built-in TEXT/INTEGER\n * columns pass through with light coercion, timestamps wrap in\n * `GraphTimestampImpl`, and `data.*` JSON projections use the paired\n * `json_type` column to recover JSON-encoded objects/arrays.\n */\nexport function decodeProjectedRow(\n row: Record<string, unknown>,\n columns: ProjectedColumnSpec[],\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const c of columns) {\n const raw = row[c.field];\n switch (c.kind) {\n case 'builtin-text':\n out[c.field] = raw === null || raw === undefined ? null : String(raw);\n break;\n case 'builtin-int':\n if (raw === null || raw === undefined) {\n out[c.field] = null;\n } else if (typeof raw === 'bigint') {\n out[c.field] = Number(raw);\n } else if (typeof raw === 'number') {\n out[c.field] = raw;\n } else {\n out[c.field] = Number(raw);\n }\n break;\n case 'builtin-timestamp': {\n const ms = rowTimestampToMillis(raw);\n out[c.field] = GraphTimestampImpl.fromMillis(ms) as unknown as GraphTimestamp;\n break;\n }\n case 'data':\n if (raw === null || raw === undefined || raw === '') {\n out[c.field] = {};\n } else {\n out[c.field] = JSON.parse(raw as string);\n }\n break;\n case 'json': {\n // Read the paired `json_type` companion column via the positional\n // sentinel recorded at compile time — see `ProjectedColumnSpec.typeAlias`.\n const t = row[c.typeAlias!] as string | null | undefined;\n if (raw === null || raw === undefined) {\n out[c.field] = null;\n } else if (t === 'object' || t === 'array') {\n out[c.field] = typeof raw === 'string' ? JSON.parse(raw) : raw;\n } else if (t === 'integer' && typeof raw === 'bigint') {\n out[c.field] = Number(raw);\n } else {\n out[c.field] = raw;\n }\n break;\n }\n }\n }\n return out;\n}\n\n/**\n * Compile an aggregate query.\n *\n * SUM/AVG/MIN/MAX cast the JSON-extracted value through\n * `CAST(... AS REAL)` for numeric semantics; without the cast,\n * comparisons would be lexicographic on the underlying string storage.\n *\n * The returned tuple includes the alias list so the JS-side caller can\n * rehydrate the result columns in spec order without reflecting on the\n * raw row keys (which the SQL layer doesn't guarantee a stable order for).\n */\nexport function compileAggregate(\n table: string,\n spec: AggregateSpec,\n filters: QueryFilter[],\n): { stmt: CompiledStatement; aliases: string[] } {\n const aliases = Object.keys(spec);\n if (aliases.length === 0) {\n throw new FiregraphError(\n 'aggregate() requires at least one aggregation in the `aggregates` map.',\n 'INVALID_QUERY',\n );\n }\n\n const projections: string[] = [];\n for (const alias of aliases) {\n const { op, field } = spec[alias];\n // Aliases are inlined into the SQL (SQL aliases can't be bound\n // parameters). Validate against the same JSON-path-key charset rule\n // used everywhere else so caller-supplied aliases can't inject SQL.\n validateJsonPathKey(alias, BACKEND_ERR_LABEL);\n if (op === 'count') {\n // Reject a stray field — see `AggregateField` JSDoc for rationale.\n if (field !== undefined) {\n throw new FiregraphError(\n `Aggregate '${alias}' op 'count' must not specify a field — ` +\n `count operates on rows, not a column expression.`,\n 'INVALID_QUERY',\n );\n }\n projections.push(`COUNT(*) AS ${quoteIdent(alias)}`);\n continue;\n }\n if (!field) {\n throw new FiregraphError(\n `Aggregate '${alias}' op '${op}' requires a field.`,\n 'INVALID_QUERY',\n );\n }\n const { expr } = compileFieldRef(field);\n const numeric = `CAST(${expr} AS REAL)`;\n if (op === 'sum') projections.push(`SUM(${numeric}) AS ${quoteIdent(alias)}`);\n else if (op === 'avg') projections.push(`AVG(${numeric}) AS ${quoteIdent(alias)}`);\n else if (op === 'min') projections.push(`MIN(${numeric}) AS ${quoteIdent(alias)}`);\n else if (op === 'max') projections.push(`MAX(${numeric}) AS ${quoteIdent(alias)}`);\n else\n throw new FiregraphError(\n `SQLite backend does not support aggregate op: ${String(op)}`,\n 'INVALID_QUERY',\n );\n }\n\n const params: unknown[] = [];\n const conditions: string[] = [];\n for (const f of filters) {\n conditions.push(compileFilter(f, params));\n }\n const where = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '';\n const sql = `SELECT ${projections.join(', ')} FROM ${quoteIdent(table)}${where}`;\n return { stmt: { sql, params }, aliases };\n}\n\n/**\n * Compile a `setDoc(record, mode)` call into a single statement.\n *\n * `mode === 'replace'` issues `INSERT OR REPLACE` (full row replacement).\n * `mode === 'merge'` issues `INSERT … ON CONFLICT(doc_id) DO UPDATE SET …`,\n * deep-merging the incoming `data` into the existing JSON via the chained\n * `json_set` / `json_remove` expression produced by `compileDataOpsExpr`.\n * Sibling keys at every depth survive; arrays are terminal (replaced).\n *\n * `created_at` is re-stamped on every put for both modes (matches the\n * cross-backend contract today).\n */\nexport function compileSet(\n table: string,\n docId: string,\n record: WritableRecord,\n nowMillis: number,\n mode: WriteMode,\n): CompiledStatement {\n // Eager validation so the first-insert path can't silently corrupt\n // Firestore special types or drop a DELETE_FIELD sentinel that\n // JSON.stringify would erase.\n assertJsonSafePayload(record.data, BACKEND_LABEL);\n if (mode === 'replace') {\n const sql = `INSERT OR REPLACE INTO ${quoteIdent(table)} (\n doc_id, a_type, a_uid, axb_type, b_type, b_uid, data, v, created_at, updated_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;\n const params: unknown[] = [\n docId,\n record.aType,\n record.aUid,\n record.axbType,\n record.bType,\n record.bUid,\n JSON.stringify(record.data ?? {}),\n record.v ?? null,\n nowMillis,\n nowMillis,\n ];\n return { sql, params };\n }\n\n const insertParams: unknown[] = [\n docId,\n record.aType,\n record.aUid,\n record.axbType,\n record.bType,\n record.bUid,\n JSON.stringify(record.data ?? {}),\n record.v ?? null,\n nowMillis,\n nowMillis,\n ];\n\n const ops = flattenPatch(record.data ?? {});\n const updateParams: unknown[] = [];\n const dataExpr =\n compileDataOpsExpr(ops, `COALESCE(\"data\", '{}')`, updateParams, BACKEND_ERR_LABEL) ??\n `COALESCE(\"data\", '{}')`;\n\n // COALESCE preserves pre-existing `v` when the incoming record has none,\n // matching Firestore's \"undefined leaves the stored field alone\" merge\n // semantic for the version stamp.\n const sql = `INSERT INTO ${quoteIdent(table)} (\n doc_id, a_type, a_uid, axb_type, b_type, b_uid, data, v, created_at, updated_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(doc_id) DO UPDATE SET\n \"a_type\" = excluded.\"a_type\",\n \"a_uid\" = excluded.\"a_uid\",\n \"axb_type\" = excluded.\"axb_type\",\n \"b_type\" = excluded.\"b_type\",\n \"b_uid\" = excluded.\"b_uid\",\n \"data\" = ${dataExpr},\n \"v\" = COALESCE(excluded.\"v\", \"v\"),\n \"created_at\" = excluded.\"created_at\",\n \"updated_at\" = excluded.\"updated_at\"`;\n\n return { sql, params: [...insertParams, ...updateParams] };\n}\n\n/**\n * Compile an `UpdatePayload` into a single UPDATE statement.\n *\n * - `replaceData` overwrites the whole `data` JSON.\n * - `dataOps` applies a deep-path patch via chained `json_remove` /\n * `json_set` — sibling keys at every depth survive; arrays are terminal;\n * delete-ops use `json_remove`.\n * - `v` is set when provided.\n * - `updated_at` is always stamped.\n */\nexport function compileUpdate(\n table: string,\n docId: string,\n update: UpdatePayload,\n nowMillis: number,\n): CompiledStatement {\n assertUpdatePayloadExclusive(update);\n const setClauses: string[] = [];\n const params: unknown[] = [];\n\n if (update.replaceData) {\n assertJsonSafePayload(update.replaceData, BACKEND_LABEL);\n setClauses.push(`\"data\" = ?`);\n params.push(JSON.stringify(update.replaceData));\n } else if (update.dataOps && update.dataOps.length > 0) {\n for (const op of update.dataOps) {\n if (!op.delete) assertJsonSafePayload(op.value, BACKEND_LABEL);\n }\n const expr = compileDataOpsExpr(\n update.dataOps,\n `COALESCE(\"data\", '{}')`,\n params,\n BACKEND_ERR_LABEL,\n );\n if (expr !== null) {\n setClauses.push(`\"data\" = ${expr}`);\n }\n }\n\n if (update.v !== undefined) {\n setClauses.push(`\"v\" = ?`);\n params.push(update.v);\n }\n\n setClauses.push(`\"updated_at\" = ?`);\n params.push(nowMillis);\n\n params.push(docId);\n\n return {\n sql: `UPDATE ${quoteIdent(table)} SET ${setClauses.join(', ')} WHERE \"doc_id\" = ?`,\n params,\n };\n}\n\nexport function compileDelete(table: string, docId: string): CompiledStatement {\n return {\n sql: `DELETE FROM ${quoteIdent(table)} WHERE \"doc_id\" = ?`,\n params: [docId],\n };\n}\n\n/**\n * Compile a server-side bulk DELETE.\n *\n * The compiler accepts an empty filter list (would emit\n * `DELETE FROM <table>`); callers that must never wipe a whole graph\n * reject that shape at their own boundary (the DO RPC layer does, as\n * defense-in-depth against a misconfigured stub).\n */\nexport function compileBulkDelete(table: string, filters: QueryFilter[]): CompiledStatement {\n const params: unknown[] = [];\n const conditions: string[] = [];\n for (const f of filters) {\n conditions.push(compileFilter(f, params));\n }\n const where = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '';\n return {\n sql: `DELETE FROM ${quoteIdent(table)}${where}`,\n params,\n };\n}\n\n/**\n * Compile a server-side bulk UPDATE.\n *\n * The `patch.data` payload is deep-merged into each matching row's `data`\n * field via the same `flattenPatch` → `compileDataOpsExpr` pipeline that\n * `compileUpdate` (single-row) uses. Identifying columns are read-only.\n *\n * Empty patches are rejected — an empty patch would only rewrite\n * `updated_at`, which is almost certainly a caller bug.\n */\nexport function compileBulkUpdate(\n table: string,\n filters: QueryFilter[],\n patchData: Record<string, unknown>,\n nowMillis: number,\n): CompiledStatement {\n const dataOps = flattenPatch(patchData);\n if (dataOps.length === 0) {\n throw new FiregraphError(\n 'bulkUpdate() patch.data must contain at least one leaf — an empty patch ' +\n 'would only rewrite `updated_at`, which is almost certainly a bug. ' +\n 'Use `setDoc` with merge mode if you want to stamp without editing data.',\n 'INVALID_QUERY',\n );\n }\n for (const op of dataOps) {\n if (!op.delete) assertJsonSafePayload(op.value, BACKEND_LABEL);\n }\n const setParams: unknown[] = [];\n const expr = compileDataOpsExpr(dataOps, `COALESCE(\"data\", '{}')`, setParams, BACKEND_ERR_LABEL);\n if (expr === null) {\n throw new FiregraphError(\n 'bulkUpdate() patch produced no SQL operations — internal invariant violated.',\n 'INVALID_ARGUMENT',\n );\n }\n const setClauses: string[] = [`\"data\" = ${expr}`, `\"updated_at\" = ?`];\n setParams.push(nowMillis);\n\n const whereParams: unknown[] = [];\n const conditions: string[] = [];\n for (const f of filters) {\n conditions.push(compileFilter(f, whereParams));\n }\n const where = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '';\n\n return {\n sql: `UPDATE ${quoteIdent(table)} SET ${setClauses.join(', ')}${where}`,\n params: [...setParams, ...whereParams],\n };\n}\n\n/**\n * DELETE every row in the table. Used when tearing down an entire graph —\n * the caller discovers the set of graphs to wipe (registry topology for the\n * DO backend, the graph catalog for the shared SQLite backend) and clears\n * each one.\n */\nexport function compileDeleteAll(table: string): CompiledStatement {\n return {\n sql: `DELETE FROM ${quoteIdent(table)}`,\n params: [],\n };\n}\n\n// ---------------------------------------------------------------------------\n// Row -> record\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a SQLite row into a full `StoredGraphRecord`, wrapping the\n * millisecond timestamp columns in `GraphTimestampImpl`.\n */\nexport function rowToRecord(row: Record<string, unknown>): StoredGraphRecord {\n const dataString = row.data as string | null;\n const data = dataString ? (JSON.parse(dataString) as Record<string, unknown>) : {};\n\n const createdMs = rowTimestampToMillis(row.created_at);\n const updatedMs = rowTimestampToMillis(row.updated_at);\n\n const record: Record<string, unknown> = {\n aType: row.a_type as string,\n aUid: row.a_uid as string,\n axbType: row.axb_type as string,\n bType: row.b_type as string,\n bUid: row.b_uid as string,\n data,\n createdAt: GraphTimestampImpl.fromMillis(createdMs) as unknown as GraphTimestamp,\n updatedAt: GraphTimestampImpl.fromMillis(updatedMs) as unknown as GraphTimestamp,\n };\n\n if (row.v !== null && row.v !== undefined) {\n record.v = Number(row.v);\n }\n return record as unknown as StoredGraphRecord;\n}\n\n/**\n * Coerce a timestamp column value to a plain millis number. The schema types\n * `created_at` / `updated_at` as `INTEGER NOT NULL` so the column should\n * always arrive as a number (or possibly a bigint on some SQLite bindings),\n * but a string row value from SQLite (e.g. BigInt.toString fallback) is also\n * accepted. Anything else indicates a corrupt row — throw loudly rather than\n * silently returning 0, which would quietly mask the bug on every read.\n */\nexport function rowTimestampToMillis(value: unknown): number {\n if (typeof value === 'number') return value;\n if (typeof value === 'bigint') return Number(value);\n if (typeof value === 'string') {\n const n = Number(value);\n if (Number.isFinite(n)) return n;\n }\n throw new FiregraphError(\n `SQLite row has non-numeric timestamp column: ${typeof value} (${String(value)})`,\n 'INVALID_QUERY',\n );\n}\n","/**\n * Search compilation for the local SQLite backend (`firegraph/sqlite-local`).\n *\n * Two capabilities are compiled here:\n *\n * - **`search.fullText`** — an FTS5 index table per graph table, kept in\n * sync by pure-SQL triggers. Text is extracted from the `data` JSON via\n * `json_tree(...) WHERE type = 'text'`, so the triggers work from ANY\n * connection or process touching the file — no user-defined function\n * required on the write path. Queries rank with `bm25()` (lower =\n * better, so `ORDER BY bm25 ASC` is relevance-descending).\n *\n * - **`search.vector`** — brute-force k-NN via a deterministic scalar UDF\n * (`firegraph_vector_distance`) registered on the better-sqlite3\n * connection by `createLocalSqliteBackend`. There is no ANN index; the\n * engine evaluates the distance per candidate row, which is the right\n * trade-off for the local-file use case (thousands to low millions of\n * rows, zero infrastructure). UDFs are connection-local: vector search\n * only works through a connection that registered the function.\n *\n * ## FTS row keying\n *\n * The FTS5 table's `rowid` is keyed through a dedicated mapping table\n * (`<t>_fts_map`, `INTEGER PRIMARY KEY AUTOINCREMENT` → `doc_id`) rather\n * than the graph table's own rowid. The graph table has a TEXT primary key,\n * so its raw rowids are NOT stable — `VACUUM` may renumber them, silently\n * detaching every FTS entry. AUTOINCREMENT ids survive VACUUM. Storing\n * `doc_id` UNINDEXED inside the FTS table was also rejected: FTS5 can't\n * index UNINDEXED columns, making the per-write delete a full scan.\n *\n * Validation parity: error messages and codes mirror the Firestore helpers\n * (`firestore-vector.ts` / `firestore-fulltext.ts`) so a caller migrating\n * between backends sees the same failures. This module must stay free of\n * `@google-cloud/firestore` imports — it is bundled into the\n * `firegraph/sqlite-local` entry.\n */\n\nimport { FiregraphError } from '../errors.js';\nimport type { FindNearestParams, FullTextSearchParams, QueryFilter } from '../types.js';\nimport { validateJsonPathKey } from './sqlite-data-ops.js';\nimport { quoteIdent } from './sqlite-schema.js';\nimport type { CompiledStatement } from './sqlite-sql.js';\nimport { compileFilterConditions } from './sqlite-sql.js';\n\n/** Name of the connection-local vector-distance UDF. */\nexport const VECTOR_DISTANCE_UDF = 'firegraph_vector_distance';\n\n/** Column alias carrying the computed distance through the vector query. */\nexport const DISTANCE_ALIAS = '__fg_distance';\n\nconst BACKEND_ERR_LABEL = 'SQLite backend';\n\n/**\n * Built-in envelope fields that must NOT be passed as search field paths.\n * Mirrors the Firestore helpers' rejection list.\n */\nconst ENVELOPE_FIELDS: ReadonlySet<string> = new Set([\n 'aType',\n 'aUid',\n 'axbType',\n 'bType',\n 'bUid',\n 'createdAt',\n 'updatedAt',\n 'v',\n]);\n\n/** FTS5 index table for a graph table. */\nexport function ftsTableName(table: string): string {\n return `${table}_fts`;\n}\n\n/** Stable-rowid mapping table for a graph table's FTS index. */\nexport function ftsMapTableName(table: string): string {\n return `${table}_fts_map`;\n}\n\n/**\n * SQL fragment extracting every string value in a `data` JSON payload as\n * one space-joined text blob. Pure SQL (`json_tree`), so it is evaluatable\n * inside triggers from any connection.\n */\nfunction textExtractionExpr(dataRef: string): string {\n return (\n `(SELECT coalesce(group_concat(\"value\", ' '), '') ` +\n `FROM json_tree(coalesce(${dataRef}, '{}')) WHERE \"type\" = 'text')`\n );\n}\n\n/**\n * DDL installing the FTS5 infrastructure for one graph table: the mapping\n * table, the FTS5 virtual table, and three sync triggers. All statements\n * are `IF NOT EXISTS` — safe to re-run on every bootstrap.\n *\n * The AFTER INSERT trigger also fires for the INSERT arm of the backend's\n * upsert (`INSERT … ON CONFLICT DO UPDATE`); the conflict arm fires AFTER\n * UPDATE. Both re-derive the indexed text from `new.\"data\"`, and both\n * start with a defensive delete of any stale FTS row so replayed writes\n * never double-index.\n */\nexport function buildFtsDDL(table: string): string[] {\n const t = quoteIdent(table);\n const fts = quoteIdent(ftsTableName(table));\n const map = quoteIdent(ftsMapTableName(table));\n const mappedId = `(SELECT \"id\" FROM ${map} WHERE \"doc_id\" = new.\"doc_id\")`;\n // The map insert must be conflict-free rather than `INSERT OR IGNORE`:\n // when the outer statement is the backend's upsert (`INSERT … ON CONFLICT\n // DO UPDATE`), SQLite replaces conflict handling inside trigger programs\n // with the outer statement's algorithm, turning the IGNORE into an abort.\n const reindexBody =\n ` INSERT INTO ${map} (\"doc_id\") SELECT new.\"doc_id\" ` +\n `WHERE NOT EXISTS (SELECT 1 FROM ${map} WHERE \"doc_id\" = new.\"doc_id\");\\n` +\n ` DELETE FROM ${fts} WHERE rowid = ${mappedId};\\n` +\n ` INSERT INTO ${fts} (rowid, \"text\") VALUES (${mappedId}, ${textExtractionExpr('new.\"data\"')});\\n`;\n return [\n `CREATE TABLE IF NOT EXISTS ${map} (\n \"id\" INTEGER PRIMARY KEY AUTOINCREMENT,\n \"doc_id\" TEXT NOT NULL UNIQUE\n )`,\n `CREATE VIRTUAL TABLE IF NOT EXISTS ${fts} USING fts5(\"text\")`,\n `CREATE TRIGGER IF NOT EXISTS ${quoteIdent(`${table}_fts_ai`)} AFTER INSERT ON ${t} BEGIN\\n${reindexBody}END`,\n `CREATE TRIGGER IF NOT EXISTS ${quoteIdent(`${table}_fts_au`)} AFTER UPDATE ON ${t} BEGIN\\n${reindexBody}END`,\n `CREATE TRIGGER IF NOT EXISTS ${quoteIdent(`${table}_fts_ad`)} AFTER DELETE ON ${t} BEGIN\n DELETE FROM ${fts} WHERE rowid = (SELECT \"id\" FROM ${map} WHERE \"doc_id\" = old.\"doc_id\");\n DELETE FROM ${map} WHERE \"doc_id\" = old.\"doc_id\";\nEND`,\n ];\n}\n\n/**\n * Idempotent reconciliation statements run at every schema bootstrap,\n * after `buildFtsDDL`:\n *\n * 1–2. Purge FTS/map rows whose `doc_id` no longer exists in the graph\n * table. Covers the recreate-after-cascade path: a parent cascade\n * DROPs the graph table (taking the triggers with it) but leaves\n * the FTS artifacts; without the purge, a recreated subgraph would\n * surface ghost matches and hit UNIQUE violations on the map.\n * 3–4. Backfill map/FTS rows for graph rows that predate the FTS\n * infrastructure (e.g. a database written by an older firegraph).\n */\nexport function buildFtsSyncStatements(table: string): string[] {\n const t = quoteIdent(table);\n const fts = quoteIdent(ftsTableName(table));\n const map = quoteIdent(ftsMapTableName(table));\n return [\n `DELETE FROM ${fts} WHERE rowid IN (\n SELECT m.\"id\" FROM ${map} m LEFT JOIN ${t} t ON t.\"doc_id\" = m.\"doc_id\"\n WHERE t.\"doc_id\" IS NULL\n )`,\n `DELETE FROM ${map} WHERE \"doc_id\" NOT IN (SELECT \"doc_id\" FROM ${t})`,\n `INSERT OR IGNORE INTO ${map} (\"doc_id\") SELECT \"doc_id\" FROM ${t}`,\n `INSERT INTO ${fts} (rowid, \"text\")\n SELECT m.\"id\", ${textExtractionExpr('t.\"data\"')}\n FROM ${t} t JOIN ${map} m ON m.\"doc_id\" = t.\"doc_id\"\n WHERE m.\"id\" NOT IN (SELECT rowid FROM ${fts})`,\n ];\n}\n\n/**\n * Full `extraTableDDL` payload for `firegraph/sqlite-local`: FTS\n * infrastructure plus the reconciliation pass.\n */\nexport function buildLocalSearchDDL(table: string): string[] {\n return [...buildFtsDDL(table), ...buildFtsSyncStatements(table)];\n}\n\n/**\n * Normalise a caller-supplied vector / distance-result field path. Bare\n * names rewrite to `data.<name>`; `'data'` and `'data.*'` pass through;\n * envelope fields are rejected. Same contract and message shape as\n * `normalizeVectorFieldPath` in `firestore-vector.ts`.\n */\nexport function normalizeVectorFieldPath(label: string, field: string): string {\n if (ENVELOPE_FIELDS.has(field)) {\n throw new FiregraphError(\n `findNearest(): ${label} '${field}' is a built-in envelope field — ` +\n `vectors must live under \\`data.*\\`. Use a path like 'data.${field}' ` +\n `if you really meant a nested data field.`,\n 'INVALID_QUERY',\n );\n }\n if (field === 'data' || field.startsWith('data.')) return field;\n return `data.${field}`;\n}\n\n/**\n * Normalise a caller-supplied FTS field path. Same contract as\n * `normalizeFullTextFieldPath` in `firestore-fulltext.ts`.\n */\nexport function normalizeFullTextFieldPath(field: string): string {\n if (ENVELOPE_FIELDS.has(field)) {\n throw new FiregraphError(\n `fullTextSearch(): field '${field}' is a built-in envelope field — ` +\n `text-indexed fields must live under \\`data.*\\`. Use a path like ` +\n `'data.${field}' if you really meant a nested data field.`,\n 'INVALID_QUERY',\n );\n }\n if (field === 'data' || field.startsWith('data.')) return field;\n return `data.${field}`;\n}\n\n/**\n * Identifying filters (`aType` / `axbType` / `bType`) plus optional `where`.\n * Bare `where` field names rewrite to `data.<name>` — the same convention\n * `buildEdgeQueryPlan` applies for `findEdges({ where })`.\n */\nfunction buildSearchFilters(params: {\n aType?: string;\n axbType?: string;\n bType?: string;\n where?: QueryFilter[];\n}): QueryFilter[] {\n const filters: QueryFilter[] = [];\n if (params.aType) filters.push({ field: 'aType', op: '==', value: params.aType });\n if (params.axbType) filters.push({ field: 'axbType', op: '==', value: params.axbType });\n if (params.bType) filters.push({ field: 'bType', op: '==', value: params.bType });\n for (const clause of params.where ?? []) {\n const field =\n ENVELOPE_FIELDS.has(clause.field) || clause.field.startsWith('data.')\n ? clause.field\n : `data.${clause.field}`;\n filters.push({ field, op: clause.op, value: clause.value });\n }\n return filters;\n}\n\n/**\n * Compile a `fullTextSearch()` call into one SELECT over the FTS5 index.\n *\n * Validation parity with `runFirestoreFullTextSearch`: non-empty string\n * query, positive integer limit, and a non-empty `fields` list is rejected\n * with `INVALID_QUERY` (\"not yet supported\") — FTS5 column filters could\n * support per-field search later, but the single-blob index built today\n * has one `text` column, so the option is reserved rather than silently\n * mis-honoured.\n *\n * Results order by `bm25()` ascending (best match first), with `doc_id`\n * as a deterministic tie-break.\n */\nexport function compileFullTextSearch(\n table: string,\n params: FullTextSearchParams,\n): CompiledStatement {\n if (typeof params.query !== 'string' || params.query.length === 0) {\n throw new FiregraphError(\n 'fullTextSearch(): query must be a non-empty string.',\n 'INVALID_QUERY',\n );\n }\n if (!Number.isInteger(params.limit) || params.limit <= 0) {\n throw new FiregraphError(\n `fullTextSearch(): limit must be a positive integer (got ${params.limit}).`,\n 'INVALID_QUERY',\n );\n }\n const normalizedFields = params.fields?.map((f) => normalizeFullTextFieldPath(f));\n if (normalizedFields !== undefined && normalizedFields.length > 0) {\n throw new FiregraphError(\n 'fullTextSearch(): the `fields` option is not yet supported — ' +\n 'the local SQLite FTS index stores one combined text column per record. ' +\n 'Omit `fields` to search all string values.',\n 'INVALID_QUERY',\n );\n }\n\n const t = quoteIdent(table);\n const fts = quoteIdent(ftsTableName(table));\n const map = quoteIdent(ftsMapTableName(table));\n\n const sqlParams: unknown[] = [params.query];\n const conditions: string[] = [`${fts} MATCH ?`];\n conditions.push(...compileFilterConditions(buildSearchFilters(params), sqlParams));\n sqlParams.push(params.limit);\n\n const sql =\n `SELECT ${t}.* FROM ${fts} ` +\n `JOIN ${map} ON ${map}.\"id\" = ${fts}.rowid ` +\n `JOIN ${t} ON ${t}.\"doc_id\" = ${map}.\"doc_id\" ` +\n `WHERE ${conditions.join(' AND ')} ` +\n `ORDER BY bm25(${fts}) ASC, ${t}.\"doc_id\" ASC LIMIT ?`;\n return { sql, params: sqlParams };\n}\n\n/**\n * Substrings that identify a malformed-FTS5-query failure raised by the\n * FTS5 MATCH parser at *query* time.\n *\n * FTS5 reports a bad MATCH expression as a generic `SQLITE_ERROR` (the same\n * code used for ordinary SQL logic errors), not as a distinct error code, so\n * a raw better-sqlite3 `SqliteError` would otherwise escape `fullTextSearch()`\n * instead of the documented `INVALID_QUERY` (the Firestore-parity contract).\n * Matching the parser's specific complaints lets us translate query-syntax\n * failures while leaving genuine storage failures (disk I/O, corruption, lock\n * contention, a non-healable `no such table`) — which carry different messages\n * / codes — to propagate unchanged.\n *\n * Observed shapes (`code === 'SQLITE_ERROR'` for all):\n * - `\"unclosed phrase (((` → `\"unterminated string\"` (unclosed quote)\n * - `AND AND` → `\"fts5: syntax error near ...\"` (grammar error)\n * - `* leading` → `\"unknown special query: ...\"` (bad directive)\n * - `col: bar` → `\"no such column: col\"` (column filter)\n *\n * `no such column` is safe to treat as a query error here: every column the\n * compiled statement references is a fixed, real column, so the only runtime\n * source of that message is an FTS5 `col:term` filter inside the user's MATCH\n * expression — it can never originate from a genuine missing column. A\n * `no such table` miss is distinct (\"table\", not \"column\") and is handled\n * upstream by the self-heal retry, so it never reaches this matcher's scope.\n */\nconst FTS5_QUERY_ERROR_SIGNATURES: readonly string[] = [\n 'fts5: syntax error',\n 'unterminated string',\n 'unknown special query',\n 'no such column',\n];\n\n/**\n * True when `message` is the FTS5 MATCH parser rejecting a malformed query\n * string — the failures `fullTextSearch()` must surface as `INVALID_QUERY`\n * rather than as a raw driver error. See `FTS5_QUERY_ERROR_SIGNATURES`.\n */\nexport function isFts5QueryError(message: string): boolean {\n const lower = message.toLowerCase();\n return FTS5_QUERY_ERROR_SIGNATURES.some((sig) => lower.includes(sig));\n}\n\nconst DISTANCE_MEASURES: ReadonlySet<string> = new Set(['EUCLIDEAN', 'COSINE', 'DOT_PRODUCT']);\n\nexport interface CompiledVectorQuery {\n stmt: CompiledStatement;\n /**\n * `data`-relative path segments to write the computed distance into on\n * each result record, or `null` when `distanceResultField` was not set.\n */\n distancePath: string[] | null;\n}\n\n/** Resolve a `queryVector` argument to a plain `number[]`. */\nfunction toNumberArray(qv: number[] | { toArray(): number[] }): number[] {\n if (Array.isArray(qv)) return qv;\n if (typeof (qv as { toArray?: unknown }).toArray === 'function') {\n return (qv as { toArray(): number[] }).toArray();\n }\n throw new FiregraphError(\n 'findNearest(): queryVector must be a number[] or a Firestore VectorValue.',\n 'INVALID_QUERY',\n );\n}\n\n/**\n * Compile a `findNearest()` call into one SELECT that scores every\n * candidate row via the `firegraph_vector_distance` UDF.\n *\n * Shape (subquery because SQLite forbids referencing a SELECT alias in\n * the same level's WHERE):\n *\n * SELECT * FROM (\n * SELECT *, firegraph_vector_distance(json_extract(\"data\", '$.<path>'), ?, ?) AS \"__fg_distance\"\n * FROM \"<t>\" [WHERE <identifiers + where>]\n * ) WHERE \"__fg_distance\" IS NOT NULL [AND \"__fg_distance\" <=|>= ?]\n * ORDER BY \"__fg_distance\" ASC|DESC, \"doc_id\" ASC LIMIT ?\n *\n * `NULL` distances (missing field, non-array value, dimension mismatch)\n * drop out of the result, mirroring Firestore's behaviour of silently\n * skipping non-conforming documents. Threshold and ordering semantics\n * follow the `FindNearestParams.distanceThreshold` contract: `<=` /\n * ascending for EUCLIDEAN and COSINE, `>=` / descending for DOT_PRODUCT.\n *\n * Validation parity with `runFirestoreFindNearest`: envelope-field\n * rejection on both field params, non-empty query vector, positive\n * integer limit ≤ 1000.\n */\nexport function compileFindNearest(table: string, params: FindNearestParams): CompiledVectorQuery {\n const vec = toNumberArray(params.queryVector);\n if (vec.length === 0) {\n throw new FiregraphError(\n 'findNearest(): queryVector is empty — at least one dimension is required.',\n 'INVALID_QUERY',\n );\n }\n if (!Number.isInteger(params.limit) || params.limit <= 0 || params.limit > 1000) {\n throw new FiregraphError(\n `findNearest(): limit must be a positive integer ≤ 1000 (got ${params.limit}).`,\n 'INVALID_QUERY',\n );\n }\n if (!DISTANCE_MEASURES.has(params.distanceMeasure)) {\n throw new FiregraphError(\n `findNearest(): unknown distanceMeasure '${String(params.distanceMeasure)}' — ` +\n `expected EUCLIDEAN, COSINE, or DOT_PRODUCT.`,\n 'INVALID_QUERY',\n );\n }\n\n const vectorField = normalizeVectorFieldPath('vectorField', params.vectorField);\n let vectorExpr: string;\n if (vectorField === 'data') {\n vectorExpr = '\"data\"';\n } else {\n const suffix = vectorField.slice('data.'.length);\n for (const part of suffix.split('.')) {\n validateJsonPathKey(part, BACKEND_ERR_LABEL);\n }\n vectorExpr = `json_extract(\"data\", '$.${suffix}')`;\n }\n\n let distancePath: string[] | null = null;\n if (params.distanceResultField !== undefined) {\n const normalized = normalizeVectorFieldPath('distanceResultField', params.distanceResultField);\n if (normalized === 'data') {\n throw new FiregraphError(\n `findNearest(): distanceResultField 'data' would replace the entire data ` +\n `payload — use a nested path like 'data.distance'.`,\n 'INVALID_QUERY',\n );\n }\n distancePath = normalized.slice('data.'.length).split('.');\n for (const part of distancePath) {\n validateJsonPathKey(part, BACKEND_ERR_LABEL);\n }\n }\n\n // Bound-parameter order tracks placeholder order in the statement text:\n // the two UDF arguments in the SELECT list come first, then the inner\n // WHERE filters, then threshold and limit.\n const sqlParams: unknown[] = [JSON.stringify(vec), params.distanceMeasure];\n const conditions = compileFilterConditions(buildSearchFilters(params), sqlParams);\n const innerWhere = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : '';\n const dist = quoteIdent(DISTANCE_ALIAS);\n const descending = params.distanceMeasure === 'DOT_PRODUCT';\n\n let sql =\n `SELECT * FROM (` +\n `SELECT *, ${VECTOR_DISTANCE_UDF}(${vectorExpr}, ?, ?) AS ${dist} ` +\n `FROM ${quoteIdent(table)}${innerWhere}` +\n `) WHERE ${dist} IS NOT NULL`;\n if (params.distanceThreshold !== undefined) {\n sql += ` AND ${dist} ${descending ? '>=' : '<='} ?`;\n sqlParams.push(params.distanceThreshold);\n }\n sql += ` ORDER BY ${dist} ${descending ? 'DESC' : 'ASC'}, \"doc_id\" ASC LIMIT ?`;\n sqlParams.push(params.limit);\n\n return { stmt: { sql, params: sqlParams }, distancePath };\n}\n\n// One-entry memo for the parsed query vector: the UDF runs once per\n// candidate row with the identical query-vector JSON, so re-parsing it\n// every call would dominate the scan cost.\nlet memoQueryJson: string | null = null;\nlet memoQueryVec: number[] | null = null;\n\n/**\n * Scalar UDF body for `firegraph_vector_distance(storedJson, queryJson,\n * measure)`. Returns the distance as a REAL, or `null` when the stored\n * value is missing, not a JSON array, dimension-mismatched, or contains\n * non-finite/non-numeric entries — NULL rows are filtered out by the\n * query, mirroring Firestore's silent skip of non-conforming documents.\n *\n * COSINE returns `1 − cos(a, b)` (Firestore's distance convention) and\n * `null` when either vector has zero norm (cosine undefined).\n *\n * Exported for direct unit testing and registered on the connection by\n * `createLocalSqliteBackend` with `deterministic: true`.\n */\nexport function computeVectorDistance(\n storedJson: unknown,\n queryJson: unknown,\n measure: unknown,\n): number | null {\n if (\n typeof storedJson !== 'string' ||\n typeof queryJson !== 'string' ||\n typeof measure !== 'string'\n ) {\n return null;\n }\n let query: number[];\n if (memoQueryJson === queryJson && memoQueryVec !== null) {\n query = memoQueryVec;\n } else {\n let parsed: unknown;\n try {\n parsed = JSON.parse(queryJson);\n } catch {\n return null;\n }\n if (!Array.isArray(parsed)) return null;\n query = parsed as number[];\n memoQueryJson = queryJson;\n memoQueryVec = query;\n }\n\n let stored: unknown;\n try {\n stored = JSON.parse(storedJson);\n } catch {\n return null;\n }\n if (!Array.isArray(stored) || stored.length !== query.length) return null;\n\n let dot = 0;\n let sumSq = 0;\n let normStored = 0;\n let normQuery = 0;\n for (let i = 0; i < query.length; i++) {\n const a = stored[i];\n const b = query[i];\n if (typeof a !== 'number' || !Number.isFinite(a)) return null;\n if (typeof b !== 'number' || !Number.isFinite(b)) return null;\n dot += a * b;\n const diff = a - b;\n sumSq += diff * diff;\n normStored += a * a;\n normQuery += b * b;\n }\n\n let result: number;\n switch (measure) {\n case 'EUCLIDEAN':\n result = Math.sqrt(sumSq);\n break;\n case 'COSINE': {\n const denom = Math.sqrt(normStored) * Math.sqrt(normQuery);\n if (denom === 0) return null;\n result = 1 - dot / denom;\n break;\n }\n case 'DOT_PRODUCT':\n result = dot;\n break;\n default:\n return null;\n }\n return Number.isFinite(result) ? result : null;\n}\n\n/**\n * Set a nested value inside a record's `data` payload, creating\n * intermediate objects along the way (replacing non-object intermediates,\n * matching Firestore's `distanceResultField` write semantics).\n */\nexport function setDataPath(\n data: Record<string, unknown>,\n path: ReadonlyArray<string>,\n value: unknown,\n): void {\n let cursor = data;\n for (let i = 0; i < path.length - 1; i++) {\n const key = path[i];\n const next = cursor[key];\n if (typeof next !== 'object' || next === null || Array.isArray(next)) {\n const created: Record<string, unknown> = {};\n cursor[key] = created;\n cursor = created;\n } else {\n cursor = next as Record<string, unknown>;\n }\n }\n cursor[path[path.length - 1]] = value;\n}\n\n/**\n * Identify orphaned FTS artifacts (`<t>_fts` / `<t>_fts_map`) whose base\n * graph table no longer exists — left behind when a parent cascade DROPs a\n * descendant subgraph table (triggers die with the table; the FTS\n * artifacts do not).\n *\n * Safety against false positives: only names under the subgraph prefix\n * (`<rootTable>_g_`) are considered, a candidate must NOT itself be a\n * registered graph table (`catalogTables` — covers a real graph whose\n * mangled scope happens to end in `_fts`), and its base table must be\n * absent from `allTables`. FTS5 shadow tables (`<t>_fts_data`,\n * `<t>_fts_idx`, …) never match the suffix patterns and are dropped\n * implicitly with their parent virtual table.\n */\nexport function findOrphanedFtsTables(\n allTables: ReadonlyArray<string>,\n catalogTables: ReadonlyArray<string>,\n rootTable: string,\n): string[] {\n const names = new Set(allTables);\n const liveGraphTables = new Set(catalogTables);\n const subgraphPrefix = `${rootTable}_g_`;\n const orphans: string[] = [];\n for (const name of names) {\n let base: string | null = null;\n if (name.endsWith('_fts_map')) base = name.slice(0, -'_fts_map'.length);\n else if (name.endsWith('_fts')) base = name.slice(0, -'_fts'.length);\n if (base === null || !base.startsWith(subgraphPrefix)) continue;\n if (liveGraphTables.has(name)) continue;\n if (names.has(base)) continue;\n orphans.push(name);\n }\n return orphans.sort();\n}\n","import { createHash } from 'node:crypto';\n\nimport { SHARD_SEPARATOR } from './internal/constants.js';\n\nexport function computeNodeDocId(uid: string): string {\n return uid;\n}\n\nexport function computeEdgeDocId(aUid: string, axbType: string, bUid: string): string {\n const composite = `${aUid}${SHARD_SEPARATOR}${axbType}${SHARD_SEPARATOR}${bUid}`;\n const hash = createHash('sha256').update(composite).digest('hex');\n const shard = hash[0];\n return `${shard}${SHARD_SEPARATOR}${aUid}${SHARD_SEPARATOR}${axbType}${SHARD_SEPARATOR}${bUid}`;\n}\n","/**\n * Graph catalog for the table-per-graph SQLite edition.\n *\n * Each graph (the root, and every subgraph reached via chained\n * `subgraph()` calls) lives in its own SQLite table. The catalog is a\n * small bookkeeping table — `<rootTable>_graphs` — mapping each graph's\n * storage scope (the interleaved `parentUid/name` path that uniquely\n * identifies a subgraph position) to its physical table name and its\n * logical scope path (chained subgraph names, used for `allowedIn`\n * matching).\n *\n * The catalog is what makes registry-free cascade possible: deleting a\n * node with subgraphs prefix-matches descendant storage scopes in the\n * catalog and drops each listed table, with no need to know the subgraph\n * topology from a schema registry.\n */\n\nimport { quoteIdent, validateTableName } from '../internal/sqlite-schema.js';\nimport type { CompiledStatement } from '../internal/sqlite-sql.js';\n\n/** Name of the catalog table that tracks every graph under `rootTable`. */\nexport function catalogTableName(rootTable: string): string {\n validateTableName(rootTable);\n return `${rootTable}_graphs`;\n}\n\n/**\n * Deterministically mangle a storage scope into a SQL-identifier-safe\n * table-name suffix. The encoding is injective so two distinct scopes can\n * never collide on the same table:\n *\n * - `[A-Za-z0-9]` pass through\n * - `_` → `__` (escape char, doubled)\n * - `-` → `_h` (nanoid alphabet includes `-`)\n * - `/` → `_s` (scope segment separator)\n * - anything else → `_u<hex codepoint>_`\n *\n * Every escape sequence starts with `_` and no passthrough character is\n * `_`, so decoding is unambiguous (not that we ever decode — the catalog\n * stores the original scope alongside the table name).\n */\nexport function mangleStorageScope(scope: string): string {\n let out = '';\n for (const ch of scope) {\n if (/[A-Za-z0-9]/.test(ch)) out += ch;\n else if (ch === '_') out += '__';\n else if (ch === '-') out += '_h';\n else if (ch === '/') out += '_s';\n else out += `_u${ch.codePointAt(0)!.toString(16)}_`;\n }\n return out;\n}\n\n/**\n * Resolve the physical table for a graph position. The root graph\n * (`storageScope === ''`) uses `rootTable` itself; subgraphs get\n * `<rootTable>_g_<mangled scope>`.\n *\n * The `_g_` infix keeps subgraph tables disjoint from both the root table\n * and the catalog (`<rootTable>_graphs` — `_graphs` is never `_g_<x>`).\n */\nexport function tableForScope(rootTable: string, storageScope: string): string {\n validateTableName(rootTable);\n if (storageScope === '') return rootTable;\n return `${rootTable}_g_${mangleStorageScope(storageScope)}`;\n}\n\n/**\n * Escape LIKE wildcards in a literal prefix. The nanoid alphabet includes\n * `_` (a single-char LIKE wildcard), so prefix matches against storage\n * scopes MUST escape and carry an `ESCAPE '\\'` clause — otherwise a scope\n * containing `_` would match unrelated siblings.\n */\nexport function escapeLikePrefix(prefix: string): string {\n return prefix.replace(/[\\\\%_]/g, (c) => `\\\\${c}`);\n}\n\n/** DDL for the catalog table. Idempotent. */\nexport function buildCatalogDDL(rootTable: string): string {\n const t = quoteIdent(catalogTableName(rootTable));\n return `CREATE TABLE IF NOT EXISTS ${t} (\n storage_scope TEXT NOT NULL PRIMARY KEY,\n table_name TEXT NOT NULL UNIQUE,\n scope_path TEXT NOT NULL\n )`;\n}\n\n/** Register a graph in the catalog. Idempotent (`INSERT OR IGNORE`). */\nexport function compileCatalogRegister(\n rootTable: string,\n storageScope: string,\n tableName: string,\n scopePath: string,\n): CompiledStatement {\n const t = quoteIdent(catalogTableName(rootTable));\n return {\n sql: `INSERT OR IGNORE INTO ${t} (storage_scope, table_name, scope_path) VALUES (?, ?, ?)`,\n params: [storageScope, tableName, scopePath],\n };\n}\n\n/**\n * List every descendant graph whose storage scope starts with\n * `scopePrefix + '/'`. Used by cascade delete to discover which tables to\n * drop. Ordered by scope for deterministic statement order.\n */\nexport function compileCatalogDescendants(\n rootTable: string,\n scopePrefix: string,\n): CompiledStatement {\n const t = quoteIdent(catalogTableName(rootTable));\n return {\n sql:\n `SELECT storage_scope, table_name FROM ${t} ` +\n `WHERE storage_scope LIKE ? ESCAPE '\\\\' ORDER BY storage_scope`,\n params: [`${escapeLikePrefix(scopePrefix)}/%`],\n };\n}\n\n/** Remove one graph's catalog row (paired with its DROP TABLE). */\nexport function compileCatalogDelete(rootTable: string, storageScope: string): CompiledStatement {\n const t = quoteIdent(catalogTableName(rootTable));\n return {\n sql: `DELETE FROM ${t} WHERE storage_scope = ?`,\n params: [storageScope],\n };\n}\n","/**\n * SQLite implementation of `StorageBackend`.\n *\n * Table-per-graph design: the root graph lives in `tableName`, and each\n * subgraph lives in its own physical table (`<tableName>_g_<mangled scope>`,\n * see `tableForScope`). There is no `scope` column — the table a row lives\n * in *is* its scope, exactly like the Cloudflare DO edition where each\n * subgraph is its own Durable Object.\n *\n * A small catalog table (`<tableName>_graphs`) records every graph's\n * storage scope → table mapping. Cascade delete prefix-matches descendant\n * scopes in the catalog and drops each listed table — no registry topology\n * required.\n *\n * Schema is ensured lazily: the first operation on a backend instance runs\n * `CREATE TABLE IF NOT EXISTS` for the graph's table, its indexes, and the\n * catalog, then registers the graph in the catalog. Callers no longer\n * pre-create tables.\n */\n\nimport { computeEdgeDocId, computeNodeDocId } from '../docid.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 { NODE_RELATION } from '../internal/constants.js';\nimport type { SqliteExecutor, SqliteTxExecutor } from '../internal/sqlite-executor.js';\nimport { buildSchemaStatements, quoteIdent, validateTableName } from '../internal/sqlite-schema.js';\nimport type { CompiledStatement } from '../internal/sqlite-sql.js';\nimport {\n compileAggregate,\n compileBulkDelete,\n compileBulkUpdate,\n compileDelete,\n compileExpand,\n compileExpandHydrate,\n compileFindEdgesProjected,\n compileSelect,\n compileSelectByDocId,\n compileSet,\n compileUpdate,\n decodeProjectedRow,\n rowToRecord,\n} from '../internal/sqlite-sql.js';\nimport type {\n AggregateSpec,\n BulkBatchError,\n BulkOptions,\n BulkResult,\n BulkUpdatePatch,\n CascadeResult,\n ExpandParams,\n ExpandResult,\n FindEdgesParams,\n GraphReader,\n GraphRegistry,\n IndexSpec,\n QueryFilter,\n QueryOptions,\n StoredGraphRecord,\n} from '../types.js';\nimport {\n buildCatalogDDL,\n compileCatalogDelete,\n compileCatalogDescendants,\n compileCatalogRegister,\n tableForScope,\n} from './catalog.js';\n\nexport interface SqliteBackendOptions {\n /** Logical scope path (chained subgraph names) — used for `allowedIn` matching. */\n scopePath?: string;\n /**\n * Internal storage scope (interleaved parent-uid/name path). Determines\n * which physical table this backend reads and writes — `''` (the default)\n * is the root graph in `tableName` itself.\n *\n * @internal Used by `subgraph()` to derive child backends. Setting it\n * directly bypasses catalog registration consistency checks (the graph\n * still self-registers, but ancestors are not validated) — always derive\n * subgraph backends via `subgraph()` instead.\n */\n storageScope?: string;\n /**\n * Registry contributing per-entry `indexes` declarations, applied to\n * every graph table this backend (and its subgraphs) lazily creates.\n */\n registry?: GraphRegistry;\n /**\n * Replaces the built-in core index preset for lazily created tables.\n * Pass `[]` to disable core indexes entirely.\n */\n coreIndexes?: IndexSpec[];\n /**\n * Extra DDL statements appended to every graph table's lazy bootstrap.\n * Called with the physical table name; the returned statements run after\n * the core table/index DDL inside the same chunked batch. Statements MUST\n * be idempotent (`IF NOT EXISTS` / `INSERT OR IGNORE` / re-runnable DML)\n * — the bootstrap re-runs after self-heal and once per backend instance,\n * including every lazily created subgraph table.\n *\n * @internal Used by `firegraph/sqlite-local` to install FTS5 index\n * tables, sync triggers, and backfill statements. Propagated to subgraph\n * backends derived via `subgraph()`.\n */\n extraTableDDL?: (tableName: string) => string[];\n}\n\n/**\n * The shape `createSqliteBackend` actually returns: `StorageBackend` plus\n * internal hooks the `firegraph/sqlite-local` search wrapper needs.\n */\nexport interface SqliteStorageBackend extends StorageBackend<SqliteCapability> {\n /**\n * Force the lazy schema bootstrap (table + indexes + catalog + any\n * `extraTableDDL` artifacts). Pass `force: true` to reset the bootstrap\n * cache first — mirrors the self-heal path in `withSchema` for callers\n * that issue their own SQL against this graph's table and hit a\n * \"no such table\" error after a parent cascade dropped it.\n *\n * @internal Used by the `firegraph/sqlite-local` search wrapper.\n */\n ensureReady(force?: boolean): Promise<void>;\n subgraph(parentNodeUid: string, name: string): SqliteStorageBackend;\n}\n\n/**\n * Default per-chunk retry budget for bulk/cascade operations. Mirrors the\n * Firestore bulk path (`src/bulk.ts`) so behaviour is consistent across\n * backends. Callers override via `BulkOptions.maxRetries`.\n */\nconst DEFAULT_MAX_RETRIES = 3;\nconst BASE_RETRY_DELAY_MS = 200;\n/**\n * Upper bound for the exponential backoff between chunk retries. Without\n * this cap, `maxRetries: 10` would push the final wait past 100s; legitimate\n * transient errors recover well within a few seconds, and longer waits just\n * delay the surfacing of permanent failures.\n */\nconst MAX_RETRY_DELAY_MS = 5000;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Return the smaller of two optional positive numbers, treating `undefined`\n * as \"no cap.\" Used to combine caller-supplied `BulkOptions.batchSize` with\n * the driver's own `maxBatchSize` so the more restrictive cap wins.\n */\nfunction minDefined(a: number | undefined, b: number | undefined): number | undefined {\n if (a === undefined) return b;\n if (b === undefined) return a;\n return Math.min(a, b);\n}\n\n/**\n * Split `statements` into chunks that respect both a per-batch statement\n * count cap (`maxStatements`) and a per-batch total bound-parameter cap\n * (`maxParams`). When neither cap is provided the entire list is returned\n * as a single chunk (preserves cross-batch atomicity for drivers like\n * DO SQLite that have no caps).\n *\n * Single-statement edge case: if a single statement's parameter count\n * already exceeds `maxParams`, it's emitted as its own chunk anyway. The\n * driver will reject it, which is the correct behavior — silently\n * dropping it would be worse.\n */\nfunction chunkStatements<T extends { params: unknown[] }>(\n statements: T[],\n maxStatements: number | undefined,\n maxParams: number | undefined,\n): T[][] {\n const stmtCap =\n maxStatements && maxStatements > 0 && Number.isFinite(maxStatements)\n ? Math.floor(maxStatements)\n : Infinity;\n const paramCap =\n maxParams && maxParams > 0 && Number.isFinite(maxParams) ? Math.floor(maxParams) : Infinity;\n\n if (stmtCap === Infinity && paramCap === Infinity) {\n return [statements];\n }\n\n const chunks: T[][] = [];\n let current: T[] = [];\n let currentParamCount = 0;\n for (const stmt of statements) {\n const stmtParams = stmt.params.length;\n const wouldExceedStmt = current.length + 1 > stmtCap;\n const wouldExceedParam = currentParamCount + stmtParams > paramCap;\n if (current.length > 0 && (wouldExceedStmt || wouldExceedParam)) {\n chunks.push(current);\n current = [];\n currentParamCount = 0;\n }\n current.push(stmt);\n currentParamCount += stmtParams;\n }\n if (current.length > 0) chunks.push(current);\n return chunks;\n}\n\nclass SqliteTransactionBackendImpl implements TransactionBackend {\n constructor(\n private readonly tx: SqliteTxExecutor,\n private readonly tableName: string,\n ) {}\n\n async getDoc(docId: string): Promise<StoredGraphRecord | null> {\n const stmt = compileSelectByDocId(this.tableName, docId);\n const rows = await this.tx.all(stmt.sql, stmt.params);\n return rows.length === 0 ? null : rowToRecord(rows[0]);\n }\n\n async query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]> {\n const stmt = compileSelect(this.tableName, filters, options);\n const rows = await this.tx.all(stmt.sql, stmt.params);\n return rows.map(rowToRecord);\n }\n\n async setDoc(docId: string, record: WritableRecord, mode: WriteMode): Promise<void> {\n const stmt = compileSet(this.tableName, docId, record, Date.now(), mode);\n await this.tx.run(stmt.sql, stmt.params);\n }\n\n async updateDoc(docId: string, update: UpdatePayload): Promise<void> {\n const stmt = compileUpdate(this.tableName, docId, update, Date.now());\n // RETURNING + `all()` for parity with Firestore — see SqliteBackendImpl.updateDoc.\n const sqlWithReturning = `${stmt.sql} RETURNING \"doc_id\"`;\n const rows = await this.tx.all(sqlWithReturning, stmt.params);\n if (rows.length === 0) {\n throw new FiregraphError(\n `updateDoc: no document found for doc_id=${docId} (table=${this.tableName})`,\n 'NOT_FOUND',\n );\n }\n }\n\n async deleteDoc(docId: string): Promise<void> {\n const stmt = compileDelete(this.tableName, docId);\n await this.tx.run(stmt.sql, stmt.params);\n }\n}\n\nclass SqliteBatchBackendImpl implements BatchBackend {\n private readonly statements: CompiledStatement[] = [];\n\n constructor(\n private readonly executor: SqliteExecutor,\n private readonly tableName: string,\n private readonly ensureSchema: () => Promise<void>,\n ) {}\n\n setDoc(docId: string, record: WritableRecord, mode: WriteMode): void {\n this.statements.push(compileSet(this.tableName, docId, record, Date.now(), mode));\n }\n\n updateDoc(docId: string, update: UpdatePayload): void {\n this.statements.push(compileUpdate(this.tableName, docId, update, Date.now()));\n }\n\n deleteDoc(docId: string): void {\n this.statements.push(compileDelete(this.tableName, docId));\n }\n\n async commit(): Promise<void> {\n if (this.statements.length === 0) return;\n await this.ensureSchema();\n await this.executor.batch(this.statements);\n this.statements.length = 0;\n }\n}\n\n/**\n * Capability union declared by the SQLite-backed `StorageBackend`.\n *\n * `core.transactions` is part of the static union because `runTransaction`\n * is always present as a method on the class. The runtime cap-set determines\n * whether that method is *functional*: D1 leaves `executor.transaction`\n * undefined and the call throws `UNSUPPORTED_OPERATION`; DO SQLite and\n * better-sqlite3 wire the executor and the call works. The static type\n * therefore promises only that the method exists — callers that care about\n * portability check `client.capabilities.has('core.transactions')` before\n * opening a tx, and code that runs against an unknown driver can rely on the\n * runtime guard inside `runTransaction`.\n *\n * The `query.*` extension capabilities follow the same conservative\n * declaration rule as the cap descriptor itself — only land in the union\n * when the corresponding method is actually wired up. Today that's\n * `query.aggregate` (Phase 4), `query.dml` (Phase 5), `query.join`\n * (Phase 6 — fan-out via `IN (…)` in one statement), and `query.select`\n * (Phase 7 — server-side projection via `json_extract`).\n */\nexport type SqliteCapability =\n | 'core.read'\n | 'core.write'\n | 'core.transactions'\n | 'core.batch'\n | 'core.subgraph'\n | 'query.aggregate'\n | 'query.dml'\n | 'query.join'\n | 'query.select'\n | 'raw.sql';\n\nconst SQLITE_CORE_CAPS: ReadonlyArray<SqliteCapability> = [\n 'core.read',\n 'core.write',\n 'core.batch',\n 'core.subgraph',\n 'query.aggregate',\n 'query.dml',\n 'query.join',\n 'query.select',\n 'raw.sql',\n];\n\nclass SqliteBackendImpl implements StorageBackend<SqliteCapability> {\n readonly capabilities: BackendCapabilities<SqliteCapability>;\n /** Physical table holding this graph's triples. */\n readonly collectionPath: string;\n readonly scopePath: string;\n /** Storage scope (interleaved parent UIDs + subgraph names) — `''` at root. */\n private readonly storageScope: string;\n /** Root graph's table name — prefix for subgraph tables and the catalog. */\n private readonly rootTable: string;\n private readonly registry: GraphRegistry | undefined;\n private readonly coreIndexes: IndexSpec[] | undefined;\n private readonly extraTableDDL: ((tableName: string) => string[]) | undefined;\n private ensured: Promise<void> | null = null;\n\n constructor(\n private readonly executor: SqliteExecutor,\n rootTable: string,\n storageScope: string,\n scopePath: string,\n registry: GraphRegistry | undefined,\n coreIndexes: IndexSpec[] | undefined,\n extraTableDDL?: (tableName: string) => string[],\n ) {\n validateTableName(rootTable);\n this.rootTable = rootTable;\n this.collectionPath = tableForScope(rootTable, storageScope);\n this.storageScope = storageScope;\n this.scopePath = scopePath;\n this.registry = registry;\n this.coreIndexes = coreIndexes;\n this.extraTableDDL = extraTableDDL;\n const caps = new Set<SqliteCapability>(SQLITE_CORE_CAPS);\n if (typeof executor.transaction === 'function') {\n caps.add('core.transactions');\n }\n this.capabilities = createCapabilities(caps);\n }\n\n /**\n * Lazily create this graph's table + indexes + the catalog, and register\n * the graph in the catalog. Runs once per backend instance; the DDL is\n * all `IF NOT EXISTS` / `INSERT OR IGNORE`, so concurrent instances over\n * the same database converge safely.\n */\n private ensureSchema(): Promise<void> {\n if (!this.ensured) {\n this.ensured = this.doEnsureSchema().catch((err) => {\n // Allow retry on the next operation rather than caching the failure.\n this.ensured = null;\n throw err;\n });\n }\n return this.ensured;\n }\n\n /** @internal See `SqliteStorageBackend.ensureReady`. */\n async ensureReady(force = false): Promise<void> {\n if (force) this.ensured = null;\n await this.ensureSchema();\n }\n\n private async doEnsureSchema(): Promise<void> {\n const ddl = [\n ...buildSchemaStatements(this.collectionPath, {\n coreIndexes: this.coreIndexes,\n registry: this.registry,\n }),\n ...(this.extraTableDDL ? this.extraTableDDL(this.collectionPath) : []),\n buildCatalogDDL(this.rootTable),\n ];\n const statements: CompiledStatement[] = ddl.map((sql) => ({ sql, params: [] }));\n statements.push(\n compileCatalogRegister(\n this.rootTable,\n this.storageScope,\n this.collectionPath,\n this.scopePath,\n ),\n );\n // Respect the driver's batch caps (D1 ≈ 100 statements / ≈ 1000 params) —\n // a registry with many index declarations could push the DDL list past\n // them. Every statement is IF NOT EXISTS / INSERT OR IGNORE, so losing\n // cross-chunk atomicity here is harmless: a partial bootstrap converges\n // on the next attempt.\n const chunks = chunkStatements(\n statements,\n this.executor.maxBatchSize,\n this.executor.maxBatchParams,\n );\n for (const chunk of chunks) {\n await this.executor.batch(chunk);\n }\n }\n\n /**\n * Run `op` with the schema bootstrap applied, self-healing when this\n * graph's table was dropped out from under the instance — a parent's\n * cascade delete DROPs descendant tables, but subgraph handles created\n * before the cascade still point at this (now missing) table with a\n * resolved bootstrap cache. On a \"no such table: <own table>\" error the\n * cache resets, the empty graph is recreated, and the op retries once.\n * This matches Firestore semantics, where a deleted subcollection reads\n * as empty and writes recreate it.\n */\n private async withSchema<T>(op: () => Promise<T>): Promise<T> {\n await this.ensureSchema();\n try {\n return await op();\n } catch (err) {\n if (!this.isMissingOwnTable(err)) throw err;\n this.ensured = null;\n await this.ensureSchema();\n return op();\n }\n }\n\n /** True when `err` is SQLite's missing-table error naming OUR table. */\n private isMissingOwnTable(err: unknown): boolean {\n const message = err instanceof Error ? err.message : String(err);\n return message.includes(`no such table: ${this.collectionPath}`);\n }\n\n // --- Reads ---\n\n async getDoc(docId: string): Promise<StoredGraphRecord | null> {\n return this.withSchema(async () => {\n const stmt = compileSelectByDocId(this.collectionPath, docId);\n const rows = await this.executor.all(stmt.sql, stmt.params);\n return rows.length === 0 ? null : rowToRecord(rows[0]);\n });\n }\n\n async query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]> {\n return this.withSchema(async () => {\n const stmt = compileSelect(this.collectionPath, filters, options);\n const rows = await this.executor.all(stmt.sql, stmt.params);\n return rows.map(rowToRecord);\n });\n }\n\n // --- Writes ---\n\n async setDoc(docId: string, record: WritableRecord, mode: WriteMode): Promise<void> {\n return this.withSchema(async () => {\n const stmt = compileSet(this.collectionPath, docId, record, Date.now(), mode);\n await this.executor.run(stmt.sql, stmt.params);\n });\n }\n\n async updateDoc(docId: string, update: UpdatePayload): Promise<void> {\n return this.withSchema(async () => {\n const stmt = compileUpdate(this.collectionPath, docId, update, Date.now());\n // Use RETURNING + `all()` so missing rows surface as an error, matching\n // Firestore's `update()` semantics (NOT_FOUND when the doc doesn't exist).\n // SQLite ≥3.35 supports UPDATE … RETURNING; better-sqlite3, D1, and DO\n // SQLite all run on a recent enough engine.\n const sqlWithReturning = `${stmt.sql} RETURNING \"doc_id\"`;\n const rows = await this.executor.all(sqlWithReturning, stmt.params);\n if (rows.length === 0) {\n throw new FiregraphError(\n `updateDoc: no document found for doc_id=${docId} (table=${this.collectionPath})`,\n 'NOT_FOUND',\n );\n }\n });\n }\n\n async deleteDoc(docId: string): Promise<void> {\n return this.withSchema(async () => {\n const stmt = compileDelete(this.collectionPath, docId);\n await this.executor.run(stmt.sql, stmt.params);\n });\n }\n\n // --- Transactions / Batches ---\n\n async runTransaction<T>(fn: (tx: TransactionBackend) => Promise<T>): Promise<T> {\n if (!this.executor.transaction) {\n throw new FiregraphError(\n 'Interactive transactions are not supported by this SQLite driver. ' +\n 'D1 in particular has no read-then-conditional-write transactions; ' +\n 'use a Durable Object SQLite client instead, or rewrite the code path ' +\n 'as a batch().',\n 'UNSUPPORTED_OPERATION',\n );\n }\n // Schema must exist before the tx opens — DDL inside an interactive\n // transaction would commit with it, complicating rollback semantics.\n await this.ensureSchema();\n return this.executor.transaction(async (tx) => {\n const txBackend = new SqliteTransactionBackendImpl(tx, this.collectionPath);\n return fn(txBackend);\n });\n }\n\n createBatch(): BatchBackend {\n return new SqliteBatchBackendImpl(this.executor, this.collectionPath, () =>\n this.ensureSchema(),\n );\n }\n\n // --- Subgraphs ---\n\n subgraph(parentNodeUid: string, name: string): SqliteStorageBackend {\n // Defense-in-depth: the public `GraphClient.subgraph()` also validates,\n // but backend users (traversal, cross-graph hops, custom integrations)\n // reach this method directly. A bad UID or a name containing '/' would\n // corrupt the materialized-path scope encoding — reject loudly.\n if (!parentNodeUid || parentNodeUid.includes('/')) {\n throw new FiregraphError(\n `Invalid parentNodeUid for subgraph: \"${parentNodeUid}\". ` +\n 'Must be a non-empty string without \"/\".',\n 'INVALID_SUBGRAPH',\n );\n }\n if (!name || name.includes('/')) {\n throw new FiregraphError(\n `Subgraph name must not contain \"/\" and must be non-empty: got \"${name}\". ` +\n 'Use chained .subgraph() calls for nested subgraphs.',\n 'INVALID_SUBGRAPH',\n );\n }\n const newStorageScope = this.storageScope\n ? `${this.storageScope}/${parentNodeUid}/${name}`\n : `${parentNodeUid}/${name}`;\n const newScope = this.scopePath ? `${this.scopePath}/${name}` : name;\n return new SqliteBackendImpl(\n this.executor,\n this.rootTable,\n newStorageScope,\n newScope,\n this.registry,\n this.coreIndexes,\n this.extraTableDDL,\n );\n }\n\n // --- Cascade & bulk ---\n\n async removeNodeCascade(\n uid: string,\n reader: GraphReader,\n options?: BulkOptions,\n ): Promise<CascadeResult> {\n await this.ensureSchema();\n // Collect all edges touching the node in the current graph (excluding self-loop).\n const [outgoingRaw, incomingRaw] = await Promise.all([\n reader.findEdges({ aUid: uid, allowCollectionScan: true, limit: 0 }),\n reader.findEdges({ bUid: uid, allowCollectionScan: true, limit: 0 }),\n ]);\n\n const seen = new Set<string>();\n const edgeDocIds: string[] = [];\n for (const edge of [...outgoingRaw, ...incomingRaw]) {\n if (edge.axbType === NODE_RELATION) continue;\n const docId = computeEdgeDocId(edge.aUid, edge.axbType, edge.bUid);\n if (!seen.has(docId)) {\n seen.add(docId);\n edgeDocIds.push(docId);\n }\n }\n\n const nodeDocId = computeNodeDocId(uid);\n const shouldDeleteSubgraphs = options?.deleteSubcollections !== false;\n\n // Discover descendant graphs via the catalog — every subgraph under this\n // node has a storage scope starting with `<scope>/<uid>/`. Pre-count\n // each table's rows so the returned `deleted` total reflects actual\n // records removed by the DROPs, not bookkeeping statements.\n const descendants: Array<{ storageScope: string; tableName: string }> = [];\n let subgraphRowCount = 0;\n if (shouldDeleteSubgraphs) {\n const prefix = this.storageScope ? `${this.storageScope}/${uid}` : uid;\n const descStmt = compileCatalogDescendants(this.rootTable, prefix);\n const rows = await this.executor.all(descStmt.sql, descStmt.params);\n for (const row of rows) {\n const tableName = String(row.table_name);\n // Catalog rows are written exclusively by `doEnsureSchema` with\n // mangled names, but validate anyway — these get interpolated into\n // DROP TABLE statements.\n validateTableName(tableName);\n descendants.push({ storageScope: String(row.storage_scope), tableName });\n }\n for (const d of descendants) {\n const countRows = await this.executor.all(\n `SELECT COUNT(*) AS n FROM ${quoteIdent(d.tableName)}`,\n [],\n );\n const n = (countRows[0] as Record<string, unknown> | undefined)?.n;\n subgraphRowCount += typeof n === 'bigint' ? Number(n) : Number(n ?? 0);\n }\n }\n\n // Build the full statement list. Order: edges → node → per-descendant\n // (DROP TABLE + catalog row delete). When the executor's `batch()` is\n // fully atomic the chunking loop below collapses to a single batch and\n // the operation is atomic. When the executor caps batches (D1, ~100\n // statements) we lose cross-batch atomicity, but `removeNodeCascade` is\n // idempotent so a caller can retry after a partial failure.\n const writeStatements: CompiledStatement[] = edgeDocIds.map((id) =>\n compileDelete(this.collectionPath, id),\n );\n writeStatements.push(compileDelete(this.collectionPath, nodeDocId));\n for (const d of descendants) {\n writeStatements.push({ sql: `DROP TABLE IF EXISTS ${quoteIdent(d.tableName)}`, params: [] });\n writeStatements.push(compileCatalogDelete(this.rootTable, d.storageScope));\n }\n\n const {\n deleted: stmtDeleted,\n batches,\n errors,\n } = await this.executeChunkedBatches(writeStatements, options);\n\n // `nodeDeleted` / `edgesDeleted` reflect best-effort completion: a\n // chunk failure leaves us unable to know which sub-batch contained the\n // node-row delete, so we conservatively flag both as incomplete when\n // any batch fails. The caller can retry — cascade is idempotent.\n const allOk = errors.length === 0;\n const edgesDeleted = allOk ? edgeDocIds.length : 0;\n // `nodeDeleted: true` means \"the node doc is gone\" — deletes are\n // idempotent, so a nonexistent node still reports true. This matches the\n // Firestore edition and the cross-backend contract pinned by\n // tests/integration/bulk.test.ts (\"handles nonexistent node gracefully\").\n const nodeDeleted = allOk;\n\n // `stmtDeleted` counts committed *statements*. Replace the per-descendant\n // bookkeeping statements' contribution (DROP + catalog delete = 2 each)\n // with the pre-computed row totals so callers see a true record count.\n // Only credit subgraph rows when every chunk succeeded — partial failure\n // means we can't be sure the chunks containing the DROPs committed.\n const bookkeepingContribution = allOk ? descendants.length * 2 : 0;\n const deleted = stmtDeleted - bookkeepingContribution + (allOk ? subgraphRowCount : 0);\n\n return { deleted, batches, errors, edgesDeleted, nodeDeleted };\n }\n\n async bulkRemoveEdges(\n params: FindEdgesParams,\n reader: GraphReader,\n options?: BulkOptions,\n ): Promise<BulkResult> {\n await this.ensureSchema();\n // Override default query limit for bulk deletion — we need all matching edges.\n // limit: 0 bypasses DEFAULT_QUERY_LIMIT; an explicit user limit is preserved.\n // allowCollectionScan: true — bulk deletion inherently implies scanning.\n const effectiveParams =\n params.limit !== undefined\n ? { ...params, allowCollectionScan: params.allowCollectionScan ?? true }\n : { ...params, limit: 0, allowCollectionScan: params.allowCollectionScan ?? true };\n const edges = await reader.findEdges(effectiveParams);\n const docIds = edges.map((e) => computeEdgeDocId(e.aUid, e.axbType, e.bUid));\n\n if (docIds.length === 0) {\n return { deleted: 0, batches: 0, errors: [] };\n }\n\n const statements = docIds.map((id) => compileDelete(this.collectionPath, id));\n\n return this.executeChunkedBatches(statements, options);\n }\n\n /**\n * Submit `statements` to the executor as one or more `batch()` calls,\n * chunking by `executor.maxBatchSize` (e.g. D1's ~100-statement cap).\n * Drivers that don't advertise a cap submit everything in one batch,\n * preserving cross-batch atomicity.\n *\n * Each chunk is retried with exponential backoff up to `maxRetries`\n * (default 3) before being recorded in `errors`. The loop continues past\n * a permanently failed chunk so the caller still gets partial progress\n * visibility — to halt on first failure, set `maxRetries: 0` and check\n * `result.errors.length` after the call.\n *\n * Returns `BulkResult`-shaped fields. `deleted` reflects only the\n * statement count of *successfully committed* batches — a DROP TABLE\n * statement contributes 1 to that total even though it may remove many\n * rows; `removeNodeCascade` patches that up with pre-counted row totals.\n *\n * **Atomicity caveat (D1):** when chunking kicks in, atomicity is lost\n * across chunk boundaries — one chunk may commit while a later one fails.\n * `removeNodeCascade` is idempotent (deleting the same docs again is a\n * no-op) so a caller can simply retry on partial failure. `bulkRemoveEdges`\n * is also idempotent for the same reason. DO SQLite leaves `maxBatchSize`\n * unset, so everything funnels through one atomic `transactionSync` and\n * this caveat does not apply.\n */\n private async executeChunkedBatches(\n statements: CompiledStatement[],\n options?: BulkOptions,\n ): Promise<{ deleted: number; batches: number; errors: BulkBatchError[] }> {\n if (statements.length === 0) {\n return { deleted: 0, batches: 0, errors: [] };\n }\n const maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES;\n\n // Split `statements` into chunks up front. Chunking honors the smallest\n // of: caller-supplied `batchSize` (used by callers who want progress\n // granularity), the driver's statement-count cap (`maxBatchSize`, D1 ≈\n // 100), and the driver's total bound-parameter cap (`maxBatchParams`,\n // D1 ≈ 1000). Most cascade/bulk statements are 1-param DELETEs so the\n // param cap rarely triggers, but we respect it defensively. Drivers with\n // no declared caps and no caller cap submit everything in one batch (DO\n // SQLite's atomic `transactionSync`).\n const callerBatchSize = options?.batchSize;\n const stmtCap = minDefined(callerBatchSize, this.executor.maxBatchSize);\n const chunks = chunkStatements(statements, stmtCap, this.executor.maxBatchParams);\n\n const errors: BulkBatchError[] = [];\n let deleted = 0;\n let batches = 0;\n const totalBatches = chunks.length;\n\n const driverParamCap = this.executor.maxBatchParams;\n\n for (let batchIndex = 0; batchIndex < chunks.length; batchIndex++) {\n const chunk = chunks[batchIndex];\n\n // A chunk that's a single statement whose param count already exceeds\n // the driver's per-batch param cap will be rejected on every attempt —\n // retrying just adds latency before surfacing the failure. `chunkStatements`\n // intentionally emits such statements as their own chunk (failing loudly\n // beats silently dropping); fast-fail here closes the loop.\n const isUnretriableOversize =\n chunk.length === 1 &&\n driverParamCap !== undefined &&\n chunk[0].params.length > driverParamCap;\n\n let committed = false;\n let lastError: Error | null = null;\n const effectiveRetries = isUnretriableOversize ? 0 : maxRetries;\n for (let attempt = 0; attempt <= effectiveRetries; attempt++) {\n try {\n await this.executor.batch(chunk);\n committed = true;\n break;\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n if (attempt < effectiveRetries) {\n const delay = Math.min(BASE_RETRY_DELAY_MS * Math.pow(2, attempt), MAX_RETRY_DELAY_MS);\n await sleep(delay);\n }\n }\n }\n\n if (committed) {\n deleted += chunk.length;\n batches += 1;\n } else if (lastError) {\n errors.push({\n batchIndex,\n error: lastError,\n operationCount: chunk.length,\n });\n }\n\n if (options?.onProgress) {\n options.onProgress({\n completedBatches: batches,\n totalBatches,\n deletedSoFar: deleted,\n });\n }\n }\n\n return { deleted, batches, errors };\n }\n\n // `findEdgesGlobal` is deliberately NOT defined on this class. Each graph\n // is its own table, so a \"collection group\" query would mean scanning every\n // table listed in the catalog — an unbounded fan-out the cross-backend\n // contract treats as unsupported (the Cloudflare DO edition makes the same\n // call: no cross-DO index, no `findEdgesGlobal`). The client surfaces\n // `UNSUPPORTED_OPERATION` when the method is absent.\n\n // --- Aggregate ---\n\n /**\n * Run an aggregate query in a single SQL statement. Supports the full\n * count/sum/avg/min/max set — the SQLite engine evaluates each aggregate\n * function over the filtered row set and the executor returns one row\n * with one column per alias. SUM/MIN/MAX of an empty set returns 0\n * (SQLite's `SUM(NULL) = NULL` is mapped to a clean number for the\n * cross-backend contract); AVG returns NaN, matching the mathematical\n * convention and the Firestore Standard helper.\n */\n async aggregate(spec: AggregateSpec, filters: QueryFilter[]): Promise<Record<string, number>> {\n const { stmt, aliases } = compileAggregate(this.collectionPath, spec, filters);\n const rows = await this.withSchema(() => this.executor.all(stmt.sql, stmt.params));\n const row = rows[0] ?? {};\n const out: Record<string, number> = {};\n for (const alias of aliases) {\n const v = row[alias];\n if (v === null || v === undefined) {\n // SQLite returns NULL for SUM/MIN/MAX over an empty set. Resolve\n // to 0 for SUM/MIN/MAX (well-defined) and NaN for AVG (empty-set\n // average is undefined). COUNT(*) is never null.\n const op = spec[alias].op;\n out[alias] = op === 'avg' ? Number.NaN : 0;\n } else if (typeof v === 'bigint') {\n out[alias] = Number(v);\n } else if (typeof v === 'number') {\n out[alias] = v;\n } else {\n // Some drivers return strings for very large or precise numerics.\n // Coerce defensively — the contract is `number`.\n out[alias] = Number(v);\n }\n }\n return out;\n }\n\n // --- Server-side DML ---\n\n /**\n * Delete every row matching `filters` in a single SQL DELETE statement.\n *\n * Uses `RETURNING \"doc_id\"` to count rows touched — the SQLite executor's\n * `run` returns void, so RETURNING + `all()` is the portable way to learn\n * how many rows the engine actually deleted. SQLite ≥ 3.35 supports\n * `DELETE … RETURNING`; better-sqlite3, D1, and DO SQLite all run on a\n * recent enough engine.\n *\n * Single-statement DML doesn't chunk: the engine handles N rows in one\n * shot, so `BulkOptions.batchSize` is intentionally ignored. The retry\n * loop here exists only for transient driver errors (e.g. D1 surface\n * congestion); a permanent failure is surfaced via the `errors` array\n * with `batchIndex: 0` so callers see the same shape as `bulkRemoveEdges`.\n *\n * Subgraph isolation is physical — the statement only ever touches this\n * graph's table, so no scoping predicate is needed.\n */\n async bulkDelete(filters: QueryFilter[], options?: BulkOptions): Promise<BulkResult> {\n await this.ensureSchema();\n const stmt = compileBulkDelete(this.collectionPath, filters);\n return this.executeDmlWithReturning(stmt, options);\n }\n\n /**\n * Update every row matching `filters` with `patch.data` in a single SQL\n * UPDATE statement. The patch is deep-merged into each row's `data`\n * column via the same `flattenPatch` → `compileDataOpsExpr` pipeline that\n * `compileUpdate` (single-row) uses.\n *\n * Same contract notes as `bulkDelete` apply: single-statement, no\n * chunking, `RETURNING \"doc_id\"` for the affected count, retry loop for\n * transient driver errors.\n */\n async bulkUpdate(\n filters: QueryFilter[],\n patch: BulkUpdatePatch,\n options?: BulkOptions,\n ): Promise<BulkResult> {\n await this.ensureSchema();\n const stmt = compileBulkUpdate(this.collectionPath, filters, patch.data, Date.now());\n return this.executeDmlWithReturning(stmt, options);\n }\n\n /**\n * Multi-source fan-out — `query.join` capability.\n *\n * Issues a single `SELECT … WHERE \"aUid\" IN (?, ?, …)` statement that\n * matches every edge from every source UID in one round trip. When\n * `params.hydrate === true`, follows up with a second statement that\n * fetches the target node rows; both queries hit the same table so\n * the executor amortises connection / parsing cost across them.\n *\n * Empty `params.sources` short-circuits to an empty result without\n * touching the executor — `IN ()` is not valid SQL.\n *\n * Per-source ordering / strict per-source LIMIT enforcement is NOT\n * implemented here; see the `ExpandParams.limitPerSource` JSDoc and\n * `compileExpand` for the cap semantics. Strict per-source caps would\n * require window functions and were judged out of scope for the\n * round-trip-collapse goal.\n */\n async expand(params: ExpandParams): Promise<ExpandResult> {\n if (params.sources.length === 0) {\n return params.hydrate ? { edges: [], targets: [] } : { edges: [] };\n }\n const stmt = compileExpand(this.collectionPath, params);\n const rows = await this.withSchema(() => this.executor.all(stmt.sql, stmt.params));\n const edges = rows.map(rowToRecord);\n if (!params.hydrate) {\n return { edges };\n }\n // Hydration: fetch target nodes for every edge in one IN-clause statement.\n // The \"target\" side depends on direction — forward hops point at `bUid`,\n // reverse hops point at `aUid`.\n const direction = params.direction ?? 'forward';\n const targetUids = edges.map((e) => (direction === 'forward' ? e.bUid : e.aUid));\n const uniqueTargets = [...new Set(targetUids)];\n if (uniqueTargets.length === 0) {\n return { edges, targets: [] };\n }\n const hydrateStmt = compileExpandHydrate(this.collectionPath, uniqueTargets);\n const hydrateRows = await this.executor.all(hydrateStmt.sql, hydrateStmt.params);\n const byUid = new Map<string, StoredGraphRecord>();\n for (const row of hydrateRows) {\n const node = rowToRecord(row);\n // Node UID is `bUid` (== `aUid` for self-loop) by convention. Key the\n // map by `bUid` so the alignment loop below indexes correctly.\n byUid.set(node.bUid, node);\n }\n const targets = targetUids.map((uid) => byUid.get(uid) ?? null);\n return { edges, targets };\n }\n\n /**\n * Server-side projection — `query.select` capability.\n *\n * Issues a single `SELECT json_extract(data, '$.f1'), …` statement that\n * returns only the requested fields. The compiler emits one column per\n * unique field plus a paired `json_type` column for `data.*` projections\n * so the decoder can recover JSON-encoded objects/arrays without a\n * second round trip. Migrations are NOT applied — the caller asked for\n * a partial shape, and rehydrating that into the migration pipeline\n * would require synthesising every absent field.\n *\n * The wire-payload reduction is the entire reason this method exists:\n * a list view that only needs `title` / `date` no longer drags the\n * full `data` JSON across the network. Callers that need the full\n * record should use `findEdges` (with migration support).\n */\n async findEdgesProjected(\n select: ReadonlyArray<string>,\n filters: QueryFilter[],\n options?: QueryOptions,\n ): Promise<Array<Record<string, unknown>>> {\n const { stmt, columns } = compileFindEdgesProjected(\n this.collectionPath,\n select,\n filters,\n options,\n );\n const rows = await this.withSchema(() => this.executor.all(stmt.sql, stmt.params));\n return rows.map((row) => decodeProjectedRow(row, columns));\n }\n\n /**\n * Run a DML statement with `RETURNING \"doc_id\"` so we can count the\n * rows the engine touched, with the same retry/backoff contract as\n * `executeChunkedBatches`. Single statement, single batch.\n */\n private async executeDmlWithReturning(\n stmt: CompiledStatement,\n options?: BulkOptions,\n ): Promise<BulkResult> {\n const sqlWithReturning = `${stmt.sql} RETURNING \"doc_id\"`;\n const maxRetries = options?.maxRetries ?? DEFAULT_MAX_RETRIES;\n let lastError: Error | null = null;\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const rows = await this.executor.all(sqlWithReturning, stmt.params);\n const deleted = rows.length;\n if (options?.onProgress) {\n options.onProgress({\n completedBatches: 1,\n totalBatches: 1,\n deletedSoFar: deleted,\n });\n }\n return { deleted, batches: 1, errors: [] };\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n if (attempt < maxRetries) {\n const delay = Math.min(BASE_RETRY_DELAY_MS * Math.pow(2, attempt), MAX_RETRY_DELAY_MS);\n await sleep(delay);\n }\n }\n }\n // `operationCount` is genuinely unknown for a server-side DML — we\n // don't know how many rows the failed statement would have touched.\n // Report 0 as the lower bound; callers concerned about partial state\n // should re-query and reconcile.\n return {\n deleted: 0,\n batches: 0,\n errors: [\n {\n batchIndex: 0,\n error: lastError ?? new Error('bulk DML failed for unknown reason'),\n operationCount: 0,\n },\n ],\n };\n }\n}\n\n/**\n * Create a SQLite-backed `StorageBackend`.\n *\n * `tableName` is the root graph's table; subgraphs get their own tables\n * derived from it (see `tableForScope`). Schema (tables, indexes, and the\n * graph catalog) is created lazily on first use — no manual DDL step.\n * Pass `options.registry` so per-entry `indexes` declarations land in\n * every lazily created table.\n */\nexport function createSqliteBackend(\n executor: SqliteExecutor,\n tableName: string,\n options: SqliteBackendOptions = {},\n): SqliteStorageBackend {\n const storageScope = options.storageScope ?? '';\n const scopePath = options.scopePath ?? '';\n return new SqliteBackendImpl(\n executor,\n tableName,\n storageScope,\n scopePath,\n options.registry,\n options.coreIndexes,\n options.extraTableDDL,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,iBAAN,cAA6B,MAAM;AAAA,EACxC,YACE,SACgB,MAChB;AACA,UAAM,OAAO;AAFG;AAGhB,SAAK,OAAO;AAAA,EACd;AACF;;;ACkDO,SAAS,mBACd,MACwB;AACxB,SAAO;AAAA,IACL,KAAK,CAAC,eAAoC,KAAK,IAAI,UAAe;AAAA,IAClE,QAAQ,MAAM,KAAK,OAAO;AAAA,EAC5B;AACF;;;ACjBO,IAAM,uBAAiD,OAAO,OAAO;AAAA,EAC1E,EAAE,QAAQ,CAAC,MAAM,EAAE;AAAA,EACnB,EAAE,QAAQ,CAAC,MAAM,EAAE;AAAA,EACnB,EAAE,QAAQ,CAAC,OAAO,EAAE;AAAA,EACpB,EAAE,QAAQ,CAAC,OAAO,EAAE;AAAA,EACpB,EAAE,QAAQ,CAAC,QAAQ,SAAS,EAAE;AAAA,EAC9B,EAAE,QAAQ,CAAC,WAAW,MAAM,EAAE;AAAA,EAC9B,EAAE,QAAQ,CAAC,SAAS,SAAS,EAAE;AAAA,EAC/B,EAAE,QAAQ,CAAC,WAAW,OAAO,EAAE;AACjC,CAAC;;;ACtBD,IAAM,WAAW;AAOjB,IAAM,mBAAmB;AAEzB,SAAS,WAAW,MAAsB;AACxC,MAAI,CAAC,SAAS,KAAK,IAAI,GAAG;AACxB,UAAM,IAAI;AAAA,MACR,wCAAwC,IAAI;AAAA,MAC5C;AAAA,IACF;AAAA,EACF;AACA,SAAO,IAAI,IAAI;AACjB;AAMA,SAAS,QAAQ,KAAqB;AACpC,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,SAAK,IAAI,WAAW,CAAC;AACrB,QAAI,KAAK,KAAK,GAAG,QAAU;AAAA,EAC7B;AACA,UAAQ,MAAM,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAC/C;AAEA,SAAS,gBACP,QACwC;AACxC,SAAO,OAAO,IAAI,CAAC,MAAM;AACvB,QAAI,OAAO,MAAM,SAAU,QAAO,EAAE,MAAM,GAAG,MAAM,MAAM;AACzD,QAAI,CAAC,EAAE,QAAQ,OAAO,EAAE,SAAS,UAAU;AACzC,YAAM,IAAI;AAAA,QACR,6EAA6E,KAAK,UAAU,CAAC,CAAC;AAAA,QAC9F;AAAA,MACF;AAAA,IACF;AACA,WAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC,CAAC,EAAE,KAAK;AAAA,EACxC,CAAC;AACH;AAEA,SAAS,gBAAgB,MAAyB;AAIhD,QAAM,aAAa;AAAA,IACjB,MAAM,CAAC;AAAA,IACP,QAAQ,gBAAgB,KAAK,MAAM;AAAA,IACnC,OAAO,KAAK,SAAS;AAAA,EACvB;AACA,SAAO,QAAQ,KAAK,UAAU,UAAU,CAAC;AAC3C;AASA,SAAS,iBAAiB,MAAc,eAA+C;AACrF,QAAM,MAAM,cAAc,IAAI;AAC9B,MAAI,IAAK,QAAO,WAAW,GAAG;AAE9B,MAAI,SAAS,QAAQ;AACnB,WAAO;AAAA,EACT;AACA,MAAI,KAAK,WAAW,OAAO,GAAG;AAC5B,UAAM,SAAS,KAAK,MAAM,CAAC;AAC3B,UAAM,QAAQ,OAAO,MAAM,GAAG;AAC9B,eAAW,QAAQ,OAAO;AACxB,UAAI,CAAC,iBAAiB,KAAK,IAAI,GAAG;AAChC,cAAM,IAAI;AAAA,UACR,wBAAwB,IAAI,4BAA4B,IAAI;AAAA,UAE5D;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,WAAO,2BAA2B,MAAM;AAAA,EAC1C;AAEA,QAAM,IAAI;AAAA,IACR,oBAAoB,IAAI;AAAA,IAGxB;AAAA,EACF;AACF;AAeO,SAAS,cAAc,MAAiB,SAAwC;AACrF,QAAM,EAAE,OAAO,cAAc,IAAI;AAEjC,MAAI,CAAC,KAAK,UAAU,KAAK,OAAO,WAAW,GAAG;AAC5C,UAAM,IAAI,eAAe,8CAA8C,eAAe;AAAA,EACxF;AAEA,QAAM,aAAa,gBAAgB,KAAK,MAAM;AAC9C,QAAM,OAAO,gBAAgB,IAAI;AACjC,QAAM,YAAY,GAAG,KAAK,QAAQ,IAAI;AAEtC,QAAM,OAAiB,CAAC;AACxB,aAAW,KAAK,YAAY;AAC1B,UAAM,OAAO,iBAAiB,EAAE,MAAM,aAAa;AACnD,SAAK,KAAK,EAAE,OAAO,GAAG,IAAI,UAAU,IAAI;AAAA,EAC1C;AAEA,MAAI,MAAM,8BAA8B,WAAW,SAAS,CAAC,OAAO,WAAW,KAAK,CAAC,IAAI,KAAK,KAAK,IAAI,CAAC;AAExG,MAAI,KAAK,OAAO;AAKd,WAAO,UAAU,KAAK,KAAK;AAAA,EAC7B;AAEA,SAAO;AACT;AAOO,SAAS,iBAAiB,OAA8C;AAC7E,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,MAAmB,CAAC;AAC1B,aAAW,QAAQ,OAAO;AACxB,UAAM,KAAK,gBAAgB,IAAI;AAC/B,QAAI,KAAK,IAAI,EAAE,EAAG;AAClB,SAAK,IAAI,EAAE;AACX,QAAI,KAAK,IAAI;AAAA,EACf;AACA,SAAO;AACT;;;ACpJO,IAAM,kBAAgD;AAAA,EAC3D,OAAO;AAAA,EACP,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AAAA,EACP,MAAM;AAAA,EACN,GAAG;AAAA,EACH,WAAW;AAAA,EACX,WAAW;AACb;AA0BO,SAAS,sBAAsB,OAAe,UAA8B,CAAC,GAAa;AAC/F,QAAM,IAAIA,YAAW,KAAK;AAC1B,QAAM,aAAuB;AAAA,IAC3B,8BAA8B,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYjC;AAEA,QAAM,OAAO,QAAQ,eAAe,CAAC,GAAG,oBAAoB;AAC5D,QAAM,eAAe,QAAQ,UAAU,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,KAAK,CAAC;AAErF,QAAM,UAAU,iBAAiB,CAAC,GAAG,MAAM,GAAG,YAAY,CAAC;AAC3D,aAAW,QAAQ,SAAS;AAC1B,eAAW,KAAK,cAAc,MAAM,EAAE,OAAO,eAAe,gBAAgB,CAAC,CAAC;AAAA,EAChF;AACA,SAAO;AACT;AASO,SAASA,YAAW,MAAsB;AAC/C,oBAAkB,IAAI;AACtB,SAAO,IAAI,IAAI;AACjB;AAOO,SAAS,kBAAkB,MAAoB;AACpD,MAAI,CAAC,2BAA2B,KAAK,IAAI,GAAG;AAC1C,UAAM,IAAI,MAAM,2BAA2B,IAAI,0CAA0C;AAAA,EAC3F;AACF;AAoBO,SAAS,iBAAiB,OAAuB;AACtD,SAAO,IAAI,MAAM,QAAQ,MAAM,IAAI,CAAC;AACtC;;;ACtHO,IAAM,uBAAuB,oBAAI,IAAI;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,SAAS,uBAAuB,OAA8B;AACnE,QAAM,WAAY,MAA8C,aAAa;AAC7E,MAAI,YAAY,qBAAqB,IAAI,QAAQ,EAAG,QAAO;AAC3D,SAAO;AACT;AAUO,IAAMC,oBAAmB;AAEzB,SAAS,oBAAoB,KAAa,cAA4B;AAC3E,MAAI,IAAI,WAAW,GAAG;AACpB,UAAM,IAAI;AAAA,MACR,GAAG,YAAY;AAAA,MACf;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAACA,kBAAiB,KAAK,GAAG,GAAG;AAC/B,UAAM,IAAI;AAAA,MACR,GAAG,YAAY,gCAAgC,GAAG;AAAA,MAGlD;AAAA,IACF;AAAA,EACF;AACF;AAkBO,SAAS,cAAc,UAAqC;AACjE,SAAO,MAAM,SAAS,IAAI,CAAC,QAAQ,MAAM,KAAK,UAAU,GAAG,CAAC,EAAE,KAAK,EAAE;AACvE;AASO,SAAS,SAAS,OAAgB,cAA8B;AACrE,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,UAAM,gBAAgB,uBAAuB,KAAK;AAClD,QAAI,eAAe;AACjB,YAAM,IAAI;AAAA,QACR,GAAG,YAAY,+BAA+B,aAAa;AAAA,QAE3D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO,KAAK,UAAU,KAAK;AAC7B;AAgBO,SAAS,mBACd,KACA,MACA,QACA,cACe;AACf,MAAI,IAAI,WAAW,EAAG,QAAO;AAE7B,QAAM,UAAwB,CAAC;AAC/B,QAAM,OAAqB,CAAC;AAC5B,aAAW,MAAM,IAAK,EAAC,GAAG,SAAS,UAAU,MAAM,KAAK,EAAE;AAE1D,MAAI,OAAO;AAEX,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,eAAe,QAAQ,IAAI,MAAM,GAAG,EAAE,KAAK,IAAI;AACrD,WAAO,eAAe,IAAI,KAAK,YAAY;AAC3C,eAAW,MAAM,SAAS;AACxB,aAAO,KAAK,cAAc,GAAG,IAAI,CAAC;AAAA,IACpC;AAAA,EACF;AAEA,MAAI,KAAK,SAAS,GAAG;AACnB,UAAM,SAAS,KAAK,IAAI,MAAM,YAAY,EAAE,KAAK,IAAI;AACrD,WAAO,YAAY,IAAI,KAAK,MAAM;AAClC,eAAW,MAAM,MAAM;AACrB,aAAO,KAAK,cAAc,GAAG,IAAI,CAAC;AAClC,aAAO,KAAK,SAAS,GAAG,OAAO,YAAY,CAAC;AAAA,IAC9C;AAAA,EACF;AAEA,SAAO;AACT;;;ACxIO,IAAM,qBAAN,MAAM,oBAA6C;AAAA,EACxD,YACkB,SACA,aAChB;AAFgB;AACA;AAAA,EACf;AAAA,EAEH,SAAe;AACb,WAAO,IAAI,KAAK,KAAK,SAAS,CAAC;AAAA,EACjC;AAAA,EAEA,WAAmB;AACjB,WAAO,KAAK,UAAU,MAAO,KAAK,MAAM,KAAK,cAAc,GAAG;AAAA,EAChE;AAAA,EAEA,SAAmD;AACjD,WAAO,EAAE,SAAS,KAAK,SAAS,aAAa,KAAK,YAAY;AAAA,EAChE;AAAA,EAEA,OAAO,WAAW,IAAgC;AAChD,UAAM,UAAU,KAAK,MAAM,KAAK,GAAI;AACpC,UAAM,eAAe,KAAK,UAAU,OAAQ;AAC5C,WAAO,IAAI,oBAAmB,SAAS,WAAW;AAAA,EACpD;AAAA,EAEA,OAAO,MAA0B;AAC/B,WAAO,oBAAmB,WAAW,KAAK,IAAI,CAAC;AAAA,EACjD;AACF;;;AClDO,IAAM,gBAAgB;AAyBtB,IAAM,kBAAkB;;;ACbxB,IAAM,oBAAoB;AAEjC,IAAM,cAAc,oBAAI,IAAI,CAAC,aAAa,YAAY,eAAe,mBAAmB,CAAC;AAGlF,SAAS,cAAc,OAAyB;AACrD,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO;AACxD,QAAM,MAAO,MAAkC,iBAAiB;AAChE,SAAO,OAAO,QAAQ,YAAY,YAAY,IAAI,GAAG;AACvD;;;ACoBO,IAAM,eAA8B,uBAAO,IAAI,uBAAuB;AAkBtE,SAAS,iBAAiB,OAAyC;AACxE,SAAO,UAAU;AACnB;AAMA,IAAM,0BAA0B,oBAAI,IAAI;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AASM,SAAS,gBAAgB,OAAyB;AACvD,MAAI,UAAU,KAAM,QAAO;AAC3B,QAAM,IAAI,OAAO;AACjB,MAAI,MAAM,SAAU,QAAO;AAC3B,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO;AAGjC,MAAI,cAAc,KAAK,EAAG,QAAO;AACjC,QAAM,QAAQ,OAAO,eAAe,KAAK;AACzC,MAAI,UAAU,QAAQ,UAAU,OAAO,UAAW,QAAO;AAEzD,QAAM,OAAQ,MAA8C;AAC5D,MAAI,QAAQ,OAAO,KAAK,SAAS,YAAY,wBAAwB,IAAI,KAAK,IAAI,EAAG,QAAO;AAG5F,SAAO;AACT;AAmCO,SAAS,6BAA6B,QAGpC;AACP,MAAI,OAAO,gBAAgB,UAAa,OAAO,YAAY,QAAW;AACpE,UAAM,IAAI;AAAA,MACR;AAAA,IAGF;AAAA,EACF;AACF;AAqCA,SAAS,uBACP,MACA,MACA,QACA,OACM;AACN,MAAI,SAAS,QAAQ,SAAS,OAAW;AACzC,MAAI,iBAAiB,IAAI,GAAG;AAC1B,UAAM,EAAE,MAAM,OAAO,CAAC;AACtB;AAAA,EACF;AACA,MAAI,OAAO,SAAS,SAAU;AAC9B,MAAI,cAAc,IAAI,EAAG;AACzB,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,6BAAuB,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,OAAO,CAAC,CAAC,GAAG,EAAE,MAAM,SAAS,OAAO,EAAE,GAAG,KAAK;AAAA,IAC1F;AACA;AAAA,EACF;AACA,QAAM,QAAQ,OAAO,eAAe,IAAI;AACxC,MAAI,UAAU,QAAQ,UAAU,OAAO,UAAW;AAClD,QAAM,MAAM;AACZ,aAAW,OAAO,OAAO,KAAK,GAAG,GAAG;AAClC,2BAAuB,IAAI,GAAG,GAAG,CAAC,GAAG,MAAM,GAAG,GAAG,EAAE,MAAM,SAAS,GAAG,KAAK;AAAA,EAC5E;AACF;AAYO,SAAS,eAAe,MAA+B;AAC5D,aAAW,OAAO,MAAM;AACtB,QAAI,QAAQ,IAAI;AACd,YAAM,IAAI;AAAA,QACR,uCAAuC,KACpC,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAC5B,KAAK,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AACF;AA2BO,SAAS,aAAa,MAA6C;AACxE,QAAM,MAAoB,CAAC;AAC3B,OAAK,MAAM,CAAC,GAAG,GAAG;AAClB,SAAO;AACT;AAEA,SAAS,oCACP,KACA,WACM;AACN,yBAAuB,KAAK,WAAW,EAAE,MAAM,OAAO,GAAG,CAAC,EAAE,OAAO,MAAM;AACvE,UAAM,eACJ,UAAU,WAAW,IAAI,WAAW,UAAU,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE,KAAK,KAAK;AACxF,QAAI,OAAO,SAAS,SAAS;AAC3B,YAAM,IAAI;AAAA,QACR,8CAA8C,OAAO,KAAK,4BAChD,YAAY;AAAA,MAIxB;AAAA,IACF;AACA,UAAM,IAAI;AAAA,MACR,qEACU,YAAY;AAAA,IAGxB;AAAA,EACF,CAAC;AACH;AAEA,SAAS,KAAK,MAAe,MAAgB,KAAyB;AAGpE,MAAI,SAAS,OAAW;AACxB,MAAI,iBAAiB,IAAI,GAAG;AAC1B,QAAI,KAAK,WAAW,GAAG;AACrB,YAAM,IAAI,MAAM,+DAA+D;AAAA,IACjF;AACA,mBAAe,IAAI;AACnB,QAAI,KAAK,EAAE,MAAM,CAAC,GAAG,IAAI,GAAG,OAAO,QAAW,QAAQ,KAAK,CAAC;AAC5D;AAAA,EACF;AACA,MAAI,gBAAgB,IAAI,GAAG;AACzB,QAAI,KAAK,WAAW,GAAG;AAGrB,YAAM,IAAI;AAAA,QACR,4DACG,SAAS,OAAO,SAAS,MAAM,QAAQ,IAAI,IAAI,UAAU,OAAO,QACjE;AAAA,MACJ;AAAA,IACF;AAMA,QAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,0CAAoC,MAAM,IAAI;AAAA,IAChD;AACA,mBAAe,IAAI;AACnB,QAAI,KAAK,EAAE,MAAM,CAAC,GAAG,IAAI,GAAG,OAAO,MAAM,QAAQ,MAAM,CAAC;AACxD;AAAA,EACF;AAEA,QAAM,MAAM;AACZ,QAAM,OAAO,OAAO,KAAK,GAAG;AAC5B,MAAI,KAAK,WAAW,GAAG;AAIrB,QAAI,KAAK,SAAS,GAAG;AACnB,qBAAe,IAAI;AACnB,UAAI,KAAK,EAAE,MAAM,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,GAAG,QAAQ,MAAM,CAAC;AAAA,IACxD;AACA;AAAA,EACF;AACA,aAAW,OAAO,MAAM;AACtB,QAAI,QAAQ,mBAAmB;AAC7B,YAAM,QAAQ,KAAK,WAAW,IAAI,WAAW,KAAK,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE,KAAK,KAAK;AAC1F,YAAM,IAAI;AAAA,QACR,kDAAkD,iBAAiB,aAC9D,KAAK;AAAA,MAGZ;AAAA,IACF;AACA,SAAK,IAAI,GAAG,GAAG,CAAC,GAAG,MAAM,GAAG,GAAG,GAAG;AAAA,EACpC;AACF;;;ACzTA,IAAMC,wBAAuB,oBAAI,IAAI;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAWM,SAAS,sBAAsB,MAAe,OAAqB;AACxE,EAAAC,MAAK,MAAM,CAAC,GAAG,KAAK;AACtB;AAEA,SAASA,MAAK,MAAe,MAAyB,OAAqB;AACzE,MAAI,SAAS,QAAQ,SAAS,OAAW;AACzC,MAAI,iBAAiB,IAAI,GAAG;AAC1B,UAAM,IAAI;AAAA,MACR,GAAG,KAAK,0MAGG,WAAW,IAAI,CAAC;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACA,QAAM,IAAI,OAAO;AACjB,MAAI,MAAM,YAAY,MAAM,YAAY;AACtC,UAAM,IAAI;AAAA,MACR,GAAG,KAAK,2CAA2C,CAAC,6CACP,WAAW,IAAI,CAAC;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AACA,MAAI,MAAM,UAAU;AAClB,UAAM,IAAI;AAAA,MACR,GAAG,KAAK,uHAEG,WAAW,IAAI,CAAC;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACA,MAAI,MAAM,SAAU;AACpB,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,MAAAA,MAAK,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,OAAO,CAAC,CAAC,GAAG,KAAK;AAAA,IAC3C;AACA;AAAA,EACF;AAOA,QAAM,MAAM;AACZ,MAAI,OAAO,UAAU,eAAe,KAAK,KAAK,iBAAiB,GAAG;AAChE,UAAM,WAAW,IAAI,iBAAiB;AACtC,UAAM,IAAI;AAAA,MACR,GAAG,KAAK,8CAA8C,iBAAiB,kBACtD,eAAe,QAAQ,CAAC,sGAElC,iBAAiB,4DACb,WAAW,IAAI,CAAC;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AAMA,QAAM,QAAQ,OAAO,eAAe,IAAI;AACxC,MAAI,UAAU,QAAQ,UAAU,OAAO,WAAW;AAChD,UAAM,OAAQ,KAA6C;AAC3D,UAAM,WAAW,QAAQ,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO;AACrE,QAAID,sBAAqB,IAAI,QAAQ,GAAG;AACtC,YAAM,IAAI;AAAA,QACR,GAAG,KAAK,uCAAuC,QAAQ,2HAEJ,WAAW,IAAI,CAAC;AAAA,QACnE;AAAA,MACF;AAAA,IACF;AAKA,QAAI,gBAAgB,KAAM;AAC1B,UAAM,IAAI;AAAA,MACR,GAAG,KAAK,oDAAoD,QAAQ,8FAE3C,WAAW,IAAI,CAAC;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AACA,aAAW,OAAO,OAAO,KAAK,GAAG,GAAG;AAClC,IAAAC,MAAK,IAAI,GAAG,GAAG,CAAC,GAAG,MAAM,GAAG,GAAG,KAAK;AAAA,EACtC;AACF;AAEA,SAAS,WAAW,MAAiC;AACnD,SAAO,KAAK,WAAW,IAAI,WAAW,KAAK,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE,KAAK,KAAK;AACrF;AAEA,SAAS,eAAe,OAAwB;AAC9C,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI,OAAO,UAAU,SAAU,QAAO,KAAK,UAAU,KAAK;AAC1D,MAAI,OAAO,UAAU,YAAY,OAAO,UAAU,aAAa,OAAO,UAAU,UAAU;AACxF,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,SAAO,OAAO;AAChB;;;ACxHA,IAAM,gBAAgB;AACtB,IAAM,oBAAoB;AAgC1B,SAAS,gBAAgB,OAAiC;AACxD,QAAM,SAAS,gBAAgB,KAAK;AACpC,MAAI,QAAQ;AACV,WAAO,EAAE,MAAMC,YAAW,MAAM,EAAE;AAAA,EACpC;AACA,MAAI,MAAM,WAAW,OAAO,GAAG;AAC7B,UAAM,SAAS,MAAM,MAAM,CAAC;AAC5B,eAAW,QAAQ,OAAO,MAAM,GAAG,GAAG;AACpC,0BAAoB,MAAM,iBAAiB;AAAA,IAC7C;AACA,WAAO,EAAE,MAAM,2BAA2B,MAAM,KAAK;AAAA,EACvD;AACA,MAAI,UAAU,QAAQ;AACpB,WAAO,EAAE,MAAM,4BAA4B;AAAA,EAC7C;AACA,QAAM,IAAI,eAAe,+CAA+C,KAAK,IAAI,eAAe;AAClG;AAQA,SAAS,UAAU,OAAyB;AAC1C,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,MACE,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU,WACjB;AACA,WAAO;AAAA,EACT;AACA,MAAI,iBAAiB,KAAM,QAAO,MAAM,QAAQ;AAChD,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,gBAAgB,uBAAuB,KAAK;AAClD,QAAI,eAAe;AACjB,YAAM,IAAI;AAAA,QACR,0CAA0C,aAAa;AAAA,QAGvD;AAAA,MACF;AAAA,IACF;AACA,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B;AACA,SAAO,OAAO,KAAK;AACrB;AAEA,SAAS,cAAc,QAAqB,QAA2B;AACrE,QAAM,EAAE,KAAK,IAAI,gBAAgB,OAAO,KAAK;AAE7C,UAAQ,OAAO,IAAI;AAAA,IACjB,KAAK;AACH,aAAO,KAAK,UAAU,OAAO,KAAK,CAAC;AACnC,aAAO,GAAG,IAAI;AAAA,IAChB,KAAK;AACH,aAAO,KAAK,UAAU,OAAO,KAAK,CAAC;AACnC,aAAO,GAAG,IAAI;AAAA,IAChB,KAAK;AACH,aAAO,KAAK,UAAU,OAAO,KAAK,CAAC;AACnC,aAAO,GAAG,IAAI;AAAA,IAChB,KAAK;AACH,aAAO,KAAK,UAAU,OAAO,KAAK,CAAC;AACnC,aAAO,GAAG,IAAI;AAAA,IAChB,KAAK;AACH,aAAO,KAAK,UAAU,OAAO,KAAK,CAAC;AACnC,aAAO,GAAG,IAAI;AAAA,IAChB,KAAK;AACH,aAAO,KAAK,UAAU,OAAO,KAAK,CAAC;AACnC,aAAO,GAAG,IAAI;AAAA,IAChB,KAAK,MAAM;AACT,YAAM,SAAS,QAAQ,OAAO,OAAO,IAAI;AACzC,YAAM,eAAe,OAAO,IAAI,MAAM,GAAG,EAAE,KAAK,IAAI;AACpD,iBAAW,KAAK,OAAQ,QAAO,KAAK,UAAU,CAAC,CAAC;AAChD,aAAO,GAAG,IAAI,QAAQ,YAAY;AAAA,IACpC;AAAA,IACA,KAAK,UAAU;AACb,YAAM,SAAS,QAAQ,OAAO,OAAO,QAAQ;AAC7C,YAAM,eAAe,OAAO,IAAI,MAAM,GAAG,EAAE,KAAK,IAAI;AACpD,iBAAW,KAAK,OAAQ,QAAO,KAAK,UAAU,CAAC,CAAC;AAChD,aAAO,GAAG,IAAI,YAAY,YAAY;AAAA,IACxC;AAAA,IACA,KAAK,kBAAkB;AACrB,aAAO,KAAK,UAAU,OAAO,KAAK,CAAC;AACnC,aAAO,mCAAmC,IAAI;AAAA,IAChD;AAAA,IACA,KAAK,sBAAsB;AACzB,YAAM,SAAS,QAAQ,OAAO,OAAO,oBAAoB;AACzD,YAAM,eAAe,OAAO,IAAI,MAAM,GAAG,EAAE,KAAK,IAAI;AACpD,iBAAW,KAAK,OAAQ,QAAO,KAAK,UAAU,CAAC,CAAC;AAChD,aAAO,mCAAmC,IAAI,qBAAqB,YAAY;AAAA,IACjF;AAAA,IACA;AACE,YAAM,IAAI;AAAA,QACR,oDAAoD,OAAO,OAAO,EAAE,CAAC;AAAA,QACrE;AAAA,MACF;AAAA,EACJ;AACF;AASO,SAAS,wBAAwB,SAAwB,QAA6B;AAC3F,SAAO,QAAQ,IAAI,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC;AACpD;AAEA,SAAS,QAAQ,OAAgB,IAAuB;AACtD,MAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAAG;AAC/C,UAAM,IAAI,eAAe,aAAa,EAAE,sCAAsC,eAAe;AAAA,EAC/F;AACA,SAAO;AACT;AAEA,SAAS,eAAe,SAAmC,SAA4B;AACrF,MAAI,CAAC,SAAS,QAAS,QAAO;AAC9B,QAAM,EAAE,OAAO,UAAU,IAAI,QAAQ;AACrC,QAAM,EAAE,KAAK,IAAI,gBAAgB,KAAK;AACtC,QAAM,MAAM,cAAc,SAAS,SAAS;AAC5C,SAAO,aAAa,IAAI,IAAI,GAAG;AACjC;AAEA,SAAS,aAAa,SAAmC,QAA2B;AAClF,MAAI,SAAS,UAAU,OAAW,QAAO;AACzC,SAAO,KAAK,QAAQ,KAAK;AACzB,SAAO;AACT;AAUO,SAAS,cACd,OACA,SACA,SACmB;AACnB,QAAM,SAAoB,CAAC;AAC3B,QAAM,aAAuB,CAAC;AAE9B,aAAW,KAAK,SAAS;AACvB,eAAW,KAAK,cAAc,GAAG,MAAM,CAAC;AAAA,EAC1C;AAEA,QAAM,QAAQ,WAAW,SAAS,IAAI,UAAU,WAAW,KAAK,OAAO,CAAC,KAAK;AAC7E,MAAI,MAAM,iBAAiBA,YAAW,KAAK,CAAC,GAAG,KAAK;AACpD,SAAO,eAAe,SAAS,MAAM;AACrC,SAAO,aAAa,SAAS,MAAM;AAEnC,SAAO,EAAE,KAAK,OAAO;AACvB;AAqBO,SAAS,cAAc,OAAe,QAAyC;AACpF,MAAI,OAAO,QAAQ,WAAW,GAAG;AAC/B,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,QAAM,YAAY,OAAO,aAAa;AAItC,QAAM,UAAU,gBAAgB,MAAM,EAAE;AACxC,QAAM,UAAU,gBAAgB,MAAM,EAAE;AACxC,QAAM,WAAW,gBAAgB,OAAO,EAAE;AAC1C,QAAM,WAAW,gBAAgB,OAAO,EAAE;AAC1C,QAAM,aAAa,gBAAgB,SAAS,EAAE;AAC9C,QAAM,eAAe,cAAc,YAAY,UAAU;AAEzD,QAAM,YAAuB,CAAC,OAAO,OAAO;AAC5C,QAAM,aAAuB,CAAC,GAAG,UAAU,MAAM;AAEjD,QAAM,eAAe,OAAO,QAAQ,IAAI,MAAM,GAAG,EAAE,KAAK,IAAI;AAC5D,aAAW,KAAK,GAAG,YAAY,QAAQ,YAAY,GAAG;AACtD,aAAW,OAAO,OAAO,QAAS,WAAU,KAAK,GAAG;AAEpD,MAAI,OAAO,UAAU,QAAW;AAC9B,eAAW,KAAK,GAAG,QAAQ,MAAM;AACjC,cAAU,KAAK,OAAO,KAAK;AAAA,EAC7B;AACA,MAAI,OAAO,UAAU,QAAW;AAC9B,eAAW,KAAK,GAAG,QAAQ,MAAM;AACjC,cAAU,KAAK,OAAO,KAAK;AAAA,EAC7B;AAKA,MAAI,OAAO,YAAY,eAAe;AACpC,eAAW,KAAK,GAAG,OAAO,OAAO,OAAO,EAAE;AAAA,EAC5C;AAEA,MAAI,MAAM,iBAAiBA,YAAW,KAAK,CAAC,UAAU,WAAW,KAAK,OAAO,CAAC;AAE9E,MAAI,OAAO,SAAS;AAClB,WAAO,eAAe,EAAE,SAAS,OAAO,QAAQ,GAAG,SAAS;AAAA,EAC9D;AACA,MAAI,OAAO,mBAAmB,QAAW;AACvC,UAAM,aAAa,OAAO,QAAQ,SAAS,OAAO;AAClD,WAAO;AACP,cAAU,KAAK,UAAU;AAAA,EAC3B;AACA,SAAO,EAAE,KAAK,QAAQ,UAAU;AAClC;AAOO,SAAS,qBAAqB,OAAe,YAAyC;AAC3F,MAAI,WAAW,WAAW,GAAG;AAC3B,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,QAAM,eAAe,WAAW,IAAI,MAAM,GAAG,EAAE,KAAK,IAAI;AACxD,QAAM,YAAuB,CAAC,aAAa;AAC3C,aAAW,OAAO,WAAY,WAAU,KAAK,GAAG;AAIhD,QAAM,UAAU,gBAAgB,MAAM,EAAE;AACxC,QAAM,UAAU,gBAAgB,MAAM,EAAE;AACxC,QAAM,aAAa,gBAAgB,SAAS,EAAE;AAE9C,SAAO;AAAA,IACL,KACE,iBAAiBA,YAAW,KAAK,CAAC,UACzB,UAAU,YAAY,OAAO,MAAM,OAAO,QAAQ,OAAO,QAAQ,YAAY;AAAA,IACxF,QAAQ;AAAA,EACV;AACF;AAMO,SAAS,qBAAqB,OAAe,OAAkC;AACpF,SAAO;AAAA,IACL,KAAK,iBAAiBA,YAAW,KAAK,CAAC;AAAA,IACvC,QAAQ,CAAC,KAAK;AAAA,EAChB;AACF;AAmCA,SAAS,yBAAyB,OAAuB;AACvD,MAAI,SAAS,gBAAiB,QAAO;AACrC,MAAI,UAAU,UAAU,MAAM,WAAW,OAAO,EAAG,QAAO;AAC1D,SAAO,QAAQ,KAAK;AACtB;AA0BO,SAAS,0BACd,OACA,QACA,SACA,SAC6D;AAC7D,MAAI,OAAO,WAAW,GAAG;AACvB,UAAM,IAAI;AAAA,MACR;AAAA,MAEA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,eAAyB,CAAC;AAChC,aAAW,KAAK,QAAQ;AACtB,QAAI,CAAC,KAAK,IAAI,CAAC,GAAG;AAChB,WAAK,IAAI,CAAC;AACV,mBAAa,KAAK,CAAC;AAAA,IACrB;AAAA,EACF;AAEA,QAAM,cAAwB,CAAC;AAC/B,QAAM,UAAiC,CAAC;AACxC,WAAS,MAAM,GAAG,MAAM,aAAa,QAAQ,OAAO;AAClD,UAAM,QAAQ,aAAa,GAAG;AAC9B,UAAM,YAAY,yBAAyB,KAAK;AAChD,UAAM,EAAE,KAAK,IAAI,gBAAgB,SAAS;AAK1C,UAAM,QAAQ,iBAAiB,KAAK;AACpC,gBAAY,KAAK,GAAG,IAAI,OAAO,KAAK,EAAE;AAEtC,QAAI;AACJ,QAAI;AACJ,QAAI,cAAc,QAAQ;AACxB,aAAO;AAAA,IACT,WAAW,UAAU,WAAW,OAAO,GAAG;AACxC,aAAO;AAGP,sBAAgB,UAAU,GAAG;AAC7B,YAAM,YAAY,iBAAiB,aAAa;AAChD,kBAAY,KAAK,wBAAwB,UAAU,MAAM,CAAC,CAAC,SAAS,SAAS,EAAE;AAAA,IACjF,OAAO;AACL,UAAI,cAAc,IAAK,QAAO;AAAA,eACrB,cAAc,eAAe,cAAc,YAAa,QAAO;AAAA,UACnE,QAAO;AAAA,IACd;AACA,YAAQ,KAAK,EAAE,OAAO,MAAM,WAAW,cAAc,CAAC;AAAA,EACxD;AAEA,QAAM,SAAoB,CAAC;AAC3B,QAAM,aAAuB,CAAC;AAC9B,aAAW,KAAK,SAAS;AACvB,eAAW,KAAK,cAAc,GAAG,MAAM,CAAC;AAAA,EAC1C;AAEA,QAAM,QAAQ,WAAW,SAAS,IAAI,UAAU,WAAW,KAAK,OAAO,CAAC,KAAK;AAC7E,MAAI,MAAM,UAAU,YAAY,KAAK,IAAI,CAAC,SAASA,YAAW,KAAK,CAAC,GAAG,KAAK;AAC5E,SAAO,eAAe,SAAS,MAAM;AACrC,SAAO,aAAa,SAAS,MAAM;AAEnC,SAAO,EAAE,MAAM,EAAE,KAAK,OAAO,GAAG,QAAQ;AAC1C;AAQO,SAAS,mBACd,KACA,SACyB;AACzB,QAAM,MAA+B,CAAC;AACtC,aAAW,KAAK,SAAS;AACvB,UAAM,MAAM,IAAI,EAAE,KAAK;AACvB,YAAQ,EAAE,MAAM;AAAA,MACd,KAAK;AACH,YAAI,EAAE,KAAK,IAAI,QAAQ,QAAQ,QAAQ,SAAY,OAAO,OAAO,GAAG;AACpE;AAAA,MACF,KAAK;AACH,YAAI,QAAQ,QAAQ,QAAQ,QAAW;AACrC,cAAI,EAAE,KAAK,IAAI;AAAA,QACjB,WAAW,OAAO,QAAQ,UAAU;AAClC,cAAI,EAAE,KAAK,IAAI,OAAO,GAAG;AAAA,QAC3B,WAAW,OAAO,QAAQ,UAAU;AAClC,cAAI,EAAE,KAAK,IAAI;AAAA,QACjB,OAAO;AACL,cAAI,EAAE,KAAK,IAAI,OAAO,GAAG;AAAA,QAC3B;AACA;AAAA,MACF,KAAK,qBAAqB;AACxB,cAAM,KAAK,qBAAqB,GAAG;AACnC,YAAI,EAAE,KAAK,IAAI,mBAAmB,WAAW,EAAE;AAC/C;AAAA,MACF;AAAA,MACA,KAAK;AACH,YAAI,QAAQ,QAAQ,QAAQ,UAAa,QAAQ,IAAI;AACnD,cAAI,EAAE,KAAK,IAAI,CAAC;AAAA,QAClB,OAAO;AACL,cAAI,EAAE,KAAK,IAAI,KAAK,MAAM,GAAa;AAAA,QACzC;AACA;AAAA,MACF,KAAK,QAAQ;AAGX,cAAM,IAAI,IAAI,EAAE,SAAU;AAC1B,YAAI,QAAQ,QAAQ,QAAQ,QAAW;AACrC,cAAI,EAAE,KAAK,IAAI;AAAA,QACjB,WAAW,MAAM,YAAY,MAAM,SAAS;AAC1C,cAAI,EAAE,KAAK,IAAI,OAAO,QAAQ,WAAW,KAAK,MAAM,GAAG,IAAI;AAAA,QAC7D,WAAW,MAAM,aAAa,OAAO,QAAQ,UAAU;AACrD,cAAI,EAAE,KAAK,IAAI,OAAO,GAAG;AAAA,QAC3B,OAAO;AACL,cAAI,EAAE,KAAK,IAAI;AAAA,QACjB;AACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAaO,SAAS,iBACd,OACA,MACA,SACgD;AAChD,QAAM,UAAU,OAAO,KAAK,IAAI;AAChC,MAAI,QAAQ,WAAW,GAAG;AACxB,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,cAAwB,CAAC;AAC/B,aAAW,SAAS,SAAS;AAC3B,UAAM,EAAE,IAAI,MAAM,IAAI,KAAK,KAAK;AAIhC,wBAAoB,OAAO,iBAAiB;AAC5C,QAAI,OAAO,SAAS;AAElB,UAAI,UAAU,QAAW;AACvB,cAAM,IAAI;AAAA,UACR,cAAc,KAAK;AAAA,UAEnB;AAAA,QACF;AAAA,MACF;AACA,kBAAY,KAAK,eAAeA,YAAW,KAAK,CAAC,EAAE;AACnD;AAAA,IACF;AACA,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;AAAA,QACR,cAAc,KAAK,SAAS,EAAE;AAAA,QAC9B;AAAA,MACF;AAAA,IACF;AACA,UAAM,EAAE,KAAK,IAAI,gBAAgB,KAAK;AACtC,UAAM,UAAU,QAAQ,IAAI;AAC5B,QAAI,OAAO,MAAO,aAAY,KAAK,OAAO,OAAO,QAAQA,YAAW,KAAK,CAAC,EAAE;AAAA,aACnE,OAAO,MAAO,aAAY,KAAK,OAAO,OAAO,QAAQA,YAAW,KAAK,CAAC,EAAE;AAAA,aACxE,OAAO,MAAO,aAAY,KAAK,OAAO,OAAO,QAAQA,YAAW,KAAK,CAAC,EAAE;AAAA,aACxE,OAAO,MAAO,aAAY,KAAK,OAAO,OAAO,QAAQA,YAAW,KAAK,CAAC,EAAE;AAAA;AAE/E,YAAM,IAAI;AAAA,QACR,iDAAiD,OAAO,EAAE,CAAC;AAAA,QAC3D;AAAA,MACF;AAAA,EACJ;AAEA,QAAM,SAAoB,CAAC;AAC3B,QAAM,aAAuB,CAAC;AAC9B,aAAW,KAAK,SAAS;AACvB,eAAW,KAAK,cAAc,GAAG,MAAM,CAAC;AAAA,EAC1C;AACA,QAAM,QAAQ,WAAW,SAAS,IAAI,UAAU,WAAW,KAAK,OAAO,CAAC,KAAK;AAC7E,QAAM,MAAM,UAAU,YAAY,KAAK,IAAI,CAAC,SAASA,YAAW,KAAK,CAAC,GAAG,KAAK;AAC9E,SAAO,EAAE,MAAM,EAAE,KAAK,OAAO,GAAG,QAAQ;AAC1C;AAcO,SAAS,WACd,OACA,OACA,QACA,WACA,MACmB;AAInB,wBAAsB,OAAO,MAAM,aAAa;AAChD,MAAI,SAAS,WAAW;AACtB,UAAMC,OAAM,0BAA0BD,YAAW,KAAK,CAAC;AAAA;AAAA;AAGvD,UAAM,SAAoB;AAAA,MACxB;AAAA,MACA,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,MACP,KAAK,UAAU,OAAO,QAAQ,CAAC,CAAC;AAAA,MAChC,OAAO,KAAK;AAAA,MACZ;AAAA,MACA;AAAA,IACF;AACA,WAAO,EAAE,KAAAC,MAAK,OAAO;AAAA,EACvB;AAEA,QAAM,eAA0B;AAAA,IAC9B;AAAA,IACA,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,KAAK,UAAU,OAAO,QAAQ,CAAC,CAAC;AAAA,IAChC,OAAO,KAAK;AAAA,IACZ;AAAA,IACA;AAAA,EACF;AAEA,QAAM,MAAM,aAAa,OAAO,QAAQ,CAAC,CAAC;AAC1C,QAAM,eAA0B,CAAC;AACjC,QAAM,WACJ,mBAAmB,KAAK,0BAA0B,cAAc,iBAAiB,KACjF;AAKF,QAAM,MAAM,eAAeD,YAAW,KAAK,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAS7B,QAAQ;AAAA;AAAA;AAAA;AAKvB,SAAO,EAAE,KAAK,QAAQ,CAAC,GAAG,cAAc,GAAG,YAAY,EAAE;AAC3D;AAYO,SAAS,cACd,OACA,OACA,QACA,WACmB;AACnB,+BAA6B,MAAM;AACnC,QAAM,aAAuB,CAAC;AAC9B,QAAM,SAAoB,CAAC;AAE3B,MAAI,OAAO,aAAa;AACtB,0BAAsB,OAAO,aAAa,aAAa;AACvD,eAAW,KAAK,YAAY;AAC5B,WAAO,KAAK,KAAK,UAAU,OAAO,WAAW,CAAC;AAAA,EAChD,WAAW,OAAO,WAAW,OAAO,QAAQ,SAAS,GAAG;AACtD,eAAW,MAAM,OAAO,SAAS;AAC/B,UAAI,CAAC,GAAG,OAAQ,uBAAsB,GAAG,OAAO,aAAa;AAAA,IAC/D;AACA,UAAM,OAAO;AAAA,MACX,OAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,QAAI,SAAS,MAAM;AACjB,iBAAW,KAAK,YAAY,IAAI,EAAE;AAAA,IACpC;AAAA,EACF;AAEA,MAAI,OAAO,MAAM,QAAW;AAC1B,eAAW,KAAK,SAAS;AACzB,WAAO,KAAK,OAAO,CAAC;AAAA,EACtB;AAEA,aAAW,KAAK,kBAAkB;AAClC,SAAO,KAAK,SAAS;AAErB,SAAO,KAAK,KAAK;AAEjB,SAAO;AAAA,IACL,KAAK,UAAUA,YAAW,KAAK,CAAC,QAAQ,WAAW,KAAK,IAAI,CAAC;AAAA,IAC7D;AAAA,EACF;AACF;AAEO,SAAS,cAAc,OAAe,OAAkC;AAC7E,SAAO;AAAA,IACL,KAAK,eAAeA,YAAW,KAAK,CAAC;AAAA,IACrC,QAAQ,CAAC,KAAK;AAAA,EAChB;AACF;AAUO,SAAS,kBAAkB,OAAe,SAA2C;AAC1F,QAAM,SAAoB,CAAC;AAC3B,QAAM,aAAuB,CAAC;AAC9B,aAAW,KAAK,SAAS;AACvB,eAAW,KAAK,cAAc,GAAG,MAAM,CAAC;AAAA,EAC1C;AACA,QAAM,QAAQ,WAAW,SAAS,IAAI,UAAU,WAAW,KAAK,OAAO,CAAC,KAAK;AAC7E,SAAO;AAAA,IACL,KAAK,eAAeA,YAAW,KAAK,CAAC,GAAG,KAAK;AAAA,IAC7C;AAAA,EACF;AACF;AAYO,SAAS,kBACd,OACA,SACA,WACA,WACmB;AACnB,QAAM,UAAU,aAAa,SAAS;AACtC,MAAI,QAAQ,WAAW,GAAG;AACxB,UAAM,IAAI;AAAA,MACR;AAAA,MAGA;AAAA,IACF;AAAA,EACF;AACA,aAAW,MAAM,SAAS;AACxB,QAAI,CAAC,GAAG,OAAQ,uBAAsB,GAAG,OAAO,aAAa;AAAA,EAC/D;AACA,QAAM,YAAuB,CAAC;AAC9B,QAAM,OAAO,mBAAmB,SAAS,0BAA0B,WAAW,iBAAiB;AAC/F,MAAI,SAAS,MAAM;AACjB,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,QAAM,aAAuB,CAAC,YAAY,IAAI,IAAI,kBAAkB;AACpE,YAAU,KAAK,SAAS;AAExB,QAAM,cAAyB,CAAC;AAChC,QAAM,aAAuB,CAAC;AAC9B,aAAW,KAAK,SAAS;AACvB,eAAW,KAAK,cAAc,GAAG,WAAW,CAAC;AAAA,EAC/C;AACA,QAAM,QAAQ,WAAW,SAAS,IAAI,UAAU,WAAW,KAAK,OAAO,CAAC,KAAK;AAE7E,SAAO;AAAA,IACL,KAAK,UAAUA,YAAW,KAAK,CAAC,QAAQ,WAAW,KAAK,IAAI,CAAC,GAAG,KAAK;AAAA,IACrE,QAAQ,CAAC,GAAG,WAAW,GAAG,WAAW;AAAA,EACvC;AACF;AAuBO,SAAS,YAAY,KAAiD;AAC3E,QAAM,aAAa,IAAI;AACvB,QAAM,OAAO,aAAc,KAAK,MAAM,UAAU,IAAgC,CAAC;AAEjF,QAAM,YAAY,qBAAqB,IAAI,UAAU;AACrD,QAAM,YAAY,qBAAqB,IAAI,UAAU;AAErD,QAAM,SAAkC;AAAA,IACtC,OAAO,IAAI;AAAA,IACX,MAAM,IAAI;AAAA,IACV,SAAS,IAAI;AAAA,IACb,OAAO,IAAI;AAAA,IACX,MAAM,IAAI;AAAA,IACV;AAAA,IACA,WAAW,mBAAmB,WAAW,SAAS;AAAA,IAClD,WAAW,mBAAmB,WAAW,SAAS;AAAA,EACpD;AAEA,MAAI,IAAI,MAAM,QAAQ,IAAI,MAAM,QAAW;AACzC,WAAO,IAAI,OAAO,IAAI,CAAC;AAAA,EACzB;AACA,SAAO;AACT;AAUO,SAAS,qBAAqB,OAAwB;AAC3D,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,KAAK;AAClD,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,IAAI,OAAO,KAAK;AACtB,QAAI,OAAO,SAAS,CAAC,EAAG,QAAO;AAAA,EACjC;AACA,QAAM,IAAI;AAAA,IACR,gDAAgD,OAAO,KAAK,KAAK,OAAO,KAAK,CAAC;AAAA,IAC9E;AAAA,EACF;AACF;;;AC50BO,IAAM,sBAAsB;AAG5B,IAAM,iBAAiB;AAE9B,IAAME,qBAAoB;AAM1B,IAAM,kBAAuC,oBAAI,IAAI;AAAA,EACnD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,SAAS,aAAa,OAAuB;AAClD,SAAO,GAAG,KAAK;AACjB;AAGO,SAAS,gBAAgB,OAAuB;AACrD,SAAO,GAAG,KAAK;AACjB;AAOA,SAAS,mBAAmB,SAAyB;AACnD,SACE,4EAC2B,OAAO;AAEtC;AAaO,SAAS,YAAY,OAAyB;AACnD,QAAM,IAAIC,YAAW,KAAK;AAC1B,QAAM,MAAMA,YAAW,aAAa,KAAK,CAAC;AAC1C,QAAM,MAAMA,YAAW,gBAAgB,KAAK,CAAC;AAC7C,QAAM,WAAW,qBAAqB,GAAG;AAKzC,QAAM,cACJ,iBAAiB,GAAG,mEACe,GAAG;AAAA,gBACrB,GAAG,kBAAkB,QAAQ;AAAA,gBAC7B,GAAG,4BAA4B,QAAQ,KAAK,mBAAmB,YAAY,CAAC;AAAA;AAC/F,SAAO;AAAA,IACL,8BAA8B,GAAG;AAAA;AAAA;AAAA;AAAA,IAIjC,sCAAsC,GAAG;AAAA,IACzC,gCAAgCA,YAAW,GAAG,KAAK,SAAS,CAAC,oBAAoB,CAAC;AAAA,EAAW,WAAW;AAAA,IACxG,gCAAgCA,YAAW,GAAG,KAAK,SAAS,CAAC,oBAAoB,CAAC;AAAA,EAAW,WAAW;AAAA,IACxG,gCAAgCA,YAAW,GAAG,KAAK,SAAS,CAAC,oBAAoB,CAAC;AAAA,gBACtE,GAAG,oCAAoC,GAAG;AAAA,gBAC1C,GAAG;AAAA;AAAA,EAEjB;AACF;AAcO,SAAS,uBAAuB,OAAyB;AAC9D,QAAM,IAAIA,YAAW,KAAK;AAC1B,QAAM,MAAMA,YAAW,aAAa,KAAK,CAAC;AAC1C,QAAM,MAAMA,YAAW,gBAAgB,KAAK,CAAC;AAC7C,SAAO;AAAA,IACL,eAAe,GAAG;AAAA,2BACK,GAAG,gBAAgB,CAAC;AAAA;AAAA;AAAA,IAG3C,eAAe,GAAG,gDAAgD,CAAC;AAAA,IACnE,yBAAyB,GAAG,oCAAoC,CAAC;AAAA,IACjE,eAAe,GAAG;AAAA,uBACC,mBAAmB,UAAU,CAAC;AAAA,aACxC,CAAC,WAAW,GAAG;AAAA,+CACmB,GAAG;AAAA,EAChD;AACF;AAMO,SAAS,oBAAoB,OAAyB;AAC3D,SAAO,CAAC,GAAG,YAAY,KAAK,GAAG,GAAG,uBAAuB,KAAK,CAAC;AACjE;AAQO,SAAS,yBAAyB,OAAe,OAAuB;AAC7E,MAAI,gBAAgB,IAAI,KAAK,GAAG;AAC9B,UAAM,IAAI;AAAA,MACR,kBAAkB,KAAK,KAAK,KAAK,mGAC8B,KAAK;AAAA,MAEpE;AAAA,IACF;AAAA,EACF;AACA,MAAI,UAAU,UAAU,MAAM,WAAW,OAAO,EAAG,QAAO;AAC1D,SAAO,QAAQ,KAAK;AACtB;AAMO,SAAS,2BAA2B,OAAuB;AAChE,MAAI,gBAAgB,IAAI,KAAK,GAAG;AAC9B,UAAM,IAAI;AAAA,MACR,4BAA4B,KAAK,+GAEtB,KAAK;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AACA,MAAI,UAAU,UAAU,MAAM,WAAW,OAAO,EAAG,QAAO;AAC1D,SAAO,QAAQ,KAAK;AACtB;AAOA,SAAS,mBAAmB,QAKV;AAChB,QAAM,UAAyB,CAAC;AAChC,MAAI,OAAO,MAAO,SAAQ,KAAK,EAAE,OAAO,SAAS,IAAI,MAAM,OAAO,OAAO,MAAM,CAAC;AAChF,MAAI,OAAO,QAAS,SAAQ,KAAK,EAAE,OAAO,WAAW,IAAI,MAAM,OAAO,OAAO,QAAQ,CAAC;AACtF,MAAI,OAAO,MAAO,SAAQ,KAAK,EAAE,OAAO,SAAS,IAAI,MAAM,OAAO,OAAO,MAAM,CAAC;AAChF,aAAW,UAAU,OAAO,SAAS,CAAC,GAAG;AACvC,UAAM,QACJ,gBAAgB,IAAI,OAAO,KAAK,KAAK,OAAO,MAAM,WAAW,OAAO,IAChE,OAAO,QACP,QAAQ,OAAO,KAAK;AAC1B,YAAQ,KAAK,EAAE,OAAO,IAAI,OAAO,IAAI,OAAO,OAAO,MAAM,CAAC;AAAA,EAC5D;AACA,SAAO;AACT;AAeO,SAAS,sBACd,OACA,QACmB;AACnB,MAAI,OAAO,OAAO,UAAU,YAAY,OAAO,MAAM,WAAW,GAAG;AACjE,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,OAAO,UAAU,OAAO,KAAK,KAAK,OAAO,SAAS,GAAG;AACxD,UAAM,IAAI;AAAA,MACR,2DAA2D,OAAO,KAAK;AAAA,MACvE;AAAA,IACF;AAAA,EACF;AACA,QAAM,mBAAmB,OAAO,QAAQ,IAAI,CAAC,MAAM,2BAA2B,CAAC,CAAC;AAChF,MAAI,qBAAqB,UAAa,iBAAiB,SAAS,GAAG;AACjE,UAAM,IAAI;AAAA,MACR;AAAA,MAGA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,IAAIA,YAAW,KAAK;AAC1B,QAAM,MAAMA,YAAW,aAAa,KAAK,CAAC;AAC1C,QAAM,MAAMA,YAAW,gBAAgB,KAAK,CAAC;AAE7C,QAAM,YAAuB,CAAC,OAAO,KAAK;AAC1C,QAAM,aAAuB,CAAC,GAAG,GAAG,UAAU;AAC9C,aAAW,KAAK,GAAG,wBAAwB,mBAAmB,MAAM,GAAG,SAAS,CAAC;AACjF,YAAU,KAAK,OAAO,KAAK;AAE3B,QAAM,MACJ,UAAU,CAAC,WAAW,GAAG,SACjB,GAAG,OAAO,GAAG,WAAW,GAAG,eAC3B,CAAC,OAAO,CAAC,eAAe,GAAG,mBAC1B,WAAW,KAAK,OAAO,CAAC,kBAChB,GAAG,UAAU,CAAC;AACjC,SAAO,EAAE,KAAK,QAAQ,UAAU;AAClC;AA4BA,IAAM,8BAAiD;AAAA,EACrD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOO,SAAS,iBAAiB,SAA0B;AACzD,QAAM,QAAQ,QAAQ,YAAY;AAClC,SAAO,4BAA4B,KAAK,CAAC,QAAQ,MAAM,SAAS,GAAG,CAAC;AACtE;AAEA,IAAM,oBAAyC,oBAAI,IAAI,CAAC,aAAa,UAAU,aAAa,CAAC;AAY7F,SAAS,cAAc,IAAkD;AACvE,MAAI,MAAM,QAAQ,EAAE,EAAG,QAAO;AAC9B,MAAI,OAAQ,GAA6B,YAAY,YAAY;AAC/D,WAAQ,GAA+B,QAAQ;AAAA,EACjD;AACA,QAAM,IAAI;AAAA,IACR;AAAA,IACA;AAAA,EACF;AACF;AAyBO,SAAS,mBAAmB,OAAe,QAAgD;AAChG,QAAM,MAAM,cAAc,OAAO,WAAW;AAC5C,MAAI,IAAI,WAAW,GAAG;AACpB,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,OAAO,UAAU,OAAO,KAAK,KAAK,OAAO,SAAS,KAAK,OAAO,QAAQ,KAAM;AAC/E,UAAM,IAAI;AAAA,MACR,oEAA+D,OAAO,KAAK;AAAA,MAC3E;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,kBAAkB,IAAI,OAAO,eAAe,GAAG;AAClD,UAAM,IAAI;AAAA,MACR,2CAA2C,OAAO,OAAO,eAAe,CAAC;AAAA,MAEzE;AAAA,IACF;AAAA,EACF;AAEA,QAAM,cAAc,yBAAyB,eAAe,OAAO,WAAW;AAC9E,MAAI;AACJ,MAAI,gBAAgB,QAAQ;AAC1B,iBAAa;AAAA,EACf,OAAO;AACL,UAAM,SAAS,YAAY,MAAM,QAAQ,MAAM;AAC/C,eAAW,QAAQ,OAAO,MAAM,GAAG,GAAG;AACpC,0BAAoB,MAAMD,kBAAiB;AAAA,IAC7C;AACA,iBAAa,2BAA2B,MAAM;AAAA,EAChD;AAEA,MAAI,eAAgC;AACpC,MAAI,OAAO,wBAAwB,QAAW;AAC5C,UAAM,aAAa,yBAAyB,uBAAuB,OAAO,mBAAmB;AAC7F,QAAI,eAAe,QAAQ;AACzB,YAAM,IAAI;AAAA,QACR;AAAA,QAEA;AAAA,MACF;AAAA,IACF;AACA,mBAAe,WAAW,MAAM,QAAQ,MAAM,EAAE,MAAM,GAAG;AACzD,eAAW,QAAQ,cAAc;AAC/B,0BAAoB,MAAMA,kBAAiB;AAAA,IAC7C;AAAA,EACF;AAKA,QAAM,YAAuB,CAAC,KAAK,UAAU,GAAG,GAAG,OAAO,eAAe;AACzE,QAAM,aAAa,wBAAwB,mBAAmB,MAAM,GAAG,SAAS;AAChF,QAAM,aAAa,WAAW,SAAS,IAAI,UAAU,WAAW,KAAK,OAAO,CAAC,KAAK;AAClF,QAAM,OAAOC,YAAW,cAAc;AACtC,QAAM,aAAa,OAAO,oBAAoB;AAE9C,MAAI,MACF,4BACa,mBAAmB,IAAI,UAAU,cAAc,IAAI,SACxDA,YAAW,KAAK,CAAC,GAAG,UAAU,WAC3B,IAAI;AACjB,MAAI,OAAO,sBAAsB,QAAW;AAC1C,WAAO,QAAQ,IAAI,IAAI,aAAa,OAAO,IAAI;AAC/C,cAAU,KAAK,OAAO,iBAAiB;AAAA,EACzC;AACA,SAAO,aAAa,IAAI,IAAI,aAAa,SAAS,KAAK;AACvD,YAAU,KAAK,OAAO,KAAK;AAE3B,SAAO,EAAE,MAAM,EAAE,KAAK,QAAQ,UAAU,GAAG,aAAa;AAC1D;AAKA,IAAI,gBAA+B;AACnC,IAAI,eAAgC;AAe7B,SAAS,sBACd,YACA,WACA,SACe;AACf,MACE,OAAO,eAAe,YACtB,OAAO,cAAc,YACrB,OAAO,YAAY,UACnB;AACA,WAAO;AAAA,EACT;AACA,MAAI;AACJ,MAAI,kBAAkB,aAAa,iBAAiB,MAAM;AACxD,YAAQ;AAAA,EACV,OAAO;AACL,QAAI;AACJ,QAAI;AACF,eAAS,KAAK,MAAM,SAAS;AAAA,IAC/B,QAAQ;AACN,aAAO;AAAA,IACT;AACA,QAAI,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO;AACnC,YAAQ;AACR,oBAAgB;AAChB,mBAAe;AAAA,EACjB;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,UAAU;AAAA,EAChC,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,OAAO,WAAW,MAAM,OAAQ,QAAO;AAErE,MAAI,MAAM;AACV,MAAI,QAAQ;AACZ,MAAI,aAAa;AACjB,MAAI,YAAY;AAChB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,IAAI,OAAO,CAAC;AAClB,UAAM,IAAI,MAAM,CAAC;AACjB,QAAI,OAAO,MAAM,YAAY,CAAC,OAAO,SAAS,CAAC,EAAG,QAAO;AACzD,QAAI,OAAO,MAAM,YAAY,CAAC,OAAO,SAAS,CAAC,EAAG,QAAO;AACzD,WAAO,IAAI;AACX,UAAM,OAAO,IAAI;AACjB,aAAS,OAAO;AAChB,kBAAc,IAAI;AAClB,iBAAa,IAAI;AAAA,EACnB;AAEA,MAAI;AACJ,UAAQ,SAAS;AAAA,IACf,KAAK;AACH,eAAS,KAAK,KAAK,KAAK;AACxB;AAAA,IACF,KAAK,UAAU;AACb,YAAM,QAAQ,KAAK,KAAK,UAAU,IAAI,KAAK,KAAK,SAAS;AACzD,UAAI,UAAU,EAAG,QAAO;AACxB,eAAS,IAAI,MAAM;AACnB;AAAA,IACF;AAAA,IACA,KAAK;AACH,eAAS;AACT;AAAA,IACF;AACE,aAAO;AAAA,EACX;AACA,SAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAC5C;AAOO,SAAS,YACd,MACA,MACA,OACM;AACN,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,KAAK,SAAS,GAAG,KAAK;AACxC,UAAM,MAAM,KAAK,CAAC;AAClB,UAAM,OAAO,OAAO,GAAG;AACvB,QAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,MAAM,QAAQ,IAAI,GAAG;AACpE,YAAM,UAAmC,CAAC;AAC1C,aAAO,GAAG,IAAI;AACd,eAAS;AAAA,IACX,OAAO;AACL,eAAS;AAAA,IACX;AAAA,EACF;AACA,SAAO,KAAK,KAAK,SAAS,CAAC,CAAC,IAAI;AAClC;AAgBO,SAAS,sBACd,WACA,eACA,WACU;AACV,QAAM,QAAQ,IAAI,IAAI,SAAS;AAC/B,QAAM,kBAAkB,IAAI,IAAI,aAAa;AAC7C,QAAM,iBAAiB,GAAG,SAAS;AACnC,QAAM,UAAoB,CAAC;AAC3B,aAAW,QAAQ,OAAO;AACxB,QAAI,OAAsB;AAC1B,QAAI,KAAK,SAAS,UAAU,EAAG,QAAO,KAAK,MAAM,GAAG,CAAC,WAAW,MAAM;AAAA,aAC7D,KAAK,SAAS,MAAM,EAAG,QAAO,KAAK,MAAM,GAAG,CAAC,OAAO,MAAM;AACnE,QAAI,SAAS,QAAQ,CAAC,KAAK,WAAW,cAAc,EAAG;AACvD,QAAI,gBAAgB,IAAI,IAAI,EAAG;AAC/B,QAAI,MAAM,IAAI,IAAI,EAAG;AACrB,YAAQ,KAAK,IAAI;AAAA,EACnB;AACA,SAAO,QAAQ,KAAK;AACtB;;;ACrlBA,yBAA2B;AAIpB,SAAS,iBAAiB,KAAqB;AACpD,SAAO;AACT;AAEO,SAAS,iBAAiB,MAAc,SAAiB,MAAsB;AACpF,QAAM,YAAY,GAAG,IAAI,GAAG,eAAe,GAAG,OAAO,GAAG,eAAe,GAAG,IAAI;AAC9E,QAAM,WAAO,+BAAW,QAAQ,EAAE,OAAO,SAAS,EAAE,OAAO,KAAK;AAChE,QAAM,QAAQ,KAAK,CAAC;AACpB,SAAO,GAAG,KAAK,GAAG,eAAe,GAAG,IAAI,GAAG,eAAe,GAAG,OAAO,GAAG,eAAe,GAAG,IAAI;AAC/F;;;ACQO,SAAS,iBAAiB,WAA2B;AAC1D,oBAAkB,SAAS;AAC3B,SAAO,GAAG,SAAS;AACrB;AAiBO,SAAS,mBAAmB,OAAuB;AACxD,MAAI,MAAM;AACV,aAAW,MAAM,OAAO;AACtB,QAAI,cAAc,KAAK,EAAE,EAAG,QAAO;AAAA,aAC1B,OAAO,IAAK,QAAO;AAAA,aACnB,OAAO,IAAK,QAAO;AAAA,aACnB,OAAO,IAAK,QAAO;AAAA,QACvB,QAAO,KAAK,GAAG,YAAY,CAAC,EAAG,SAAS,EAAE,CAAC;AAAA,EAClD;AACA,SAAO;AACT;AAUO,SAAS,cAAc,WAAmB,cAA8B;AAC7E,oBAAkB,SAAS;AAC3B,MAAI,iBAAiB,GAAI,QAAO;AAChC,SAAO,GAAG,SAAS,MAAM,mBAAmB,YAAY,CAAC;AAC3D;AAQO,SAAS,iBAAiB,QAAwB;AACvD,SAAO,OAAO,QAAQ,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE;AAClD;AAGO,SAAS,gBAAgB,WAA2B;AACzD,QAAM,IAAIC,YAAW,iBAAiB,SAAS,CAAC;AAChD,SAAO,8BAA8B,CAAC;AAAA;AAAA;AAAA;AAAA;AAKxC;AAGO,SAAS,uBACd,WACA,cACA,WACA,WACmB;AACnB,QAAM,IAAIA,YAAW,iBAAiB,SAAS,CAAC;AAChD,SAAO;AAAA,IACL,KAAK,yBAAyB,CAAC;AAAA,IAC/B,QAAQ,CAAC,cAAc,WAAW,SAAS;AAAA,EAC7C;AACF;AAOO,SAAS,0BACd,WACA,aACmB;AACnB,QAAM,IAAIA,YAAW,iBAAiB,SAAS,CAAC;AAChD,SAAO;AAAA,IACL,KACE,yCAAyC,CAAC;AAAA,IAE5C,QAAQ,CAAC,GAAG,iBAAiB,WAAW,CAAC,IAAI;AAAA,EAC/C;AACF;AAGO,SAAS,qBAAqB,WAAmB,cAAyC;AAC/F,QAAM,IAAIA,YAAW,iBAAiB,SAAS,CAAC;AAChD,SAAO;AAAA,IACL,KAAK,eAAe,CAAC;AAAA,IACrB,QAAQ,CAAC,YAAY;AAAA,EACvB;AACF;;;ACYA,IAAM,sBAAsB;AAC5B,IAAM,sBAAsB;AAO5B,IAAM,qBAAqB;AAE3B,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AAOA,SAAS,WAAW,GAAuB,GAA2C;AACpF,MAAI,MAAM,OAAW,QAAO;AAC5B,MAAI,MAAM,OAAW,QAAO;AAC5B,SAAO,KAAK,IAAI,GAAG,CAAC;AACtB;AAcA,SAAS,gBACP,YACA,eACA,WACO;AACP,QAAM,UACJ,iBAAiB,gBAAgB,KAAK,OAAO,SAAS,aAAa,IAC/D,KAAK,MAAM,aAAa,IACxB;AACN,QAAM,WACJ,aAAa,YAAY,KAAK,OAAO,SAAS,SAAS,IAAI,KAAK,MAAM,SAAS,IAAI;AAErF,MAAI,YAAY,YAAY,aAAa,UAAU;AACjD,WAAO,CAAC,UAAU;AAAA,EACpB;AAEA,QAAM,SAAgB,CAAC;AACvB,MAAI,UAAe,CAAC;AACpB,MAAI,oBAAoB;AACxB,aAAW,QAAQ,YAAY;AAC7B,UAAM,aAAa,KAAK,OAAO;AAC/B,UAAM,kBAAkB,QAAQ,SAAS,IAAI;AAC7C,UAAM,mBAAmB,oBAAoB,aAAa;AAC1D,QAAI,QAAQ,SAAS,MAAM,mBAAmB,mBAAmB;AAC/D,aAAO,KAAK,OAAO;AACnB,gBAAU,CAAC;AACX,0BAAoB;AAAA,IACtB;AACA,YAAQ,KAAK,IAAI;AACjB,yBAAqB;AAAA,EACvB;AACA,MAAI,QAAQ,SAAS,EAAG,QAAO,KAAK,OAAO;AAC3C,SAAO;AACT;AAEA,IAAM,+BAAN,MAAiE;AAAA,EAC/D,YACmB,IACA,WACjB;AAFiB;AACA;AAAA,EAChB;AAAA,EAEH,MAAM,OAAO,OAAkD;AAC7D,UAAM,OAAO,qBAAqB,KAAK,WAAW,KAAK;AACvD,UAAM,OAAO,MAAM,KAAK,GAAG,IAAI,KAAK,KAAK,KAAK,MAAM;AACpD,WAAO,KAAK,WAAW,IAAI,OAAO,YAAY,KAAK,CAAC,CAAC;AAAA,EACvD;AAAA,EAEA,MAAM,MAAM,SAAwB,SAAsD;AACxF,UAAM,OAAO,cAAc,KAAK,WAAW,SAAS,OAAO;AAC3D,UAAM,OAAO,MAAM,KAAK,GAAG,IAAI,KAAK,KAAK,KAAK,MAAM;AACpD,WAAO,KAAK,IAAI,WAAW;AAAA,EAC7B;AAAA,EAEA,MAAM,OAAO,OAAe,QAAwB,MAAgC;AAClF,UAAM,OAAO,WAAW,KAAK,WAAW,OAAO,QAAQ,KAAK,IAAI,GAAG,IAAI;AACvE,UAAM,KAAK,GAAG,IAAI,KAAK,KAAK,KAAK,MAAM;AAAA,EACzC;AAAA,EAEA,MAAM,UAAU,OAAe,QAAsC;AACnE,UAAM,OAAO,cAAc,KAAK,WAAW,OAAO,QAAQ,KAAK,IAAI,CAAC;AAEpE,UAAM,mBAAmB,GAAG,KAAK,GAAG;AACpC,UAAM,OAAO,MAAM,KAAK,GAAG,IAAI,kBAAkB,KAAK,MAAM;AAC5D,QAAI,KAAK,WAAW,GAAG;AACrB,YAAM,IAAI;AAAA,QACR,2CAA2C,KAAK,WAAW,KAAK,SAAS;AAAA,QACzE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,UAAU,OAA8B;AAC5C,UAAM,OAAO,cAAc,KAAK,WAAW,KAAK;AAChD,UAAM,KAAK,GAAG,IAAI,KAAK,KAAK,KAAK,MAAM;AAAA,EACzC;AACF;AAEA,IAAM,yBAAN,MAAqD;AAAA,EAGnD,YACmB,UACA,WACA,cACjB;AAHiB;AACA;AACA;AAAA,EAChB;AAAA,EANc,aAAkC,CAAC;AAAA,EAQpD,OAAO,OAAe,QAAwB,MAAuB;AACnE,SAAK,WAAW,KAAK,WAAW,KAAK,WAAW,OAAO,QAAQ,KAAK,IAAI,GAAG,IAAI,CAAC;AAAA,EAClF;AAAA,EAEA,UAAU,OAAe,QAA6B;AACpD,SAAK,WAAW,KAAK,cAAc,KAAK,WAAW,OAAO,QAAQ,KAAK,IAAI,CAAC,CAAC;AAAA,EAC/E;AAAA,EAEA,UAAU,OAAqB;AAC7B,SAAK,WAAW,KAAK,cAAc,KAAK,WAAW,KAAK,CAAC;AAAA,EAC3D;AAAA,EAEA,MAAM,SAAwB;AAC5B,QAAI,KAAK,WAAW,WAAW,EAAG;AAClC,UAAM,KAAK,aAAa;AACxB,UAAM,KAAK,SAAS,MAAM,KAAK,UAAU;AACzC,SAAK,WAAW,SAAS;AAAA,EAC3B;AACF;AAkCA,IAAM,mBAAoD;AAAA,EACxD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,oBAAN,MAAM,mBAA8D;AAAA,EAclE,YACmB,UACjB,WACA,cACA,WACA,UACA,aACA,eACA;AAPiB;AAQjB,sBAAkB,SAAS;AAC3B,SAAK,YAAY;AACjB,SAAK,iBAAiB,cAAc,WAAW,YAAY;AAC3D,SAAK,eAAe;AACpB,SAAK,YAAY;AACjB,SAAK,WAAW;AAChB,SAAK,cAAc;AACnB,SAAK,gBAAgB;AACrB,UAAM,OAAO,IAAI,IAAsB,gBAAgB;AACvD,QAAI,OAAO,SAAS,gBAAgB,YAAY;AAC9C,WAAK,IAAI,mBAAmB;AAAA,IAC9B;AACA,SAAK,eAAe,mBAAmB,IAAI;AAAA,EAC7C;AAAA,EAnCS;AAAA;AAAA,EAEA;AAAA,EACA;AAAA;AAAA,EAEQ;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT,UAAgC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgChC,eAA8B;AACpC,QAAI,CAAC,KAAK,SAAS;AACjB,WAAK,UAAU,KAAK,eAAe,EAAE,MAAM,CAAC,QAAQ;AAElD,aAAK,UAAU;AACf,cAAM;AAAA,MACR,CAAC;AAAA,IACH;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,MAAM,YAAY,QAAQ,OAAsB;AAC9C,QAAI,MAAO,MAAK,UAAU;AAC1B,UAAM,KAAK,aAAa;AAAA,EAC1B;AAAA,EAEA,MAAc,iBAAgC;AAC5C,UAAM,MAAM;AAAA,MACV,GAAG,sBAAsB,KAAK,gBAAgB;AAAA,QAC5C,aAAa,KAAK;AAAA,QAClB,UAAU,KAAK;AAAA,MACjB,CAAC;AAAA,MACD,GAAI,KAAK,gBAAgB,KAAK,cAAc,KAAK,cAAc,IAAI,CAAC;AAAA,MACpE,gBAAgB,KAAK,SAAS;AAAA,IAChC;AACA,UAAM,aAAkC,IAAI,IAAI,CAAC,SAAS,EAAE,KAAK,QAAQ,CAAC,EAAE,EAAE;AAC9E,eAAW;AAAA,MACT;AAAA,QACE,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,MACP;AAAA,IACF;AAMA,UAAM,SAAS;AAAA,MACb;AAAA,MACA,KAAK,SAAS;AAAA,MACd,KAAK,SAAS;AAAA,IAChB;AACA,eAAW,SAAS,QAAQ;AAC1B,YAAM,KAAK,SAAS,MAAM,KAAK;AAAA,IACjC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAc,WAAc,IAAkC;AAC5D,UAAM,KAAK,aAAa;AACxB,QAAI;AACF,aAAO,MAAM,GAAG;AAAA,IAClB,SAAS,KAAK;AACZ,UAAI,CAAC,KAAK,kBAAkB,GAAG,EAAG,OAAM;AACxC,WAAK,UAAU;AACf,YAAM,KAAK,aAAa;AACxB,aAAO,GAAG;AAAA,IACZ;AAAA,EACF;AAAA;AAAA,EAGQ,kBAAkB,KAAuB;AAC/C,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,WAAO,QAAQ,SAAS,kBAAkB,KAAK,cAAc,EAAE;AAAA,EACjE;AAAA;AAAA,EAIA,MAAM,OAAO,OAAkD;AAC7D,WAAO,KAAK,WAAW,YAAY;AACjC,YAAM,OAAO,qBAAqB,KAAK,gBAAgB,KAAK;AAC5D,YAAM,OAAO,MAAM,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK,MAAM;AAC1D,aAAO,KAAK,WAAW,IAAI,OAAO,YAAY,KAAK,CAAC,CAAC;AAAA,IACvD,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,MAAM,SAAwB,SAAsD;AACxF,WAAO,KAAK,WAAW,YAAY;AACjC,YAAM,OAAO,cAAc,KAAK,gBAAgB,SAAS,OAAO;AAChE,YAAM,OAAO,MAAM,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK,MAAM;AAC1D,aAAO,KAAK,IAAI,WAAW;AAAA,IAC7B,CAAC;AAAA,EACH;AAAA;AAAA,EAIA,MAAM,OAAO,OAAe,QAAwB,MAAgC;AAClF,WAAO,KAAK,WAAW,YAAY;AACjC,YAAM,OAAO,WAAW,KAAK,gBAAgB,OAAO,QAAQ,KAAK,IAAI,GAAG,IAAI;AAC5E,YAAM,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK,MAAM;AAAA,IAC/C,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAAU,OAAe,QAAsC;AACnE,WAAO,KAAK,WAAW,YAAY;AACjC,YAAM,OAAO,cAAc,KAAK,gBAAgB,OAAO,QAAQ,KAAK,IAAI,CAAC;AAKzE,YAAM,mBAAmB,GAAG,KAAK,GAAG;AACpC,YAAM,OAAO,MAAM,KAAK,SAAS,IAAI,kBAAkB,KAAK,MAAM;AAClE,UAAI,KAAK,WAAW,GAAG;AACrB,cAAM,IAAI;AAAA,UACR,2CAA2C,KAAK,WAAW,KAAK,cAAc;AAAA,UAC9E;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAAU,OAA8B;AAC5C,WAAO,KAAK,WAAW,YAAY;AACjC,YAAM,OAAO,cAAc,KAAK,gBAAgB,KAAK;AACrD,YAAM,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK,MAAM;AAAA,IAC/C,CAAC;AAAA,EACH;AAAA;AAAA,EAIA,MAAM,eAAkB,IAAwD;AAC9E,QAAI,CAAC,KAAK,SAAS,aAAa;AAC9B,YAAM,IAAI;AAAA,QACR;AAAA,QAIA;AAAA,MACF;AAAA,IACF;AAGA,UAAM,KAAK,aAAa;AACxB,WAAO,KAAK,SAAS,YAAY,OAAO,OAAO;AAC7C,YAAM,YAAY,IAAI,6BAA6B,IAAI,KAAK,cAAc;AAC1E,aAAO,GAAG,SAAS;AAAA,IACrB,CAAC;AAAA,EACH;AAAA,EAEA,cAA4B;AAC1B,WAAO,IAAI;AAAA,MAAuB,KAAK;AAAA,MAAU,KAAK;AAAA,MAAgB,MACpE,KAAK,aAAa;AAAA,IACpB;AAAA,EACF;AAAA;AAAA,EAIA,SAAS,eAAuB,MAAoC;AAKlE,QAAI,CAAC,iBAAiB,cAAc,SAAS,GAAG,GAAG;AACjD,YAAM,IAAI;AAAA,QACR,wCAAwC,aAAa;AAAA,QAErD;AAAA,MACF;AAAA,IACF;AACA,QAAI,CAAC,QAAQ,KAAK,SAAS,GAAG,GAAG;AAC/B,YAAM,IAAI;AAAA,QACR,kEAAkE,IAAI;AAAA,QAEtE;AAAA,MACF;AAAA,IACF;AACA,UAAM,kBAAkB,KAAK,eACzB,GAAG,KAAK,YAAY,IAAI,aAAa,IAAI,IAAI,KAC7C,GAAG,aAAa,IAAI,IAAI;AAC5B,UAAM,WAAW,KAAK,YAAY,GAAG,KAAK,SAAS,IAAI,IAAI,KAAK;AAChE,WAAO,IAAI;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,IACP;AAAA,EACF;AAAA;AAAA,EAIA,MAAM,kBACJ,KACA,QACA,SACwB;AACxB,UAAM,KAAK,aAAa;AAExB,UAAM,CAAC,aAAa,WAAW,IAAI,MAAM,QAAQ,IAAI;AAAA,MACnD,OAAO,UAAU,EAAE,MAAM,KAAK,qBAAqB,MAAM,OAAO,EAAE,CAAC;AAAA,MACnE,OAAO,UAAU,EAAE,MAAM,KAAK,qBAAqB,MAAM,OAAO,EAAE,CAAC;AAAA,IACrE,CAAC;AAED,UAAM,OAAO,oBAAI,IAAY;AAC7B,UAAM,aAAuB,CAAC;AAC9B,eAAW,QAAQ,CAAC,GAAG,aAAa,GAAG,WAAW,GAAG;AACnD,UAAI,KAAK,YAAY,cAAe;AACpC,YAAM,QAAQ,iBAAiB,KAAK,MAAM,KAAK,SAAS,KAAK,IAAI;AACjE,UAAI,CAAC,KAAK,IAAI,KAAK,GAAG;AACpB,aAAK,IAAI,KAAK;AACd,mBAAW,KAAK,KAAK;AAAA,MACvB;AAAA,IACF;AAEA,UAAM,YAAY,iBAAiB,GAAG;AACtC,UAAM,wBAAwB,SAAS,yBAAyB;AAMhE,UAAM,cAAkE,CAAC;AACzE,QAAI,mBAAmB;AACvB,QAAI,uBAAuB;AACzB,YAAM,SAAS,KAAK,eAAe,GAAG,KAAK,YAAY,IAAI,GAAG,KAAK;AACnE,YAAM,WAAW,0BAA0B,KAAK,WAAW,MAAM;AACjE,YAAM,OAAO,MAAM,KAAK,SAAS,IAAI,SAAS,KAAK,SAAS,MAAM;AAClE,iBAAW,OAAO,MAAM;AACtB,cAAM,YAAY,OAAO,IAAI,UAAU;AAIvC,0BAAkB,SAAS;AAC3B,oBAAY,KAAK,EAAE,cAAc,OAAO,IAAI,aAAa,GAAG,UAAU,CAAC;AAAA,MACzE;AACA,iBAAW,KAAK,aAAa;AAC3B,cAAM,YAAY,MAAM,KAAK,SAAS;AAAA,UACpC,6BAA6BC,YAAW,EAAE,SAAS,CAAC;AAAA,UACpD,CAAC;AAAA,QACH;AACA,cAAM,IAAK,UAAU,CAAC,GAA2C;AACjE,4BAAoB,OAAO,MAAM,WAAW,OAAO,CAAC,IAAI,OAAO,KAAK,CAAC;AAAA,MACvE;AAAA,IACF;AAQA,UAAM,kBAAuC,WAAW;AAAA,MAAI,CAAC,OAC3D,cAAc,KAAK,gBAAgB,EAAE;AAAA,IACvC;AACA,oBAAgB,KAAK,cAAc,KAAK,gBAAgB,SAAS,CAAC;AAClE,eAAW,KAAK,aAAa;AAC3B,sBAAgB,KAAK,EAAE,KAAK,wBAAwBA,YAAW,EAAE,SAAS,CAAC,IAAI,QAAQ,CAAC,EAAE,CAAC;AAC3F,sBAAgB,KAAK,qBAAqB,KAAK,WAAW,EAAE,YAAY,CAAC;AAAA,IAC3E;AAEA,UAAM;AAAA,MACJ,SAAS;AAAA,MACT;AAAA,MACA;AAAA,IACF,IAAI,MAAM,KAAK,sBAAsB,iBAAiB,OAAO;AAM7D,UAAM,QAAQ,OAAO,WAAW;AAChC,UAAM,eAAe,QAAQ,WAAW,SAAS;AAKjD,UAAM,cAAc;AAOpB,UAAM,0BAA0B,QAAQ,YAAY,SAAS,IAAI;AACjE,UAAM,UAAU,cAAc,2BAA2B,QAAQ,mBAAmB;AAEpF,WAAO,EAAE,SAAS,SAAS,QAAQ,cAAc,YAAY;AAAA,EAC/D;AAAA,EAEA,MAAM,gBACJ,QACA,QACA,SACqB;AACrB,UAAM,KAAK,aAAa;AAIxB,UAAM,kBACJ,OAAO,UAAU,SACb,EAAE,GAAG,QAAQ,qBAAqB,OAAO,uBAAuB,KAAK,IACrE,EAAE,GAAG,QAAQ,OAAO,GAAG,qBAAqB,OAAO,uBAAuB,KAAK;AACrF,UAAM,QAAQ,MAAM,OAAO,UAAU,eAAe;AACpD,UAAM,SAAS,MAAM,IAAI,CAAC,MAAM,iBAAiB,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC;AAE3E,QAAI,OAAO,WAAW,GAAG;AACvB,aAAO,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC,EAAE;AAAA,IAC9C;AAEA,UAAM,aAAa,OAAO,IAAI,CAAC,OAAO,cAAc,KAAK,gBAAgB,EAAE,CAAC;AAE5E,WAAO,KAAK,sBAAsB,YAAY,OAAO;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA2BA,MAAc,sBACZ,YACA,SACyE;AACzE,QAAI,WAAW,WAAW,GAAG;AAC3B,aAAO,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC,EAAE;AAAA,IAC9C;AACA,UAAM,aAAa,SAAS,cAAc;AAU1C,UAAM,kBAAkB,SAAS;AACjC,UAAM,UAAU,WAAW,iBAAiB,KAAK,SAAS,YAAY;AACtE,UAAM,SAAS,gBAAgB,YAAY,SAAS,KAAK,SAAS,cAAc;AAEhF,UAAM,SAA2B,CAAC;AAClC,QAAI,UAAU;AACd,QAAI,UAAU;AACd,UAAM,eAAe,OAAO;AAE5B,UAAM,iBAAiB,KAAK,SAAS;AAErC,aAAS,aAAa,GAAG,aAAa,OAAO,QAAQ,cAAc;AACjE,YAAM,QAAQ,OAAO,UAAU;AAO/B,YAAM,wBACJ,MAAM,WAAW,KACjB,mBAAmB,UACnB,MAAM,CAAC,EAAE,OAAO,SAAS;AAE3B,UAAI,YAAY;AAChB,UAAI,YAA0B;AAC9B,YAAM,mBAAmB,wBAAwB,IAAI;AACrD,eAAS,UAAU,GAAG,WAAW,kBAAkB,WAAW;AAC5D,YAAI;AACF,gBAAM,KAAK,SAAS,MAAM,KAAK;AAC/B,sBAAY;AACZ;AAAA,QACF,SAAS,KAAK;AACZ,sBAAY,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAC9D,cAAI,UAAU,kBAAkB;AAC9B,kBAAM,QAAQ,KAAK,IAAI,sBAAsB,KAAK,IAAI,GAAG,OAAO,GAAG,kBAAkB;AACrF,kBAAM,MAAM,KAAK;AAAA,UACnB;AAAA,QACF;AAAA,MACF;AAEA,UAAI,WAAW;AACb,mBAAW,MAAM;AACjB,mBAAW;AAAA,MACb,WAAW,WAAW;AACpB,eAAO,KAAK;AAAA,UACV;AAAA,UACA,OAAO;AAAA,UACP,gBAAgB,MAAM;AAAA,QACxB,CAAC;AAAA,MACH;AAEA,UAAI,SAAS,YAAY;AACvB,gBAAQ,WAAW;AAAA,UACjB,kBAAkB;AAAA,UAClB;AAAA,UACA,cAAc;AAAA,QAChB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,EAAE,SAAS,SAAS,OAAO;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,MAAM,UAAU,MAAqB,SAAyD;AAC5F,UAAM,EAAE,MAAM,QAAQ,IAAI,iBAAiB,KAAK,gBAAgB,MAAM,OAAO;AAC7E,UAAM,OAAO,MAAM,KAAK,WAAW,MAAM,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK,MAAM,CAAC;AACjF,UAAM,MAAM,KAAK,CAAC,KAAK,CAAC;AACxB,UAAM,MAA8B,CAAC;AACrC,eAAW,SAAS,SAAS;AAC3B,YAAM,IAAI,IAAI,KAAK;AACnB,UAAI,MAAM,QAAQ,MAAM,QAAW;AAIjC,cAAM,KAAK,KAAK,KAAK,EAAE;AACvB,YAAI,KAAK,IAAI,OAAO,QAAQ,OAAO,MAAM;AAAA,MAC3C,WAAW,OAAO,MAAM,UAAU;AAChC,YAAI,KAAK,IAAI,OAAO,CAAC;AAAA,MACvB,WAAW,OAAO,MAAM,UAAU;AAChC,YAAI,KAAK,IAAI;AAAA,MACf,OAAO;AAGL,YAAI,KAAK,IAAI,OAAO,CAAC;AAAA,MACvB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBA,MAAM,WAAW,SAAwB,SAA4C;AACnF,UAAM,KAAK,aAAa;AACxB,UAAM,OAAO,kBAAkB,KAAK,gBAAgB,OAAO;AAC3D,WAAO,KAAK,wBAAwB,MAAM,OAAO;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,WACJ,SACA,OACA,SACqB;AACrB,UAAM,KAAK,aAAa;AACxB,UAAM,OAAO,kBAAkB,KAAK,gBAAgB,SAAS,MAAM,MAAM,KAAK,IAAI,CAAC;AACnF,WAAO,KAAK,wBAAwB,MAAM,OAAO;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,MAAM,OAAO,QAA6C;AACxD,QAAI,OAAO,QAAQ,WAAW,GAAG;AAC/B,aAAO,OAAO,UAAU,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE;AAAA,IACnE;AACA,UAAM,OAAO,cAAc,KAAK,gBAAgB,MAAM;AACtD,UAAM,OAAO,MAAM,KAAK,WAAW,MAAM,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK,MAAM,CAAC;AACjF,UAAM,QAAQ,KAAK,IAAI,WAAW;AAClC,QAAI,CAAC,OAAO,SAAS;AACnB,aAAO,EAAE,MAAM;AAAA,IACjB;AAIA,UAAM,YAAY,OAAO,aAAa;AACtC,UAAM,aAAa,MAAM,IAAI,CAAC,MAAO,cAAc,YAAY,EAAE,OAAO,EAAE,IAAK;AAC/E,UAAM,gBAAgB,CAAC,GAAG,IAAI,IAAI,UAAU,CAAC;AAC7C,QAAI,cAAc,WAAW,GAAG;AAC9B,aAAO,EAAE,OAAO,SAAS,CAAC,EAAE;AAAA,IAC9B;AACA,UAAM,cAAc,qBAAqB,KAAK,gBAAgB,aAAa;AAC3E,UAAM,cAAc,MAAM,KAAK,SAAS,IAAI,YAAY,KAAK,YAAY,MAAM;AAC/E,UAAM,QAAQ,oBAAI,IAA+B;AACjD,eAAW,OAAO,aAAa;AAC7B,YAAM,OAAO,YAAY,GAAG;AAG5B,YAAM,IAAI,KAAK,MAAM,IAAI;AAAA,IAC3B;AACA,UAAM,UAAU,WAAW,IAAI,CAAC,QAAQ,MAAM,IAAI,GAAG,KAAK,IAAI;AAC9D,WAAO,EAAE,OAAO,QAAQ;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,MAAM,mBACJ,QACA,SACA,SACyC;AACzC,UAAM,EAAE,MAAM,QAAQ,IAAI;AAAA,MACxB,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,UAAM,OAAO,MAAM,KAAK,WAAW,MAAM,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK,MAAM,CAAC;AACjF,WAAO,KAAK,IAAI,CAAC,QAAQ,mBAAmB,KAAK,OAAO,CAAC;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,wBACZ,MACA,SACqB;AACrB,UAAM,mBAAmB,GAAG,KAAK,GAAG;AACpC,UAAM,aAAa,SAAS,cAAc;AAC1C,QAAI,YAA0B;AAC9B,aAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACtD,UAAI;AACF,cAAM,OAAO,MAAM,KAAK,SAAS,IAAI,kBAAkB,KAAK,MAAM;AAClE,cAAM,UAAU,KAAK;AACrB,YAAI,SAAS,YAAY;AACvB,kBAAQ,WAAW;AAAA,YACjB,kBAAkB;AAAA,YAClB,cAAc;AAAA,YACd,cAAc;AAAA,UAChB,CAAC;AAAA,QACH;AACA,eAAO,EAAE,SAAS,SAAS,GAAG,QAAQ,CAAC,EAAE;AAAA,MAC3C,SAAS,KAAK;AACZ,oBAAY,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAC9D,YAAI,UAAU,YAAY;AACxB,gBAAM,QAAQ,KAAK,IAAI,sBAAsB,KAAK,IAAI,GAAG,OAAO,GAAG,kBAAkB;AACrF,gBAAM,MAAM,KAAK;AAAA,QACnB;AAAA,MACF;AAAA,IACF;AAKA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,SAAS;AAAA,MACT,QAAQ;AAAA,QACN;AAAA,UACE,YAAY;AAAA,UACZ,OAAO,aAAa,IAAI,MAAM,oCAAoC;AAAA,UAClE,gBAAgB;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAWO,SAAS,oBACd,UACA,WACA,UAAgC,CAAC,GACX;AACtB,QAAM,eAAe,QAAQ,gBAAgB;AAC7C,QAAM,YAAY,QAAQ,aAAa;AACvC,SAAO,IAAI;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AACF;;;AhBh6BO,SAAS,2BAA2B,IAAoC;AAC7E,SAAO;AAAA,IACL,MAAM,IAAI,KAAa,QAAuD;AAC5E,aAAO,GAAG,QAAQ,GAAG,EAAE,IAAI,GAAG,MAAM;AAAA,IACtC;AAAA,IACA,MAAM,IAAI,KAAa,QAAkC;AACvD,SAAG,QAAQ,GAAG,EAAE,IAAI,GAAG,MAAM;AAAA,IAC/B;AAAA,IACA,MAAM,MAAM,YAA2B;AACrC,YAAM,KAAK,GAAG,YAAY,CAAC,UAA6B;AACtD,mBAAW,KAAK,OAAO;AACrB,aAAG,QAAQ,EAAE,GAAG,EAAE,IAAI,GAAG,EAAE,MAAM;AAAA,QACnC;AAAA,MACF,CAAC;AACD,SAAG,UAAU;AAAA,IACf;AAAA,IACA,MAAM,YAAe,IAAsD;AACzE,SAAG,KAAK,iBAAiB;AACzB,UAAI;AACF,cAAM,SAAS,MAAM,GAAG;AAAA,UACtB,MAAM,IAAI,KAAa,QAAmB;AACxC,mBAAO,GAAG,QAAQ,GAAG,EAAE,IAAI,GAAG,MAAM;AAAA,UACtC;AAAA,UACA,MAAM,IAAI,KAAa,QAAmB;AACxC,eAAG,QAAQ,GAAG,EAAE,IAAI,GAAG,MAAM;AAAA,UAC/B;AAAA,QACF,CAAC;AACD,WAAG,KAAK,QAAQ;AAChB,eAAO;AAAA,MACT,SAAS,KAAK;AACZ,WAAG,KAAK,UAAU;AAClB,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,WAAW,OAAyC;AAC3D,SACE,OAAO,UAAU,YACjB,UAAU,QACV,OAAQ,MAAgC,YAAY,cACpD,OAAQ,MAA6B,SAAS;AAElD;AAEA,IAAM,qBAAqB;AAG3B,IAAM,uBAAuB;AAE7B,SAAS,aAAa,IAAoB,SAAgD;AACxF,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,QAAI,CAAC,mBAAmB,KAAK,GAAG,GAAG;AACjC,YAAM,IAAI,eAAe,wBAAwB,KAAK,UAAU,GAAG,CAAC,IAAI,kBAAkB;AAAA,IAC5F;AACA,QACE,CAAC,qBAAqB,KAAK,OAAO,KAAK,CAAC,KACvC,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,GACpD;AACA,YAAM,IAAI;AAAA,QACR,4BAA4B,GAAG,KAAK,KAAK,UAAU,KAAK,CAAC;AAAA,QACzD;AAAA,MACF;AAAA,IACF;AACA,OAAG,OAAO,GAAG,GAAG,MAAM,KAAK,EAAE;AAAA,EAC/B;AACF;AAQA,SAAS,kBAAkB,IAA0B;AACnD,MAAI;AACF,OAAG;AAAA,MAAS;AAAA,MAAqB,EAAE,eAAe,KAAK;AAAA,MAAG,CAAC,QAAQ,OAAO,YACxE,sBAAsB,QAAQ,OAAO,OAAO;AAAA,IAC9C;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAWA,eAAe,0BACb,UACA,WACe;AACf,QAAM,YAAY,MAAM,SAAS;AAAA,IAC/B;AAAA,IACA,CAAC;AAAA,EACH;AACA,QAAM,YAAY,UAAU,IAAI,CAAC,MAAM,OAAO,EAAE,IAAI,CAAC;AACrD,QAAM,cAAc,MAAM,SAAS;AAAA,IACjC,4BAA4BC,YAAW,iBAAiB,SAAS,CAAC,CAAC;AAAA,IACnE,CAAC;AAAA,EACH;AACA,QAAM,gBAAgB,YAAY,IAAI,CAAC,MAAM,OAAO,EAAE,UAAU,CAAC;AACjE,aAAW,QAAQ,sBAAsB,WAAW,eAAe,SAAS,GAAG;AAC7E,sBAAkB,IAAI;AACtB,UAAM,SAAS,IAAI,wBAAwBA,YAAW,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,EACnE;AACF;AAQA,SAAS,uBACP,OACA,UACA,WACuC;AACvC,QAAM,OAAO,oBAAI,IAA2B;AAAA,IAC1C,GAAI,MAAM,aAAa,OAAO;AAAA,IAC9B;AAAA,IACA;AAAA,EACF,CAAC;AAOD,QAAM,iBAAiB,oBAAI,IAAI;AAAA,IAC7B,MAAM;AAAA,IACN,aAAa,MAAM,cAAc;AAAA,IACjC,gBAAgB,MAAM,cAAc;AAAA,EACtC,CAAC;AACD,QAAM,gBAAgB,OAAU,OAAqC;AACnE,UAAM,MAAM,YAAY;AACxB,QAAI;AACF,aAAO,MAAM,GAAG;AAAA,IAClB,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,YAAM,UAAU,uBAAuB,KAAK,OAAO,IAAI,CAAC;AACxD,UAAI,YAAY,UAAa,CAAC,eAAe,IAAI,OAAO,EAAG,OAAM;AACjE,YAAM,MAAM,YAAY,IAAI;AAC5B,aAAO,GAAG;AAAA,IACZ;AAAA,EACF;AAEA,QAAM,UAAiD;AAAA,IACrD,cAAc,mBAAmB,IAAI;AAAA,IACrC,gBAAgB,MAAM;AAAA,IACtB,WAAW,MAAM;AAAA,IAEjB,QAAQ,CAAC,UAAU,MAAM,OAAO,KAAK;AAAA,IACrC,OAAO,CAAC,SAAS,YAAY,MAAM,MAAM,SAAS,OAAO;AAAA,IACzD,QAAQ,CAAC,OAAO,QAAQ,SAAS,MAAM,OAAO,OAAO,QAAQ,IAAI;AAAA,IACjE,WAAW,CAAC,OAAO,WAAW,MAAM,UAAU,OAAO,MAAM;AAAA,IAC3D,WAAW,CAAC,UAAU,MAAM,UAAU,KAAK;AAAA,IAC3C,gBAAgB,CAAC,OAAO,MAAM,eAAe,EAAE;AAAA,IAC/C,aAAa,MAAM,MAAM,YAAY;AAAA,IAErC,UAAU,CAAC,eAAe,SACxB,uBAAuB,MAAM,SAAS,eAAe,IAAI,GAAG,UAAU,SAAS;AAAA,IAEjF,mBAAmB,OAAO,KAAK,QAAQ,YAAY;AACjD,YAAM,SAAS,MAAM,MAAM,kBAAkB,KAAK,QAAQ,OAAO;AACjE,UAAI,OAAO,OAAO,WAAW,GAAG;AAC9B,cAAM,0BAA0B,UAAU,SAAS;AAAA,MACrD;AACA,aAAO;AAAA,IACT;AAAA,IACA,iBAAiB,CAAC,QAAQ,QAAQ,YAAY,MAAM,gBAAgB,QAAQ,QAAQ,OAAO;AAAA,IAE3F,WAAW,CAAC,MAAM,YAAY,MAAM,UAAW,MAAM,OAAO;AAAA,IAC5D,YAAY,CAAC,SAAS,YAAY,MAAM,WAAY,SAAS,OAAO;AAAA,IACpE,YAAY,CAAC,SAAS,OAAO,YAAY,MAAM,WAAY,SAAS,OAAO,OAAO;AAAA,IAClF,QAAQ,CAAC,WAAW,MAAM,OAAQ,MAAM;AAAA,IACxC,oBAAoB,CAAC,QAAQ,SAAS,YACpC,MAAM,mBAAoB,QAAQ,SAAS,OAAO;AAAA;AAAA;AAAA,IAKpD,MAAM,YAAY,QAAyD;AACzE,YAAM,EAAE,MAAM,aAAa,IAAI,mBAAmB,MAAM,gBAAgB,MAAM;AAC9E,YAAM,OAAO,MAAM,cAAc,MAAM,SAAS,IAAI,KAAK,KAAK,KAAK,MAAM,CAAC;AAC1E,aAAO,KAAK,IAAI,CAAC,QAAQ;AACvB,cAAM,SAAS,YAAY,GAAG;AAC9B,YAAI,cAAc;AAChB,gBAAM,WAAW,IAAI,cAAc;AACnC;AAAA,YACE,OAAO;AAAA,YACP;AAAA,YACA,OAAO,aAAa,WAAW,WAAW,OAAO,QAAQ;AAAA,UAC3D;AAAA,QACF;AACA,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,eAAe,QAA4D;AAC/E,YAAM,OAAO,sBAAsB,MAAM,gBAAgB,MAAM;AAC/D,UAAI;AACJ,UAAI;AACF,eAAO,MAAM,cAAc,MAAM,SAAS,IAAI,KAAK,KAAK,KAAK,MAAM,CAAC;AAAA,MACtE,SAAS,KAAK;AACZ,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAO/D,YAAI,iBAAiB,OAAO,GAAG;AAC7B,gBAAM,IAAI;AAAA,YACR,sDAAiD,OAAO;AAAA,YACxD;AAAA,UACF;AAAA,QACF;AACA,cAAM;AAAA,MACR;AACA,aAAO,KAAK,IAAI,WAAW;AAAA,IAC7B;AAAA,EACF;AACA,SAAO;AACT;AAoBA,eAAsB,yBACpB,UACA,UAAqC,CAAC,GACT;AAC7B,QAAM;AAAA,IACJ,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,IAAI;AAEJ,MAAI;AACJ,MAAI;AACJ,MAAI,OAAO,aAAa,UAAU;AAChC,QAAI;AACJ,QAAI;AACF,kBAAY,MAAM,OAAO,gBAAgB,GAAG;AAAA,IAC9C,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,sIACE,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CACjD;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,SAAK,IAAI,SAAS,UAAU,gBAAgB,EAAE,eAAe,KAAK,IAAI,CAAC,CAAC;AACxE,aAAS;AAIT,OAAG,OAAO,oBAAoB;AAAA,EAChC,WAAW,WAAW,QAAQ,GAAG;AAC/B,SAAK;AACL,aAAS;AAAA,EACX,OAAO;AACL,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,KAAG,OAAO,kBAAkB,KAAK,IAAI,GAAG,KAAK,MAAM,aAAa,CAAC,CAAC,EAAE;AACpE,MAAI,SAAS;AACX,iBAAa,IAAI,OAAO;AAAA,EAC1B;AACA,oBAAkB,EAAE;AAKpB,QAAM,eAAe,eAAe;AACpC,QAAM,oBAA0C;AAAA,IAC9C,GAAG;AAAA,IACH,eAAe,CAAC,UAAU;AAAA,MACxB,GAAI,eAAe,aAAa,KAAK,IAAI,CAAC;AAAA,MAC1C,GAAG,oBAAoB,KAAK;AAAA,IAC9B;AAAA,EACF;AAEA,QAAM,WAAW,2BAA2B,EAAE;AAC9C,QAAM,QAAQ,oBAAoB,UAAU,WAAW,iBAAiB;AACxE,QAAM,UAAU,uBAAuB,OAAO,UAAU,SAAS;AACjE,MAAI,SAAS;AACb,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,QAAc;AACZ,UAAI,UAAU,CAAC,OAAQ;AACvB,eAAS;AACT,SAAG,MAAM;AAAA,IACX;AAAA,EACF;AACF;","names":["quoteIdent","JSON_PATH_KEY_RE","FIRESTORE_TYPE_NAMES","walk","quoteIdent","sql","BACKEND_ERR_LABEL","quoteIdent","quoteIdent","quoteIdent","quoteIdent"]}