@typicalday/firegraph 0.11.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +355 -78
- package/dist/backend-DuvHGgK1.d.cts +1897 -0
- package/dist/backend-DuvHGgK1.d.ts +1897 -0
- package/dist/backend.cjs +365 -5
- package/dist/backend.cjs.map +1 -1
- package/dist/backend.d.cts +25 -5
- package/dist/backend.d.ts +25 -5
- package/dist/backend.js +209 -7
- package/dist/backend.js.map +1 -1
- package/dist/chunk-2DHMNTV6.js +16 -0
- package/dist/chunk-2DHMNTV6.js.map +1 -0
- package/dist/chunk-4MMQ5W74.js +288 -0
- package/dist/chunk-4MMQ5W74.js.map +1 -0
- package/dist/{chunk-5753Y42M.js → chunk-C2QMD7RY.js} +6 -10
- package/dist/chunk-C2QMD7RY.js.map +1 -0
- package/dist/chunk-D4J7Z4FE.js +67 -0
- package/dist/chunk-D4J7Z4FE.js.map +1 -0
- package/dist/chunk-EQJUUVFG.js +14 -0
- package/dist/chunk-EQJUUVFG.js.map +1 -0
- package/dist/chunk-N5HFDWQX.js +23 -0
- package/dist/chunk-N5HFDWQX.js.map +1 -0
- package/dist/chunk-PAD7WFFU.js +573 -0
- package/dist/chunk-PAD7WFFU.js.map +1 -0
- package/dist/chunk-TK64DNVK.js +256 -0
- package/dist/chunk-TK64DNVK.js.map +1 -0
- package/dist/{chunk-NJSOD64C.js → chunk-WRTFC5NG.js} +438 -30
- package/dist/chunk-WRTFC5NG.js.map +1 -0
- package/dist/client-BKi3vk0Q.d.ts +34 -0
- package/dist/client-BrsaXtDV.d.cts +34 -0
- package/dist/cloudflare/index.cjs +1386 -74
- package/dist/cloudflare/index.cjs.map +1 -1
- package/dist/cloudflare/index.d.cts +217 -13
- package/dist/cloudflare/index.d.ts +217 -13
- package/dist/cloudflare/index.js +639 -180
- package/dist/cloudflare/index.js.map +1 -1
- package/dist/codegen/index.d.cts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/errors-BRc3I_eH.d.cts +73 -0
- package/dist/errors-BRc3I_eH.d.ts +73 -0
- package/dist/firestore-enterprise/index.cjs +3877 -0
- package/dist/firestore-enterprise/index.cjs.map +1 -0
- package/dist/firestore-enterprise/index.d.cts +141 -0
- package/dist/firestore-enterprise/index.d.ts +141 -0
- package/dist/firestore-enterprise/index.js +985 -0
- package/dist/firestore-enterprise/index.js.map +1 -0
- package/dist/firestore-standard/index.cjs +3117 -0
- package/dist/firestore-standard/index.cjs.map +1 -0
- package/dist/firestore-standard/index.d.cts +49 -0
- package/dist/firestore-standard/index.d.ts +49 -0
- package/dist/firestore-standard/index.js +283 -0
- package/dist/firestore-standard/index.js.map +1 -0
- package/dist/index.cjs +809 -534
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -100
- package/dist/index.d.ts +24 -100
- package/dist/index.js +184 -531
- package/dist/index.js.map +1 -1
- package/dist/registry-Bc7h6WTM.d.cts +64 -0
- package/dist/registry-C2KUPVZj.d.ts +64 -0
- package/dist/{scope-path-B1G3YiA7.d.ts → scope-path-CROFZGr9.d.cts} +1 -56
- package/dist/{scope-path-B1G3YiA7.d.cts → scope-path-CROFZGr9.d.ts} +1 -56
- package/dist/{serialization-ZZ7RSDRX.js → serialization-OE2PFZMY.js} +6 -4
- package/dist/sqlite/index.cjs +3631 -0
- package/dist/sqlite/index.cjs.map +1 -0
- package/dist/sqlite/index.d.cts +111 -0
- package/dist/sqlite/index.d.ts +111 -0
- package/dist/sqlite/index.js +1164 -0
- package/dist/sqlite/index.js.map +1 -0
- package/package.json +33 -3
- package/dist/backend-U-MLShlg.d.ts +0 -97
- package/dist/backend-np4gEVhB.d.cts +0 -97
- package/dist/chunk-5753Y42M.js.map +0 -1
- package/dist/chunk-NJSOD64C.js.map +0 -1
- package/dist/chunk-R7CRGYY4.js +0 -94
- package/dist/chunk-R7CRGYY4.js.map +0 -1
- package/dist/types-BGWxcpI_.d.cts +0 -736
- package/dist/types-BGWxcpI_.d.ts +0 -736
- /package/dist/{serialization-ZZ7RSDRX.js.map → serialization-OE2PFZMY.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/internal/sqlite-schema.ts","../../src/sqlite/sql.ts","../../src/sqlite/backend.ts"],"sourcesContent":["/**\n * SQLite schema for firegraph triples.\n *\n * Single-table design — both nodes (self-loops with `axbType = 'is'`) and\n * edges share one row. The `scope` column carries the materialized subgraph\n * path (parent UIDs interleaved with subgraph names), which preserves\n * Firestore's nested-subcollection semantics in a flat table.\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 * Every index is prefixed with the `scope` column so the query compiler\n * (which always emits a `scope = ?` predicate) can use the index prefix\n * directly. This is the single difference from the DO SQLite backend, where\n * each DO is physically scoped and no discriminator column exists.\n *\n * Index specs come from the core preset (overridable via\n * `buildSchemaStatementsOptions.coreIndexes`) plus per-entry `indexes`\n * declared on registry entries. Specs are deduplicated by canonical\n * fingerprint before 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 'scope',\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 the firegraph table and its indexes.\n * Returned as separate statements because some drivers (D1) require one\n * statement per `prepare()` call.\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,\n scope TEXT NOT NULL DEFAULT '',\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 PRIMARY KEY (scope, doc_id)\n )`,\n // `doc_id`-only index for edge-doc lookup across scopes (the primary key\n // leads with `scope`, so a scope-less lookup wouldn't otherwise hit it).\n `CREATE INDEX IF NOT EXISTS ${quoteIdent(`${table}_idx_doc_id`)} ON ${t}(doc_id)`,\n ];\n\n const core = options.coreIndexes ?? [...DEFAULT_CORE_INDEXES];\n const fromRegistry = options.registry?.entries().flatMap((e) => e.indexes ?? []) ?? [];\n\n const leadingColumns = ['scope'];\n const deduped = dedupeIndexSpecs([...core, ...fromRegistry], leadingColumns);\n for (const spec of deduped) {\n statements.push(buildIndexDDL(spec, { table, fieldToColumn: FIELD_TO_COLUMN, leadingColumns }));\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` (and the DO mirror) for the\n * caller-supplied projection field name; the underlying SQL expression\n * (`json_extract(...)`, column reference) still goes through the strict\n * compiler with no caller input.\n */\nexport function quoteColumnAlias(label: string): string {\n return `\"${label.replace(/\"/g, '\"\"')}\"`;\n}\n","/**\n * Compile firegraph queries and writes into parameterized SQLite statements.\n *\n * The single-table SQLite schema mirrors the Firestore record envelope:\n * built-in fields (`aType`, `aUid`, etc.) are typed columns, and `data` is a\n * JSON string. `data.<key>` filter fields are translated to `json_extract`\n * expressions; built-in fields go straight to their column.\n */\n\nimport { FiregraphError } from '../errors.js';\nimport type { UpdatePayload, WritableRecord, WriteMode } from '../internal/backend.js';\nimport { NODE_RELATION } from '../internal/constants.js';\nimport {\n compileDataOpsExpr,\n isFirestoreSpecialType,\n validateJsonPathKey,\n} from '../internal/sqlite-data-ops.js';\nimport { assertJsonSafePayload } from '../internal/sqlite-payload-guard.js';\nimport { FIELD_TO_COLUMN, quoteColumnAlias, quoteIdent } from '../internal/sqlite-schema.js';\nimport { assertUpdatePayloadExclusive, flattenPatch } from '../internal/write-plan.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';\n\nconst SQLITE_BACKEND_LABEL = 'shared-table SQLite';\nconst SQLITE_BACKEND_ERR_LABEL = 'SQLite backend';\n\nexport interface CompiledStatement {\n sql: string;\n params: unknown[];\n}\n\n/**\n * Translate a firegraph filter field to either a column reference or a\n * `json_extract(\"data\", '$.<path>')` expression with the JSON path\n * **inlined as a 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\", ?)` with the path as a bound parameter would never hit the\n * index, so every `data.*` filter would fall back to a full scan even\n * when a matching expression index exists. The index builder in\n * `sqlite-index-ddl.ts` emits the inlined form, and this compiler must\n * match it verbatim.\n *\n * Safety: each path component passes `JSON_PATH_KEY_RE`\n * (`/^[A-Za-z_][A-Za-z0-9_-]*$/`) before embedding, which excludes every\n * character SQLite treats as syntax (quote, dot, bracket, whitespace).\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, SQLITE_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 value into a SQLite-bindable primitive. JS objects\n * (other than null) are JSON-stringified so they can match values stored\n * via `json_extract`.\n *\n * Firestore special types (Timestamp, GeoPoint, VectorValue,\n * DocumentReference, FieldValue) are rejected with `INVALID_QUERY` —\n * `JSON.stringify` would emit garbage (`{}` for Timestamp, etc.) that\n * silently fails to match anything. Convert to a primitive at the call site\n * (e.g. `ts.toMillis()` or `ts.toDate().toISOString()`) 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 ` +\n `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 * SELECT all rows matching `filters` within `scope`. The scope filter is\n * always added as the leading predicate so the `(scope, …)` indexes apply.\n */\nexport function compileSelect(\n table: string,\n scope: string,\n filters: QueryFilter[],\n options?: QueryOptions,\n): CompiledStatement {\n const params: unknown[] = [];\n const conditions: string[] = ['\"scope\" = ?'];\n params.push(scope);\n\n for (const f of filters) {\n conditions.push(compileFilter(f, params));\n }\n\n let sql = `SELECT * FROM ${quoteIdent(table)} WHERE ${conditions.join(' AND ')}`;\n // ORDER BY parameters must come after WHERE parameters in the bind list.\n const orderClause = compileOrderBy(options, params);\n sql += orderClause;\n sql += compileLimit(options, params);\n\n return { sql, params };\n}\n\n/**\n * Compile an aggregate query into a single `SELECT` statement.\n *\n * Each entry in `spec` becomes one aggregate function in the projection\n * list, aliased with the caller-supplied key. Field references reuse\n * `compileFieldRef` so dotted `data.*` paths are translated to\n * `json_extract` exactly the same way as in regular filters — including\n * the index-friendly inlined-path form.\n *\n * Numeric coercion: SUM/AVG/MIN/MAX cast `json_extract` results through\n * `CAST(... AS REAL)`. SQLite stores JSON as text, so without the cast a\n * numeric value extracted from the JSON column comes back as a string and\n * `MIN`/`MAX` would compare lexicographically (`\"100\" < \"20\"`). The cast\n * forces numeric semantics on those three; `COUNT(*)` is unaffected.\n *\n * Empty result handling matches the Firestore semantics surfaced by the\n * `runFirestoreAggregate` helper: SUM/MIN/MAX of an empty set returns 0\n * (resolving SQLite's `SUM(NULL) = NULL` to a clean number); AVG returns\n * `NaN` (mathematically undefined for empty input). The translation\n * happens at the JS layer in the SQLite backend so the SQL stays simple.\n */\nexport function compileAggregate(\n table: string,\n scope: 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 // Validate the alias as a JSON path key — same charset rule used for\n // dotted field references. This guards against accidental SQL\n // injection through caller-supplied alias names; aliases are inlined\n // (not parametrised) because SQL aliases can't be bound parameters.\n validateJsonPathKey(alias, SQLITE_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[] = [scope];\n const conditions: string[] = ['\"scope\" = ?'];\n for (const f of filters) {\n conditions.push(compileFilter(f, params));\n }\n\n const sql =\n `SELECT ${projections.join(', ')} ` +\n `FROM ${quoteIdent(table)} ` +\n `WHERE ${conditions.join(' AND ')}`;\n return { stmt: { sql, params }, aliases };\n}\n\n/**\n * Cross-scope SELECT — equivalent to Firestore's collection group query.\n * Used by `findEdgesGlobal`.\n *\n * `scopeNameFilter`, when present, narrows the result to rows whose scope's\n * last materialized-path segment equals the given subgraph name (or to the\n * root rows when the name equals the table name itself, matching Firestore's\n * `db.collectionGroup(tableName)` semantics).\n */\nexport function compileSelectGlobal(\n table: string,\n filters: QueryFilter[],\n options?: QueryOptions,\n scopeNameFilter?: { name: string; isRoot: boolean },\n): CompiledStatement {\n if (filters.length === 0) {\n throw new FiregraphError(\n 'compileSelectGlobal requires at least one filter — refusing to issue an unbounded SELECT.',\n 'INVALID_QUERY',\n );\n }\n\n const params: unknown[] = [];\n const conditions: string[] = [];\n\n if (scopeNameFilter) {\n if (scopeNameFilter.isRoot) {\n conditions.push(`\"scope\" = ?`);\n params.push('');\n } else {\n conditions.push(`\"scope\" LIKE ? ESCAPE '\\\\'`);\n params.push(`%/${escapeLike(scopeNameFilter.name)}`);\n }\n }\n\n for (const f of filters) {\n conditions.push(compileFilter(f, params));\n }\n\n const sql =\n `SELECT * FROM ${quoteIdent(table)} WHERE ${conditions.join(' AND ')}` +\n compileOrderBy(options, params) +\n compileLimit(options, params);\n return { sql, params };\n}\n\nexport function compileSelectByDocId(\n table: string,\n scope: string,\n docId: string,\n): CompiledStatement {\n return {\n sql: `SELECT * FROM ${quoteIdent(table)} WHERE \"scope\" = ? AND \"doc_id\" = ? LIMIT 1`,\n params: [scope, 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 from the SQL row.\n */\nexport type ProjectedColumnKind =\n /** Top-level TEXT column: `a_type`, `a_uid`, `axb_type`, `b_type`, `b_uid`. */\n | 'builtin-text'\n /** Top-level INTEGER column: `v`. */\n | 'builtin-int'\n /** Top-level INTEGER millis column: `created_at`, `updated_at`.\n * Decoder wraps the millisecond value in `GraphTimestampImpl` so the\n * output matches `findEdges` / `findNodes`. */\n | 'builtin-timestamp'\n /** Whole `data` JSON payload — emitted when the caller projects `'data'`\n * literally. Decoder JSON.parses the column. */\n | 'data'\n /** Any `data.<path>` projection. The compiler emits a paired\n * `json_extract(...)` value column and `json_type(...)` type column;\n * the decoder uses the type to recover JSON-encoded objects/arrays as\n * native JS while passing primitives through verbatim. */\n | 'json';\n\n/** Per-column metadata returned alongside the compiled statement. The\n * backend uses this to translate row -> projected JS object. */\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 /** Alias of the paired `json_type(...)` column when `kind === 'json'`,\n * otherwise undefined. Stored explicitly (rather than derived from\n * `field`) so the compiler can use a guaranteed-unique sentinel that\n * cannot collide with any caller-supplied select entry. */\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` (with no dot and not in the built-in map) is rewritten to\n * `data.name`.\n *\n * Why bare names: the documented use case for `findEdgesProjected` is\n * \"give me titles and dates for a list view\", and the canonical example\n * (`select: ['title', 'date']`) reads naturally as bare names. Requiring\n * `'data.title'` would be portable to `WhereClause.field` but uglier in the\n * common case. The result row keys preserve the original form (`title`,\n * not `data.title`) so callers see what they asked for.\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 * statement that returns only the requested fields.\n *\n * Shape:\n *\n * SELECT\n * <expr-1> AS \"<field-1>\", [json_type(...) AS \"__fg_t_<idx>\",]\n * <expr-2> AS \"<field-2>\", [json_type(...) AS \"__fg_t_<idx>\",]\n * ...\n * FROM <table>\n * WHERE \"scope\" = ? AND <filters>\n * [ORDER BY ...]\n * [LIMIT ?]\n *\n * For `data.*` fields the compiler also projects `json_type` so the\n * decoder can distinguish a stored string from a serialized object/array\n * (`json_extract` returns both as TEXT; `json_type` is the only reliable\n * disambiguator). The cost is one extra column per `data.*` field — all in\n * the same row, no extra round trip. The companion alias uses a positional\n * sentinel `__fg_t_<idx>` rather than `<field>__t` so it cannot collide\n * with a user-provided field literally named `<x>__t`.\n *\n * Duplicate entries in `select` are de-duped at compile time so the SQL\n * projection list carries one column per unique field. Order in the input\n * `select` is preserved (first occurrence wins).\n *\n * The compiler rejects an empty `select` (the client wrapper enforces this\n * too — both layers reject so a misuse caught by either surfaces a clean\n * `INVALID_QUERY`).\n */\nexport function compileFindEdgesProjected(\n table: string,\n scope: 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 // De-dupe while preserving first-occurrence order. Two entries that\n // differ only by normalization (e.g. `'title'` and `'data.title'`)\n // remain distinct so the result row carries both keys — that's the\n // caller's choice and we honour it.\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 — this is the key\n // the decoder reads back from each result row, and the contract is\n // \"projection result is keyed by what the caller passed in\". Use the\n // relaxed alias quoter so dotted paths like `data.detail.region` are\n // accepted (the strict `quoteIdent` is for table/column names only).\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 // Pair every json_extract with a json_type so the decoder can\n // recover objects/arrays. The paired column needs a guaranteed-\n // unique alias — `<field>__t` would collide if the caller projects\n // both `'foo'` and `'foo__t'` (legal user input). Use a positional\n // sentinel keyed by the field's de-duped index. The decoder reads\n // the type column off the same name we generated here, so we record\n // it in the column spec rather than reconstructing from the field.\n typeAliasName = `__fg_t_${idx}`;\n const typeAlias = quoteColumnAlias(typeAliasName);\n projections.push(`json_type(\"data\", '$.${canonical.slice(5)}') AS ${typeAlias}`);\n } else {\n // Built-in field. Discriminate by column name so the decoder can\n // wrap timestamps in `GraphTimestampImpl` and coerce `v` to number.\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[] = [scope];\n const conditions: string[] = ['\"scope\" = ?'];\n for (const f of filters) {\n conditions.push(compileFilter(f, params));\n }\n\n let sql =\n `SELECT ${projections.join(', ')} ` +\n `FROM ${quoteIdent(table)} ` +\n `WHERE ${conditions.join(' AND ')}`;\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 described by `columns`.\n *\n * Built-in TEXT/INTEGER columns pass through with light coercion (BigInt\n * to number for `v`); timestamps wrap into `GraphTimestampImpl`. `data.*`\n * fields use the paired `json_type` column to decide whether to JSON.parse\n * the value (objects and arrays come back as JSON-encoded TEXT from\n * `json_extract`; primitives come through with their native SQLite type).\n *\n * The function is exported so the SQLite and shared SQLite backends share\n * one decoder; both call this with the spec returned from\n * `compileFindEdgesProjected`.\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 // `v` is nullable in the schema; preserve null explicitly so\n // callers can distinguish \"no version\" from a numeric 0.\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 = toMillis(raw);\n out[c.field] = GraphTimestampImpl.fromMillis(ms) as unknown as GraphTimestamp;\n break;\n }\n case 'data':\n // Whole `data` payload — JSON.parse the column directly. Empty /\n // null defaults to `{}` for symmetry with `rowToRecord`.\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 — the historical `<field>__t`\n // suffix would silently collide if the caller projected both\n // `'foo'` and `'foo__t'`.\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 // Stored object/array — `json_extract` returned a JSON-encoded\n // string. Re-parse to recover native JS shape.\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 // text / real / true / false / null — pass through. SQLite's\n // driver already returns the native JS primitive type.\n out[c.field] = raw;\n }\n break;\n }\n }\n }\n return out;\n}\n\n/**\n * Compile an `expand()` fan-out into one SELECT statement.\n *\n * Shape (forward direction, with hydrate=false):\n *\n * SELECT * FROM <table>\n * WHERE \"scope\" = ? AND \"axbType\" = ? AND \"aUid\" IN (?, ?, …)\n * [AND \"aType\" = ?] [AND \"bType\" = ?]\n * [ORDER BY …]\n * [LIMIT ?]\n *\n * The `IN (\"aUid\", …)` clause replaces the per-source `findEdges` loop\n * `traverse.ts` would otherwise issue, collapsing N round trips into one\n * regardless of how many sources the caller passes. Reverse direction\n * swaps the `IN` predicate to `\"bUid\"` and the optional `aType`/`bType`\n * filters cover the same dimensions either way.\n *\n * Hydration (`params.hydrate === true`) is **not** baked into this\n * function. The backend issues a follow-up `SELECT * WHERE \"scope\" = ?\n * AND \"aUid\" = \"bUid\" AND \"axbType\" = 'is' AND \"bUid\" IN (…)` against the\n * target-side UIDs and stitches the alignment in JS. A single-statement\n * JOIN is technically possible (edges and nodes share one table here)\n * but produces colliding column names and forces a manual aliased\n * row-decoder, with no measurable perf win on an in-process executor.\n * Two `exec()` calls keep the row-decoder shared with the rest of\n * `findEdges` / `findNodes`.\n *\n * Empty `sources` is an invariant violation here — the SQLite backend\n * intercepts that case before reaching the compiler and short-circuits\n * to an empty result. The compiler itself rejects it because an empty\n * `IN ()` clause is invalid SQL.\n */\nexport function compileExpand(\n table: string,\n scope: string,\n params: ExpandParams,\n): 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 'Callers should short-circuit empty input before reaching the compiler.',\n 'INVALID_QUERY',\n );\n }\n const direction = params.direction ?? 'forward';\n // Column identifiers must use the on-disk snake_case names\n // (`a_uid`, `axb_type`, `b_uid`, …) — see `FIELD_TO_COLUMN` in\n // `src/internal/sqlite-schema.ts`. We resolve every column reference\n // through `compileFieldRef` so a future schema rename can't drift\n // between read-paths.\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[] = [scope, params.axbType];\n const conditions: string[] = ['\"scope\" = ?', `${axbTypeCol} = ?`];\n\n // The IN list. Source UIDs are not currently chunked — SQLite has no hard\n // cap on bound parameters (the default `SQLITE_MAX_VARIABLE_NUMBER` is\n // 32766 in modern builds), and traversal callers cap source-set growth\n // through `maxReads` long before any realistic IN-list size. If a caller\n // ever blows past that cap, the backend surfaces it as the underlying\n // SQLite error rather than failing silently.\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 // Exclude self-loop \"node\" rows. Without this, an `axbType = 'is'`\n // expand (which would only happen via an explicit hop on the node\n // relation) could match the node-as-self-loop rows. Forward expand\n // over a non-`is` axbType already excludes them via the axbType\n // predicate; this clause is a belt-and-braces guard for the corner\n // case where the caller explicitly asks for `axbType: 'is'`.\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 // ORDER BY uses the same `compileFieldRef` rules as `findEdges`, so\n // dotted `data.*` paths translate to `json_extract` and hit the same\n // expression indexes when present. Per-source-strict ordering would\n // require window functions; see `ExpandParams.limitPerSource` JSDoc\n // for the contract — this LIMIT is a soft total cap.\n if (params.orderBy) {\n sql += compileOrderBy({ orderBy: params.orderBy }, sqlParams);\n }\n\n if (params.limitPerSource !== undefined) {\n // Total cap = sources.length * limitPerSource. We multiply at compile\n // time so the bound parameter is a concrete integer; SQLite parses\n // `LIMIT ?` once and the executor binds it in flight.\n const totalLimit = params.sources.length * params.limitPerSource;\n sql += ` LIMIT ?`;\n sqlParams.push(totalLimit);\n }\n\n return { sql, params: sqlParams };\n}\n\n/**\n * Compile the hydration-pass query for `expand({ hydrate: true })`. Issues\n * one statement against the same table that fetches every node row whose\n * `bUid` is in the supplied set. The backend stitches alignment in JS\n * (a `Map<bUid, StoredGraphRecord>` keyed by the canonical node UID).\n *\n * Empty input is rejected for the same reason as `compileExpand`.\n */\nexport function compileExpandHydrate(\n table: string,\n scope: string,\n targetUids: string[],\n): 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[] = [scope, NODE_RELATION];\n for (const uid of targetUids) sqlParams.push(uid);\n\n // Resolve column refs via `compileFieldRef` — see `compileExpand` for\n // the schema-rename rationale.\n const aUidCol = compileFieldRef('aUid').expr;\n const bUidCol = compileFieldRef('bUid').expr;\n const axbTypeCol = compileFieldRef('axbType').expr;\n\n // Self-loop predicate (`a_uid = b_uid`) is what distinguishes a node\n // row from an edge row in the single-table schema. Without it, an\n // accidental `is`-typed edge (which firegraph forbids today, but the\n // schema doesn't enforce) could mask a real node row in the result.\n return {\n sql:\n `SELECT * FROM ${quoteIdent(table)} ` +\n `WHERE \"scope\" = ? AND ${axbTypeCol} = ? AND ${aUidCol} = ${bUidCol} AND ${bUidCol} IN (${placeholders})`,\n params: sqlParams,\n };\n}\n\n/**\n * Compile a `setDoc(record, mode)` call into a single statement.\n *\n * - `mode === 'replace'` — `INSERT OR REPLACE`. Every row column is\n * overwritten; any pre-existing JSON keys not present in `record.data`\n * are dropped.\n * - `mode === 'merge'` — `INSERT … ON CONFLICT(scope, doc_id) DO UPDATE\n * SET …`. New rows insert the full record; existing rows have their\n * `data` JSON deep-merged via the chained `json_set`/`json_remove`\n * expression produced by `compileDataOpsExpr`. Sibling keys at every\n * depth survive. Arrays and primitives are terminal (replaced as a\n * unit), matching Firestore's `.set(..., { merge: true })` behaviour.\n *\n * `created_at` is re-stamped on every put — both modes use\n * `excluded.created_at` so the cross-backend contract matches today's\n * Firestore behaviour. (Future work: switch to insert-only createdAt; out\n * of scope for the merge-semantics fix.)\n */\nexport function compileSet(\n table: string,\n scope: string,\n docId: string,\n record: WritableRecord,\n nowMillis: number,\n mode: WriteMode,\n): CompiledStatement {\n // Eager validation. Both branches below feed `record.data` to a raw\n // `JSON.stringify` for the INSERT path; flattenPatch only catches issues\n // on the merge UPDATE branch and only fires when there's a conflicting\n // row. Validating up front keeps first-insert and ON CONFLICT errors\n // identical, and rejects the DELETE_FIELD sentinel that JSON.stringify\n // would silently drop.\n assertJsonSafePayload(record.data, SQLITE_BACKEND_LABEL);\n if (mode === 'replace') {\n const sql = `INSERT OR REPLACE INTO ${quoteIdent(table)} (\n doc_id, scope, 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 scope,\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 // Merge mode.\n const insertParams: unknown[] = [\n docId,\n scope,\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 // Translate record.data into deep-path ops, then build the merge expr.\n const ops = flattenPatch(record.data ?? {});\n const updateParams: unknown[] = [];\n const dataExpr =\n compileDataOpsExpr(ops, `COALESCE(\"data\", '{}')`, updateParams, SQLITE_BACKEND_ERR_LABEL) ??\n `COALESCE(\"data\", '{}')`;\n\n // `v` uses COALESCE(excluded.v, v) so an incoming record with `v=undefined`\n // (registry has no migrations, or stampWritableRecord didn't stamp) leaves\n // any previously-stamped `v` intact. Firestore's `set(record, {merge: true})`\n // omits the key when undefined and behaves the same way; SQLite must too.\n // Without COALESCE, `excluded.v` would be NULL and clobber a pre-existing\n // `v` — which silently breaks migration replay if migrations are removed\n // and later re-added to a type.\n const sql = `INSERT INTO ${quoteIdent(table)} (\n doc_id, scope, a_type, a_uid, axb_type, b_type, b_uid, data, v, created_at, updated_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(scope, 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. (Used by migration\n * write-back.)\n * - `dataOps` deep-merges via chained `json_remove` / `json_set` —\n * siblings at every nesting depth survive; arrays / primitives /\n * Firestore special types are terminal; 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 scope: 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, SQLITE_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, SQLITE_BACKEND_LABEL);\n }\n const expr = compileDataOpsExpr(\n update.dataOps,\n `COALESCE(\"data\", '{}')`,\n params,\n SQLITE_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 // WHERE params come last\n params.push(scope, docId);\n\n return {\n sql: `UPDATE ${quoteIdent(table)} SET ${setClauses.join(', ')} WHERE \"scope\" = ? AND \"doc_id\" = ?`,\n params,\n };\n}\n\nexport function compileDelete(table: string, scope: string, docId: string): CompiledStatement {\n return {\n sql: `DELETE FROM ${quoteIdent(table)} WHERE \"scope\" = ? AND \"doc_id\" = ?`,\n params: [scope, docId],\n };\n}\n\n/**\n * Compile a server-side bulk DELETE — `query.dml` capability.\n *\n * Mirrors `compileSelect`'s WHERE construction (scope-leading predicate\n * + filter list) so any composite index that accelerates a `findEdges`\n * also accelerates the equivalent `bulkDelete`. Empty-filter callers (a\n * \"delete everything in this scope\" sweep) are accepted — the caller is\n * expected to have opted into a collection scan via `allowCollectionScan`\n * at the client layer; the SQL itself is the same shape regardless.\n */\nexport function compileBulkDelete(\n table: string,\n scope: string,\n filters: QueryFilter[],\n): CompiledStatement {\n const params: unknown[] = [scope];\n const conditions: string[] = ['\"scope\" = ?'];\n for (const f of filters) {\n conditions.push(compileFilter(f, params));\n }\n return {\n sql: `DELETE FROM ${quoteIdent(table)} WHERE ${conditions.join(' AND ')}`,\n params,\n };\n}\n\n/**\n * Compile a server-side bulk UPDATE — `query.dml` capability.\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 * single-row `compileUpdate` uses. Identifying columns (`aType`, `axbType`,\n * `aUid`, `bType`, `bUid`, `v`) are intentionally read-only through this\n * path — to relabel rows, delete and re-insert.\n *\n * Empty-patch (no leaves to merge) is rejected: a no-op UPDATE that only\n * touched `updated_at` would silently rewrite every matching row's\n * timestamp, which is almost never what the caller wants. If you want to\n * stamp without editing data, use `setDoc` with `'merge'`.\n */\nexport function compileBulkUpdate(\n table: string,\n scope: 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, SQLITE_BACKEND_LABEL);\n }\n const setParams: unknown[] = [];\n const expr = compileDataOpsExpr(\n dataOps,\n `COALESCE(\"data\", '{}')`,\n setParams,\n SQLITE_BACKEND_ERR_LABEL,\n );\n if (expr === null) {\n // `compileDataOpsExpr` only returns null when there's nothing to do —\n // we already guarded the empty-patch case above so this is unreachable\n // in practice, but the type system can't see that.\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 // WHERE: scope + filters. Filter params follow the SET params in the\n // bind list — same ordering convention as `compileUpdate` /\n // `compileSelect`.\n const whereParams: unknown[] = [scope];\n const conditions: string[] = ['\"scope\" = ?'];\n for (const f of filters) {\n conditions.push(compileFilter(f, whereParams));\n }\n\n return {\n sql:\n `UPDATE ${quoteIdent(table)} SET ${setClauses.join(', ')} ` +\n `WHERE ${conditions.join(' AND ')}`,\n params: [...setParams, ...whereParams],\n };\n}\n\n/**\n * Delete every row whose scope starts with `scopePrefix` followed by '/'.\n * Used by cascade delete to wipe all subgraphs nested under a node.\n *\n * The trailing '/' guard prevents `'a'` from matching `'ab'`. The exact-\n * match case (`scope = scopePrefix`) is intentionally NOT included: the\n * prefix passed in is always `<storageScope>/<uid>` (or just `<uid>` at\n * root), which is an odd-segment count, while every stored row's scope is\n * `''` (root) or an even-segment `<uid>/<name>[/…]` pair sequence — so\n * `scope = scopePrefix` can never match a real row.\n */\nexport function compileDeleteScopePrefix(table: string, scopePrefix: string): CompiledStatement {\n // SQLite LIKE escape: prefix could contain '%' or '_'. Escape with '\\\\'.\n const escaped = escapeLike(scopePrefix);\n return {\n sql: `DELETE FROM ${quoteIdent(table)} WHERE \"scope\" LIKE ? ESCAPE '\\\\'`,\n params: [`${escaped}/%`],\n };\n}\n\n/**\n * Count rows that `compileDeleteScopePrefix` would delete. Used by cascade\n * to report an accurate total in `CascadeResult.deleted` — the prefix-\n * delete is a single SQL statement and the executor surface doesn't expose\n * per-row counts. One extra index lookup per cascade is cheap relative to\n * the delete itself.\n */\nexport function compileCountScopePrefix(table: string, scopePrefix: string): CompiledStatement {\n const escaped = escapeLike(scopePrefix);\n return {\n sql: `SELECT COUNT(*) AS n FROM ${quoteIdent(table)} WHERE \"scope\" LIKE ? ESCAPE '\\\\'`,\n params: [`${escaped}/%`],\n };\n}\n\nfunction escapeLike(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/%/g, '\\\\%').replace(/_/g, '\\\\_');\n}\n\n/**\n * Convert a row returned by a SQLite driver into a `StoredGraphRecord`.\n * `created_at`/`updated_at` are wrapped in `GraphTimestampImpl` so they\n * present the same surface as Firestore's `Timestamp`.\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 = toMillis(row.created_at);\n const updatedMs = toMillis(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\nfunction toMillis(value: unknown): number {\n if (typeof value === 'number') return value;\n if (typeof value === 'bigint') return Number(value);\n if (typeof value === 'string') return Number(value);\n return 0;\n}\n","/**\n * SQLite implementation of `StorageBackend`.\n *\n * Uses a single table keyed by `(scope, doc_id)`. Subgraphs are encoded in\n * the `scope` column as a materialized path of interleaved parent UIDs and\n * subgraph names — `''` at the root, `'<uid>/<name>'` one level down,\n * `'<uid1>/<name1>/<uid2>/<name2>'` two levels down, and so on. Cascade\n * delete uses a single `DELETE … WHERE scope LIKE 'prefix/%'` instead of\n * walking subcollections.\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 { buildEdgeQueryPlan } from '../query.js';\nimport type {\n AggregateSpec,\n BulkBatchError,\n BulkOptions,\n BulkResult,\n BulkUpdatePatch,\n CascadeResult,\n ExpandParams,\n ExpandResult,\n FindEdgesParams,\n GraphReader,\n QueryFilter,\n QueryOptions,\n StoredGraphRecord,\n} from '../types.js';\nimport type { CompiledStatement } from './sql.js';\nimport {\n compileAggregate,\n compileBulkDelete,\n compileBulkUpdate,\n compileCountScopePrefix,\n compileDelete,\n compileDeleteScopePrefix,\n compileExpand,\n compileExpandHydrate,\n compileFindEdgesProjected,\n compileSelect,\n compileSelectByDocId,\n compileSelectGlobal,\n compileSet,\n compileUpdate,\n decodeProjectedRow,\n rowToRecord,\n} from './sql.js';\n\nexport interface SqliteBackendOptions {\n /** Logical scope path (chained subgraph names) — used for `allowedIn` matching. */\n scopePath?: string;\n /** Internal storage scope (interleaved parent-uid/name path). */\n storageScope?: string;\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 private readonly storageScope: string,\n ) {}\n\n async getDoc(docId: string): Promise<StoredGraphRecord | null> {\n const stmt = compileSelectByDocId(this.tableName, this.storageScope, 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, this.storageScope, 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, this.storageScope, 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, this.storageScope, 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} (scope=${this.storageScope})`,\n 'NOT_FOUND',\n );\n }\n }\n\n async deleteDoc(docId: string): Promise<void> {\n const stmt = compileDelete(this.tableName, this.storageScope, 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 storageScope: string,\n ) {}\n\n setDoc(docId: string, record: WritableRecord, mode: WriteMode): void {\n this.statements.push(\n compileSet(this.tableName, this.storageScope, docId, record, Date.now(), mode),\n );\n }\n\n updateDoc(docId: string, update: UpdatePayload): void {\n this.statements.push(\n compileUpdate(this.tableName, this.storageScope, docId, update, Date.now()),\n );\n }\n\n deleteDoc(docId: string): void {\n this.statements.push(compileDelete(this.tableName, this.storageScope, docId));\n }\n\n async commit(): Promise<void> {\n if (this.statements.length === 0) return;\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 /** Logical table name (returned through `collectionPath` for parity with Firestore). */\n readonly collectionPath: string;\n readonly scopePath: string;\n /** Materialized storage scope (interleaved parent UIDs + subgraph names). */\n private readonly storageScope: string;\n\n constructor(\n private readonly executor: SqliteExecutor,\n tableName: string,\n storageScope: string,\n scopePath: string,\n ) {\n this.collectionPath = tableName;\n this.storageScope = storageScope;\n this.scopePath = scopePath;\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 // --- Reads ---\n\n async getDoc(docId: string): Promise<StoredGraphRecord | null> {\n const stmt = compileSelectByDocId(this.collectionPath, this.storageScope, docId);\n const rows = await this.executor.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.collectionPath, this.storageScope, filters, options);\n const rows = await this.executor.all(stmt.sql, stmt.params);\n return rows.map(rowToRecord);\n }\n\n // --- Writes ---\n\n async setDoc(docId: string, record: WritableRecord, mode: WriteMode): Promise<void> {\n const stmt = compileSet(\n this.collectionPath,\n this.storageScope,\n docId,\n record,\n Date.now(),\n mode,\n );\n await this.executor.run(stmt.sql, stmt.params);\n }\n\n async updateDoc(docId: string, update: UpdatePayload): Promise<void> {\n const stmt = compileUpdate(this.collectionPath, this.storageScope, 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} (scope=${this.storageScope})`,\n 'NOT_FOUND',\n );\n }\n }\n\n async deleteDoc(docId: string): Promise<void> {\n const stmt = compileDelete(this.collectionPath, this.storageScope, docId);\n await this.executor.run(stmt.sql, stmt.params);\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 return this.executor.transaction(async (tx) => {\n const txBackend = new SqliteTransactionBackendImpl(\n tx,\n this.collectionPath,\n this.storageScope,\n );\n return fn(txBackend);\n });\n }\n\n createBatch(): BatchBackend {\n return new SqliteBatchBackendImpl(this.executor, this.collectionPath, this.storageScope);\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(this.executor, this.collectionPath, newStorageScope, newScope);\n }\n\n // --- Cascade & bulk ---\n\n async removeNodeCascade(\n uid: string,\n reader: GraphReader,\n options?: BulkOptions,\n ): Promise<CascadeResult> {\n // Collect all edges touching the node in the current scope (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 // Pre-count subgraph rows so the returned `deleted` total reflects the\n // actual number of records removed by the prefix-delete (which is a\n // single statement, but may match many rows). One extra index lookup is\n // cheap relative to the cascade itself.\n let subgraphRowCount = 0;\n if (shouldDeleteSubgraphs) {\n const prefix = this.storageScope ? `${this.storageScope}/${uid}` : uid;\n const countStmt = compileCountScopePrefix(this.collectionPath, prefix);\n const countRows = await this.executor.all(countStmt.sql, countStmt.params);\n const first = countRows[0] as Record<string, unknown> | undefined;\n const n = first?.n;\n subgraphRowCount = typeof n === 'bigint' ? Number(n) : Number(n ?? 0);\n }\n\n // Build the full statement list. Order: edges → node → prefix-delete.\n // When the executor's `batch()` is fully atomic (DO SQLite uses\n // `transactionSync`) the chunking loop below collapses to a single batch\n // and 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, this.storageScope, id),\n );\n writeStatements.push(compileDelete(this.collectionPath, this.storageScope, nodeDocId));\n if (shouldDeleteSubgraphs) {\n const prefix = this.storageScope ? `${this.storageScope}/${uid}` : uid;\n writeStatements.push(compileDeleteScopePrefix(this.collectionPath, prefix));\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 const nodeDeleted = allOk;\n\n // `stmtDeleted` counts committed *statements*. Replace the prefix-\n // delete's per-statement contribution (1) with the pre-computed row\n // count so callers see a true row total. Only credit subgraph rows when\n // every chunk succeeded — partial failure means we can't be sure the\n // chunk containing the prefix-delete actually committed.\n const prefixStatementContribution = shouldDeleteSubgraphs && allOk ? 1 : 0;\n const deleted = stmtDeleted - prefixStatementContribution + (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 // 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) =>\n compileDelete(this.collectionPath, this.storageScope, id),\n );\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 prefix-delete\n * statement contributes 1 to that total even though it may match many\n * rows; `removeNodeCascade` patches that up with a pre-counted row total.\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 2-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 // --- Cross-scope (collection group) ---\n\n async findEdgesGlobal(\n params: FindEdgesParams,\n collectionName?: string,\n ): Promise<StoredGraphRecord[]> {\n const plan = buildEdgeQueryPlan(params);\n if (plan.strategy === 'get') {\n throw new FiregraphError(\n 'findEdgesGlobal() requires a query, not a direct document lookup. ' +\n 'Omit one of aUid/axbType/bUid to force a query strategy.',\n 'INVALID_QUERY',\n );\n }\n // Mirror Firestore's `collectionGroup(name)` semantics over the\n // materialized-scope SQLite layout: when `collectionName` matches the\n // table name (the implicit root default), filter to root rows; otherwise\n // filter to rows whose scope's last segment equals the requested name.\n const name = collectionName ?? this.collectionPath;\n const scopeNameFilter = {\n name,\n isRoot: name === this.collectionPath,\n };\n const stmt = compileSelectGlobal(\n this.collectionPath,\n plan.filters,\n plan.options,\n scopeNameFilter,\n );\n const rows = await this.executor.all(stmt.sql, stmt.params);\n return rows.map(rowToRecord);\n }\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(\n this.collectionPath,\n this.storageScope,\n spec,\n filters,\n );\n const rows = await 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 scoping is enforced inside `compileBulkDelete` (the leading\n * `\"scope\" = ?` predicate) so this method, like every other backend\n * surface, naturally honours subgraph isolation.\n */\n async bulkDelete(filters: QueryFilter[], options?: BulkOptions): Promise<BulkResult> {\n const stmt = compileBulkDelete(this.collectionPath, this.storageScope, 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 const stmt = compileBulkUpdate(\n this.collectionPath,\n this.storageScope,\n filters,\n patch.data,\n Date.now(),\n );\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, this.storageScope, params);\n const rows = await 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, this.storageScope, 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 this.storageScope,\n select,\n filters,\n options,\n );\n const rows = await 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 single table that holds every triple. The driver must\n * have already created the table and indexes via `buildSchemaStatements()`\n * before any reads/writes arrive — callers that ship their own SQLite\n * driver are responsible for wiring that up.\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(executor, tableName, storageScope, scopePath);\n}\n"],"mappings":"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","names":["sql"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typicalday/firegraph",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Generic Firestore adjacency graph client with smart query planning",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -66,6 +66,36 @@
|
|
|
66
66
|
"types": "./dist/backend.d.cts",
|
|
67
67
|
"default": "./dist/backend.cjs"
|
|
68
68
|
}
|
|
69
|
+
},
|
|
70
|
+
"./firestore-standard": {
|
|
71
|
+
"import": {
|
|
72
|
+
"types": "./dist/firestore-standard/index.d.ts",
|
|
73
|
+
"default": "./dist/firestore-standard/index.js"
|
|
74
|
+
},
|
|
75
|
+
"require": {
|
|
76
|
+
"types": "./dist/firestore-standard/index.d.cts",
|
|
77
|
+
"default": "./dist/firestore-standard/index.cjs"
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"./firestore-enterprise": {
|
|
81
|
+
"import": {
|
|
82
|
+
"types": "./dist/firestore-enterprise/index.d.ts",
|
|
83
|
+
"default": "./dist/firestore-enterprise/index.js"
|
|
84
|
+
},
|
|
85
|
+
"require": {
|
|
86
|
+
"types": "./dist/firestore-enterprise/index.d.cts",
|
|
87
|
+
"default": "./dist/firestore-enterprise/index.cjs"
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"./sqlite": {
|
|
91
|
+
"import": {
|
|
92
|
+
"types": "./dist/sqlite/index.d.ts",
|
|
93
|
+
"default": "./dist/sqlite/index.js"
|
|
94
|
+
},
|
|
95
|
+
"require": {
|
|
96
|
+
"types": "./dist/sqlite/index.d.cts",
|
|
97
|
+
"default": "./dist/sqlite/index.cjs"
|
|
98
|
+
}
|
|
69
99
|
}
|
|
70
100
|
},
|
|
71
101
|
"bin": {
|
|
@@ -105,7 +135,7 @@
|
|
|
105
135
|
},
|
|
106
136
|
"peerDependencies": {
|
|
107
137
|
"@cloudflare/workers-types": "^4.20240419.0",
|
|
108
|
-
"@google-cloud/firestore": "^8.
|
|
138
|
+
"@google-cloud/firestore": "^8.5.0",
|
|
109
139
|
"react": "^18.0.0 || ^19.0.0",
|
|
110
140
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
111
141
|
"svelte": "^5.0.0"
|
|
@@ -135,7 +165,7 @@
|
|
|
135
165
|
"@cloudflare/workers-types": "^4.20260424.1",
|
|
136
166
|
"@commitlint/cli": "^20.5.0",
|
|
137
167
|
"@commitlint/config-conventional": "^20.5.0",
|
|
138
|
-
"@google-cloud/firestore": "^8.
|
|
168
|
+
"@google-cloud/firestore": "^8.5.0",
|
|
139
169
|
"@types/better-sqlite3": "^7.6.13",
|
|
140
170
|
"@typescript-eslint/eslint-plugin": "^8.57.2",
|
|
141
171
|
"@typescript-eslint/parser": "^8.57.2",
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { S as StoredGraphRecord, i as QueryFilter, z as QueryOptions, d as GraphReader, m as BulkOptions, C as CascadeResult, F as FindEdgesParams, o as BulkResult } from './types-BGWxcpI_.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Backend abstraction for firegraph.
|
|
5
|
-
*
|
|
6
|
-
* `StorageBackend` is the single interface every storage driver implements.
|
|
7
|
-
* The Firestore backend wraps `@google-cloud/firestore`; the SQLite backend
|
|
8
|
-
* (shared by D1 and Durable Object SQLite) uses a parameterized SQL executor.
|
|
9
|
-
*
|
|
10
|
-
* `GraphClientImpl` and friends depend only on this interface — they have
|
|
11
|
-
* no direct knowledge of Firestore or SQLite.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Per-record write payload — backend-agnostic. Timestamps are not present;
|
|
16
|
-
* the backend supplies them via `serverTimestamp()` placeholders that it
|
|
17
|
-
* itself resolves at commit time.
|
|
18
|
-
*/
|
|
19
|
-
interface WritableRecord {
|
|
20
|
-
aType: string;
|
|
21
|
-
aUid: string;
|
|
22
|
-
axbType: string;
|
|
23
|
-
bType: string;
|
|
24
|
-
bUid: string;
|
|
25
|
-
data: Record<string, unknown>;
|
|
26
|
-
/** Schema version (set by the writer when registry has migrations). */
|
|
27
|
-
v?: number;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Patch shape for `updateDoc`. Captures the two patterns that exist today:
|
|
31
|
-
* - `dataFields`: shallow merge under `data` (used by `updateNode`)
|
|
32
|
-
* - `replaceData`: full data replacement (used by migration write-back)
|
|
33
|
-
* - `v`: optional schema-version stamp
|
|
34
|
-
*
|
|
35
|
-
* `updatedAt` is always set by the backend.
|
|
36
|
-
*/
|
|
37
|
-
interface UpdatePayload {
|
|
38
|
-
dataFields?: Record<string, unknown>;
|
|
39
|
-
replaceData?: Record<string, unknown>;
|
|
40
|
-
v?: number;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Read/write transaction adapter. Mirrors Firestore's transaction semantics:
|
|
44
|
-
* reads are snapshot-consistent; writes are issued inside the transaction
|
|
45
|
-
* and a rejection from any write aborts the surrounding `runTransaction`.
|
|
46
|
-
*
|
|
47
|
-
* Writes return `Promise<void>` so SQL drivers can surface row-level errors
|
|
48
|
-
* (constraint violations, malformed JSON paths) rather than swallowing them.
|
|
49
|
-
* Firestore implementations can resolve synchronously since the underlying
|
|
50
|
-
* `Transaction.set/update/delete` calls are themselves synchronous buffers.
|
|
51
|
-
*/
|
|
52
|
-
interface TransactionBackend {
|
|
53
|
-
getDoc(docId: string): Promise<StoredGraphRecord | null>;
|
|
54
|
-
query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]>;
|
|
55
|
-
setDoc(docId: string, record: WritableRecord): Promise<void>;
|
|
56
|
-
updateDoc(docId: string, update: UpdatePayload): Promise<void>;
|
|
57
|
-
deleteDoc(docId: string): Promise<void>;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Atomic multi-write batch.
|
|
61
|
-
*/
|
|
62
|
-
interface BatchBackend {
|
|
63
|
-
setDoc(docId: string, record: WritableRecord): void;
|
|
64
|
-
updateDoc(docId: string, update: UpdatePayload): void;
|
|
65
|
-
deleteDoc(docId: string): void;
|
|
66
|
-
commit(): Promise<void>;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* The single storage abstraction.
|
|
70
|
-
*
|
|
71
|
-
* Each backend instance is scoped to a "graph location" — for Firestore
|
|
72
|
-
* that's a collection path; for SQLite it's a (table, scopePath) pair.
|
|
73
|
-
* `subgraph()` returns a child backend bound to a nested location.
|
|
74
|
-
*/
|
|
75
|
-
interface StorageBackend {
|
|
76
|
-
/** Backend-internal location identifier (collection path or table name). */
|
|
77
|
-
readonly collectionPath: string;
|
|
78
|
-
/** Subgraph scope (empty string for root). */
|
|
79
|
-
readonly scopePath: string;
|
|
80
|
-
getDoc(docId: string): Promise<StoredGraphRecord | null>;
|
|
81
|
-
query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]>;
|
|
82
|
-
setDoc(docId: string, record: WritableRecord): Promise<void>;
|
|
83
|
-
updateDoc(docId: string, update: UpdatePayload): Promise<void>;
|
|
84
|
-
deleteDoc(docId: string): Promise<void>;
|
|
85
|
-
runTransaction<T>(fn: (tx: TransactionBackend) => Promise<T>): Promise<T>;
|
|
86
|
-
createBatch(): BatchBackend;
|
|
87
|
-
subgraph(parentNodeUid: string, name: string): StorageBackend;
|
|
88
|
-
removeNodeCascade(uid: string, reader: GraphReader, options?: BulkOptions): Promise<CascadeResult>;
|
|
89
|
-
bulkRemoveEdges(params: FindEdgesParams, reader: GraphReader, options?: BulkOptions): Promise<BulkResult>;
|
|
90
|
-
/**
|
|
91
|
-
* Find edges across all subgraphs sharing a given collection name.
|
|
92
|
-
* Optional — backends that can't support this should throw a clear error.
|
|
93
|
-
*/
|
|
94
|
-
findEdgesGlobal?(params: FindEdgesParams, collectionName?: string): Promise<StoredGraphRecord[]>;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export type { BatchBackend as B, StorageBackend as S, TransactionBackend as T, UpdatePayload as U, WritableRecord as W };
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { S as StoredGraphRecord, i as QueryFilter, z as QueryOptions, d as GraphReader, m as BulkOptions, C as CascadeResult, F as FindEdgesParams, o as BulkResult } from './types-BGWxcpI_.cjs';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Backend abstraction for firegraph.
|
|
5
|
-
*
|
|
6
|
-
* `StorageBackend` is the single interface every storage driver implements.
|
|
7
|
-
* The Firestore backend wraps `@google-cloud/firestore`; the SQLite backend
|
|
8
|
-
* (shared by D1 and Durable Object SQLite) uses a parameterized SQL executor.
|
|
9
|
-
*
|
|
10
|
-
* `GraphClientImpl` and friends depend only on this interface — they have
|
|
11
|
-
* no direct knowledge of Firestore or SQLite.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Per-record write payload — backend-agnostic. Timestamps are not present;
|
|
16
|
-
* the backend supplies them via `serverTimestamp()` placeholders that it
|
|
17
|
-
* itself resolves at commit time.
|
|
18
|
-
*/
|
|
19
|
-
interface WritableRecord {
|
|
20
|
-
aType: string;
|
|
21
|
-
aUid: string;
|
|
22
|
-
axbType: string;
|
|
23
|
-
bType: string;
|
|
24
|
-
bUid: string;
|
|
25
|
-
data: Record<string, unknown>;
|
|
26
|
-
/** Schema version (set by the writer when registry has migrations). */
|
|
27
|
-
v?: number;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Patch shape for `updateDoc`. Captures the two patterns that exist today:
|
|
31
|
-
* - `dataFields`: shallow merge under `data` (used by `updateNode`)
|
|
32
|
-
* - `replaceData`: full data replacement (used by migration write-back)
|
|
33
|
-
* - `v`: optional schema-version stamp
|
|
34
|
-
*
|
|
35
|
-
* `updatedAt` is always set by the backend.
|
|
36
|
-
*/
|
|
37
|
-
interface UpdatePayload {
|
|
38
|
-
dataFields?: Record<string, unknown>;
|
|
39
|
-
replaceData?: Record<string, unknown>;
|
|
40
|
-
v?: number;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Read/write transaction adapter. Mirrors Firestore's transaction semantics:
|
|
44
|
-
* reads are snapshot-consistent; writes are issued inside the transaction
|
|
45
|
-
* and a rejection from any write aborts the surrounding `runTransaction`.
|
|
46
|
-
*
|
|
47
|
-
* Writes return `Promise<void>` so SQL drivers can surface row-level errors
|
|
48
|
-
* (constraint violations, malformed JSON paths) rather than swallowing them.
|
|
49
|
-
* Firestore implementations can resolve synchronously since the underlying
|
|
50
|
-
* `Transaction.set/update/delete` calls are themselves synchronous buffers.
|
|
51
|
-
*/
|
|
52
|
-
interface TransactionBackend {
|
|
53
|
-
getDoc(docId: string): Promise<StoredGraphRecord | null>;
|
|
54
|
-
query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]>;
|
|
55
|
-
setDoc(docId: string, record: WritableRecord): Promise<void>;
|
|
56
|
-
updateDoc(docId: string, update: UpdatePayload): Promise<void>;
|
|
57
|
-
deleteDoc(docId: string): Promise<void>;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Atomic multi-write batch.
|
|
61
|
-
*/
|
|
62
|
-
interface BatchBackend {
|
|
63
|
-
setDoc(docId: string, record: WritableRecord): void;
|
|
64
|
-
updateDoc(docId: string, update: UpdatePayload): void;
|
|
65
|
-
deleteDoc(docId: string): void;
|
|
66
|
-
commit(): Promise<void>;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* The single storage abstraction.
|
|
70
|
-
*
|
|
71
|
-
* Each backend instance is scoped to a "graph location" — for Firestore
|
|
72
|
-
* that's a collection path; for SQLite it's a (table, scopePath) pair.
|
|
73
|
-
* `subgraph()` returns a child backend bound to a nested location.
|
|
74
|
-
*/
|
|
75
|
-
interface StorageBackend {
|
|
76
|
-
/** Backend-internal location identifier (collection path or table name). */
|
|
77
|
-
readonly collectionPath: string;
|
|
78
|
-
/** Subgraph scope (empty string for root). */
|
|
79
|
-
readonly scopePath: string;
|
|
80
|
-
getDoc(docId: string): Promise<StoredGraphRecord | null>;
|
|
81
|
-
query(filters: QueryFilter[], options?: QueryOptions): Promise<StoredGraphRecord[]>;
|
|
82
|
-
setDoc(docId: string, record: WritableRecord): Promise<void>;
|
|
83
|
-
updateDoc(docId: string, update: UpdatePayload): Promise<void>;
|
|
84
|
-
deleteDoc(docId: string): Promise<void>;
|
|
85
|
-
runTransaction<T>(fn: (tx: TransactionBackend) => Promise<T>): Promise<T>;
|
|
86
|
-
createBatch(): BatchBackend;
|
|
87
|
-
subgraph(parentNodeUid: string, name: string): StorageBackend;
|
|
88
|
-
removeNodeCascade(uid: string, reader: GraphReader, options?: BulkOptions): Promise<CascadeResult>;
|
|
89
|
-
bulkRemoveEdges(params: FindEdgesParams, reader: GraphReader, options?: BulkOptions): Promise<BulkResult>;
|
|
90
|
-
/**
|
|
91
|
-
* Find edges across all subgraphs sharing a given collection name.
|
|
92
|
-
* Optional — backends that can't support this should throw a clear error.
|
|
93
|
-
*/
|
|
94
|
-
findEdgesGlobal?(params: FindEdgesParams, collectionName?: string): Promise<StoredGraphRecord[]>;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export type { BatchBackend as B, StorageBackend as S, TransactionBackend as T, UpdatePayload as U, WritableRecord as W };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/serialization.ts"],"sourcesContent":["/**\n * Firestore-aware serialization for the sandbox migration pipeline.\n *\n * Firestore documents can contain special types (Timestamp, GeoPoint,\n * VectorValue, DocumentReference) that don't survive plain JSON\n * round-tripping. This module provides tagged serialization: Firestore\n * types are wrapped in tagged plain objects before JSON marshaling and\n * reconstructed after.\n *\n * Only used by the `defaultExecutor` sandbox path. Static migrations\n * (in-memory functions) receive raw Firestore objects directly.\n */\n\nimport type { DocumentReference, Firestore } from '@google-cloud/firestore';\nimport { FieldValue, GeoPoint, Timestamp } from '@google-cloud/firestore';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Sentinel key used to tag serialized Firestore types. */\nexport const SERIALIZATION_TAG = '__firegraph_ser__' as const;\n\n/** Known discriminator values for tagged types. */\nconst KNOWN_TYPES = new Set(['Timestamp', 'GeoPoint', 'VectorValue', 'DocumentReference']);\n\n// One-time warning for DocumentReference deserialization without db\nlet _docRefWarned = false;\n\n// ---------------------------------------------------------------------------\n// Type guard\n// ---------------------------------------------------------------------------\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// ---------------------------------------------------------------------------\n// Detection helpers\n// ---------------------------------------------------------------------------\n\nfunction isTimestamp(value: unknown): value is Timestamp {\n return value instanceof Timestamp;\n}\n\nfunction isGeoPoint(value: unknown): value is GeoPoint {\n return value instanceof GeoPoint;\n}\n\nfunction isDocumentReference(value: unknown): value is DocumentReference {\n // Duck-type check: DocumentReference has path (string) and firestore properties\n if (value === null || typeof value !== 'object') return false;\n const v = value as Record<string, unknown>;\n return (\n typeof v.path === 'string' &&\n v.firestore !== undefined &&\n typeof v.id === 'string' &&\n v.constructor?.name === 'DocumentReference'\n );\n}\n\nfunction isVectorValue(value: unknown): boolean {\n if (value === null || typeof value !== 'object') return false;\n const v = value as Record<string, unknown>;\n return (\n v.constructor?.name === 'VectorValue' && Array.isArray((v as Record<string, unknown>)._values)\n );\n}\n\n// ---------------------------------------------------------------------------\n// Serialize\n// ---------------------------------------------------------------------------\n\n/**\n * Recursively walk a data object and replace Firestore types with tagged\n * plain objects suitable for JSON serialization.\n *\n * Returns a new object tree — the input is never mutated.\n */\nexport function serializeFirestoreTypes(data: Record<string, unknown>): Record<string, unknown> {\n return serializeValue(data) as Record<string, unknown>;\n}\n\nfunction serializeValue(value: unknown): unknown {\n // Primitives\n if (value === null || value === undefined) return value;\n if (typeof value !== 'object') return value;\n\n // Firestore types (check before generic object/array)\n if (isTimestamp(value)) {\n return {\n [SERIALIZATION_TAG]: 'Timestamp',\n seconds: value.seconds,\n nanoseconds: value.nanoseconds,\n };\n }\n if (isGeoPoint(value)) {\n return {\n [SERIALIZATION_TAG]: 'GeoPoint',\n latitude: value.latitude,\n longitude: value.longitude,\n };\n }\n if (isDocumentReference(value)) {\n return { [SERIALIZATION_TAG]: 'DocumentReference', path: (value as DocumentReference).path };\n }\n if (isVectorValue(value)) {\n // Prefer toArray() (public API) over _values (private internal property)\n const v = value as Record<string, unknown>;\n const values =\n typeof v.toArray === 'function' ? (v.toArray as () => number[])() : (v._values as number[]);\n return { [SERIALIZATION_TAG]: 'VectorValue', values: [...values] };\n }\n\n // Arrays\n if (Array.isArray(value)) {\n return value.map(serializeValue);\n }\n\n // Plain objects — recurse\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(value as Record<string, unknown>)) {\n result[key] = serializeValue((value as Record<string, unknown>)[key]);\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Deserialize\n// ---------------------------------------------------------------------------\n\n/**\n * Recursively walk a data object and reconstruct Firestore types from\n * tagged plain objects.\n *\n * @param data - The data to deserialize (typically from JSON.parse)\n * @param db - Optional Firestore instance for DocumentReference reconstruction.\n * If not provided, tagged DocumentReferences are left as-is with a one-time warning.\n *\n * Returns a new object tree — the input is never mutated.\n */\nexport function deserializeFirestoreTypes(\n data: Record<string, unknown>,\n db?: Firestore,\n): Record<string, unknown> {\n return deserializeValue(data, db) as Record<string, unknown>;\n}\n\nfunction deserializeValue(value: unknown, db?: Firestore): unknown {\n if (value === null || value === undefined) return value;\n if (typeof value !== 'object') return value;\n\n // Short-circuit for values that are already real Firestore types.\n // This makes deserializeFirestoreTypes idempotent — safe to call on data\n // that has already been deserialized (e.g., write-back after defaultExecutor\n // already reconstructed types, or static migrations that return raw types).\n if (\n isTimestamp(value) ||\n isGeoPoint(value) ||\n isDocumentReference(value) ||\n isVectorValue(value)\n ) {\n return value;\n }\n\n // Arrays\n if (Array.isArray(value)) {\n return value.map((v) => deserializeValue(v, db));\n }\n\n const obj = value as Record<string, unknown>;\n\n // Check for tagged Firestore type\n if (isTaggedValue(obj)) {\n const tag = obj[SERIALIZATION_TAG] as string;\n\n switch (tag) {\n case 'Timestamp':\n // Validate expected fields before reconstruction\n if (typeof obj.seconds !== 'number' || typeof obj.nanoseconds !== 'number') return obj;\n return new Timestamp(obj.seconds, obj.nanoseconds);\n\n case 'GeoPoint':\n if (typeof obj.latitude !== 'number' || typeof obj.longitude !== 'number') return obj;\n return new GeoPoint(obj.latitude, obj.longitude);\n\n case 'VectorValue':\n if (!Array.isArray(obj.values)) return obj;\n return FieldValue.vector(obj.values as number[]);\n\n case 'DocumentReference':\n if (typeof obj.path !== 'string') return obj;\n if (db) {\n return db.doc(obj.path);\n }\n // No db available — leave as tagged object with one-time warning\n if (!_docRefWarned) {\n _docRefWarned = true;\n console.warn(\n '[firegraph] DocumentReference encountered during migration deserialization ' +\n 'but no Firestore instance available. The reference will remain as a tagged ' +\n 'object with its path. Enable write-back for full reconstruction.',\n );\n }\n return obj;\n\n default:\n // Unknown tag — leave as-is (forward compatibility)\n return obj;\n }\n }\n\n // Plain object — recurse\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(obj)) {\n result[key] = deserializeValue(obj[key], db);\n }\n return result;\n}\n"],"mappings":"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","names":[]}
|