@typicalday/firegraph 0.14.1 → 0.16.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.
Files changed (72) hide show
  1. package/README.md +62 -20
  2. package/dist/backend-CE3pM9-T.d.ts +167 -0
  3. package/dist/{backend-DuvHGgK1.d.cts → backend-DNzv8KSR.d.cts} +34 -20
  4. package/dist/{backend-DuvHGgK1.d.ts → backend-DNzv8KSR.d.ts} +34 -20
  5. package/dist/backend-EjFfw9yO.d.cts +167 -0
  6. package/dist/backend.cjs.map +1 -1
  7. package/dist/backend.d.cts +2 -2
  8. package/dist/backend.d.ts +2 -2
  9. package/dist/backend.js +1 -1
  10. package/dist/chunk-5JBNLH5W.js +732 -0
  11. package/dist/chunk-5JBNLH5W.js.map +1 -0
  12. package/dist/{chunk-3AHHXMWX.js → chunk-6IO74NKD.js} +23 -44
  13. package/dist/chunk-6IO74NKD.js.map +1 -0
  14. package/dist/{chunk-DJI3VXXA.js → chunk-7IEZ6IYY.js} +2 -2
  15. package/dist/chunk-7IEZ6IYY.js.map +1 -0
  16. package/dist/chunk-NGAJCALM.js +34 -0
  17. package/dist/chunk-NGAJCALM.js.map +1 -0
  18. package/dist/chunk-NZVSLWNY.js +867 -0
  19. package/dist/chunk-NZVSLWNY.js.map +1 -0
  20. package/dist/{chunk-N5HFDWQX.js → chunk-PWIO46RT.js} +1 -1
  21. package/dist/{chunk-N5HFDWQX.js.map → chunk-PWIO46RT.js.map} +1 -1
  22. package/dist/{client-BKi3vk0Q.d.ts → client-CNAwJayO.d.ts} +1 -1
  23. package/dist/{client-BrsaXtDV.d.cts → client-CaXH5D5C.d.cts} +1 -1
  24. package/dist/{client-Bk2Cm6xv.d.cts → client-DoyEdJ5w.d.cts} +1 -1
  25. package/dist/{client-Bk2Cm6xv.d.ts → client-DoyEdJ5w.d.ts} +1 -1
  26. package/dist/cloudflare/index.cjs +159 -167
  27. package/dist/cloudflare/index.cjs.map +1 -1
  28. package/dist/cloudflare/index.d.cts +73 -70
  29. package/dist/cloudflare/index.d.ts +73 -70
  30. package/dist/cloudflare/index.js +54 -589
  31. package/dist/cloudflare/index.js.map +1 -1
  32. package/dist/codegen/index.d.cts +1 -1
  33. package/dist/codegen/index.d.ts +1 -1
  34. package/dist/firestore-enterprise/index.cjs +11 -9
  35. package/dist/firestore-enterprise/index.cjs.map +1 -1
  36. package/dist/firestore-enterprise/index.d.cts +3 -3
  37. package/dist/firestore-enterprise/index.d.ts +3 -3
  38. package/dist/firestore-enterprise/index.js +6 -4
  39. package/dist/firestore-enterprise/index.js.map +1 -1
  40. package/dist/firestore-standard/index.cjs +11 -9
  41. package/dist/firestore-standard/index.cjs.map +1 -1
  42. package/dist/firestore-standard/index.d.cts +3 -3
  43. package/dist/firestore-standard/index.d.ts +3 -3
  44. package/dist/firestore-standard/index.js +4 -3
  45. package/dist/firestore-standard/index.js.map +1 -1
  46. package/dist/index.cjs +11 -9
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.d.cts +5 -5
  49. package/dist/index.d.ts +5 -5
  50. package/dist/index.js +6 -4
  51. package/dist/index.js.map +1 -1
  52. package/dist/query-client/index.d.cts +2 -2
  53. package/dist/query-client/index.d.ts +2 -2
  54. package/dist/{registry-C2KUPVZj.d.ts → registry-By1i-zge.d.ts} +2 -2
  55. package/dist/{registry-Bc7h6WTM.d.cts → registry-CNToyEra.d.cts} +2 -2
  56. package/dist/sqlite/index.cjs +599 -380
  57. package/dist/sqlite/index.cjs.map +1 -1
  58. package/dist/sqlite/index.d.cts +4 -110
  59. package/dist/sqlite/index.d.ts +4 -110
  60. package/dist/sqlite/index.js +7 -1144
  61. package/dist/sqlite/index.js.map +1 -1
  62. package/dist/sqlite/local.cjs +2262 -0
  63. package/dist/sqlite/local.cjs.map +1 -0
  64. package/dist/sqlite/local.d.cts +109 -0
  65. package/dist/sqlite/local.d.ts +109 -0
  66. package/dist/sqlite/local.js +546 -0
  67. package/dist/sqlite/local.js.map +1 -0
  68. package/package.json +15 -1
  69. package/dist/chunk-3AHHXMWX.js.map +0 -1
  70. package/dist/chunk-DJI3VXXA.js.map +0 -1
  71. package/dist/chunk-NNBSUOOF.js +0 -289
  72. package/dist/chunk-NNBSUOOF.js.map +0 -1
package/README.md CHANGED
@@ -129,21 +129,21 @@ if (client.capabilities.has('query.join')) {
129
129
 
130
130
  **Capability values:**
131
131
 
132
- | Capability | Methods unlocked | Backends |
133
- | ----------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
134
- | `core.read` / `core.write` / `core.batch` / `core.subgraph` | `getNode`, `putNode`, `findEdges`, `batch()`, `subgraph()`, etc. | All |
135
- | `core.transactions` | `runTransaction(fn)` | Firestore (both), SQLite (`better-sqlite3` only; absent on D1); **absent on Cloudflare DO** |
136
- | `query.aggregate` | `aggregate(spec)` | All; `min`/`max` only on SQLite + DO (both Firestore editions reject `min`/`max` — classic `Query.aggregate` exposes only count/sum/avg) |
137
- | `query.select` | `findEdgesProjected(params)` | All |
138
- | `query.join` | `expand(params)` | All |
139
- | `query.dml` | `bulkDelete(params)`, `bulkUpdate(params)` | Enterprise (requires `previewDml: true`), SQLite, DO |
140
- | `traversal.serverSide` | `runEngineTraversal(params)` | Enterprise |
141
- | `search.vector` | `findNearest(params)` | Firestore (both) |
142
- | `search.fullText` | `fullTextSearch(params)` | Enterprise. **Note:** the `fields` option is not yet supported — passing a non-empty `fields` array throws `INVALID_QUERY`. |
143
- | `search.geo` | `geoSearch(params)` | Enterprise |
144
- | `raw.firestore` | _(reserved — no methods yet)_ | Firestore (both) |
145
- | `raw.sql` | _(reserved — no methods yet)_ | SQLite |
146
- | `realtime.listen` | _(reserved — no methods yet)_ | _(none currently)_ |
132
+ | Capability | Methods unlocked | Backends |
133
+ | ----------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
134
+ | `core.read` / `core.write` / `core.batch` / `core.subgraph` | `getNode`, `putNode`, `findEdges`, `batch()`, `subgraph()`, etc. | All |
135
+ | `core.transactions` | `runTransaction(fn)` | Firestore (both), SQLite (`better-sqlite3` only; absent on D1); **absent on Cloudflare DO** |
136
+ | `query.aggregate` | `aggregate(spec)` | All; `min`/`max` only on SQLite + DO (both Firestore editions reject `min`/`max` — classic `Query.aggregate` exposes only count/sum/avg) |
137
+ | `query.select` | `findEdgesProjected(params)` | All |
138
+ | `query.join` | `expand(params)` | All |
139
+ | `query.dml` | `bulkDelete(params)`, `bulkUpdate(params)` | Enterprise (requires `previewDml: true`), SQLite, DO |
140
+ | `traversal.serverSide` | `runEngineTraversal(params)` | Enterprise |
141
+ | `search.vector` | `findNearest(params)` | Firestore (both), local SQLite (`firegraph/sqlite-local` — exact brute-force scan via a SQL distance function) |
142
+ | `search.fullText` | `fullTextSearch(params)` | Enterprise, local SQLite (`firegraph/sqlite-local` — FTS5, bm25-ranked). **Note:** the `fields` option is not yet supported — passing a non-empty `fields` array throws `INVALID_QUERY`. |
143
+ | `search.geo` | `geoSearch(params)` | Enterprise |
144
+ | `raw.firestore` | _(reserved — no methods yet)_ | Firestore (both) |
145
+ | `raw.sql` | _(reserved — no methods yet)_ | SQLite |
146
+ | `realtime.listen` | _(reserved — no methods yet)_ | _(none currently)_ |
147
147
 
148
148
  ### Extension Methods
149
149
 
@@ -200,7 +200,7 @@ const tree = await (client as TraversalExtension).runEngineTraversal({
200
200
  hops: [{ axbType: 'hasDeparture', bType: 'departure', limitPerSource: 10 }],
201
201
  });
202
202
 
203
- // search.vector — approximate nearest-neighbour (Firestore both editions)
203
+ // search.vector — nearest-neighbour (Firestore both editions; local SQLite via firegraph/sqlite-local)
204
204
  const similar = await (client as VectorExtension).findNearest({
205
205
  aType: 'tour',
206
206
  axbType: 'is',
@@ -210,7 +210,7 @@ const similar = await (client as VectorExtension).findNearest({
210
210
  limit: 5,
211
211
  });
212
212
 
213
- // search.fullText — full-text search (Enterprise; `fields` throws INVALID_QUERY if non-empty)
213
+ // search.fullText — full-text search (Enterprise + firegraph/sqlite-local; `fields` throws INVALID_QUERY if non-empty)
214
214
  const results = await (client as FullTextExtension).fullTextSearch({
215
215
  aType: 'tour',
216
216
  axbType: 'is',
@@ -833,6 +833,8 @@ const allAssignments = await g.findEdgesGlobal(
833
833
 
834
834
  This uses Firestore collection group queries and requires collection group indexes. The collection name defaults to the last segment of the client's collection path if omitted.
835
835
 
836
+ Firestore-only: the SQLite backend uses a table-per-graph layout with no cross-table index, so it omits `findEdgesGlobal` entirely (calling it throws `UNSUPPORTED_OPERATION`).
837
+
836
838
  #### Multi-Hop Limitation
837
839
 
838
840
  Each hop carries its reader context forward — if hop 1 crosses into a subgraph, hop 2 stays in that subgraph. To return to the root or traverse a different subgraph, create a separate traversal from the desired client:
@@ -1140,18 +1142,58 @@ The match on `FIRESTORE_EMULATOR_EDITION` is case-insensitive and whitespace-tri
1140
1142
 
1141
1143
  ### SQLite Backend (`firegraph/sqlite`)
1142
1144
 
1143
- Shared-table SQLite backend for Node.js (`better-sqlite3`) and Cloudflare D1. Supports all four core capabilities plus `query.aggregate`, `query.select`, `query.join`, and `query.dml`. Does not support `search.*`.
1145
+ Table-per-graph SQLite backend for Node.js (`better-sqlite3`) and Cloudflare D1. There is no `scope` column — the root graph lives in `tableName` itself and each subgraph gets its own physical table, tracked in a small `<tableName>_graphs` catalog. Schema (tables, indexes, catalog) is bootstrapped lazily on first use; no manual DDL step. Supports all four core capabilities plus `query.aggregate`, `query.select`, `query.join`, `query.dml`, and `raw.sql`. Does not support `search.*` or `findEdgesGlobal` (no cross-table index).
1144
1146
 
1145
1147
  ```typescript
1146
1148
  import { createSqliteBackend } from 'firegraph/sqlite';
1147
- import { createGraphClientFromBackend } from 'firegraph';
1149
+ import { createGraphClient } from 'firegraph';
1148
1150
 
1149
1151
  const backend = createSqliteBackend(executor, 'graph');
1150
- const g = createGraphClientFromBackend(backend, { registry });
1152
+ const g = createGraphClient(backend, { registry });
1151
1153
  ```
1152
1154
 
1153
1155
  Note: `core.transactions` is only declared when `executor.transaction` is defined — `better-sqlite3` provides this, but Cloudflare D1 does not.
1154
1156
 
1157
+ ### Local SQLite Files (`firegraph/sqlite-local`)
1158
+
1159
+ For a local graph database in a single SQLite file, use the better-sqlite3 factory. It opens (or wraps) a database, applies `journal_mode = WAL` and a `busy_timeout`, and returns a ready-to-use backend. Requires the optional peer dependency `better-sqlite3` (loaded via dynamic `import()`, so this subpath is safe to reference from code that also targets D1/workerd — just don't bundle it there).
1160
+
1161
+ ```typescript
1162
+ import { createLocalSqliteBackend } from 'firegraph/sqlite-local';
1163
+ import { createGraphClient } from 'firegraph';
1164
+
1165
+ const { backend, db, close } = await createLocalSqliteBackend('./graph.db', {
1166
+ tableName: 'graph', // default 'firegraph'
1167
+ });
1168
+ const g = createGraphClient(backend, { registry });
1169
+ // ... use the client; `db` is the raw better-sqlite3 Database if needed ...
1170
+ close();
1171
+ ```
1172
+
1173
+ `createLocalSqliteBackend` also accepts an already-open better-sqlite3 `Database` (in which case `close()` is a no-op — the caller owns the lifecycle), `':memory:'` for ephemeral graphs, a `pragmas` map for extra tuning, and `fileMustExist: true` to refuse creating new files. `createBetterSqliteExecutor(db)` is exported separately for wiring `createSqliteBackend` directly.
1174
+
1175
+ On top of the shared SQLite capability set, the local factory declares `search.fullText` and `search.vector`:
1176
+
1177
+ - **`fullTextSearch(params)`** — every graph table gets a contentless FTS5 index kept in sync by pure-SQL triggers (`json_tree` extracts all string leaves from `data`, so nested fields are searchable). Results are ranked by bm25. FTS5 query syntax (`AND` / `OR` / `NOT`, `"phrase"` quoting, `prefix*`) passes through; malformed queries throw `INVALID_QUERY`. The `fields` option is not supported (the index is one combined text column) — a non-empty `fields` array throws `INVALID_QUERY`, matching Firestore Enterprise. Because the triggers are plain SQL, writes from _any_ connection or process stay indexed, and rows written before the index existed are backfilled on bootstrap.
1178
+ - **`findNearest(params)`** — exact (not approximate) nearest-neighbour via a brute-force scan scored by a connection-local SQL distance function. Supports `EUCLIDEAN`, `COSINE`, and `DOT_PRODUCT`, plus `distanceThreshold` and `distanceResultField`, mirroring Firestore semantics (rows with a missing field, wrong dimension, or non-finite values are silently skipped). Vector queries must run through the factory-created backend — the distance function is registered per connection.
1179
+
1180
+ ```typescript
1181
+ const hits = await g.fullTextSearch({
1182
+ aType: 'tour',
1183
+ axbType: 'is',
1184
+ bType: 'tour',
1185
+ query: 'dolomites OR alps',
1186
+ });
1187
+ const similar = await g.findNearest({
1188
+ aType: 'tour',
1189
+ axbType: 'is',
1190
+ bType: 'tour',
1191
+ queryVector: [0.1, 0.2, 0.3],
1192
+ vectorField: 'embedding',
1193
+ limit: 5,
1194
+ });
1195
+ ```
1196
+
1155
1197
  ### Cloudflare Durable Object Backend (`firegraph/cloudflare`)
1156
1198
 
1157
1199
  Runs inside a Durable Object via `state.storage.sql`. Same capability set as SQLite minus `core.transactions` (the DO's single-threaded executor cannot block on transaction callbacks) and `raw.sql` (the DO SQL surface is hidden behind RPC).
@@ -0,0 +1,167 @@
1
+ import { p as GraphRegistry, I as IndexSpec, S as StorageBackend } from './backend-DNzv8KSR.js';
2
+
3
+ /**
4
+ * Driver-level SQLite abstraction.
5
+ *
6
+ * The `SqliteBackend` only depends on this interface, not on any particular
7
+ * SQLite driver. Callers wire up whichever driver suits their runtime —
8
+ * `better-sqlite3` in Node tests, D1 in Workers, DO SQLite inside a Durable
9
+ * Object, etc. — and `createSqliteBackend` composes the rest.
10
+ *
11
+ * Some drivers are fully async with native atomic batches (e.g. D1); others
12
+ * are synchronous and wrap `run`/`all` in immediately-resolved promises while
13
+ * providing interactive transactions via a sync primitive (e.g. DO SQLite's
14
+ * `transactionSync`). Both shapes fit behind this interface.
15
+ */
16
+ interface SqliteExecutor {
17
+ /** Run a query and return all rows. */
18
+ all(sql: string, params: unknown[]): Promise<Record<string, unknown>[]>;
19
+ /** Run a write statement. */
20
+ run(sql: string, params: unknown[]): Promise<void>;
21
+ /**
22
+ * Execute a list of write statements atomically. Drivers that lack
23
+ * native batch support (e.g., a wrapped synchronous SQLite) should still
24
+ * implement this so `BatchBackend.commit()` works.
25
+ */
26
+ batch(statements: ReadonlyArray<{
27
+ sql: string;
28
+ params: unknown[];
29
+ }>): Promise<void>;
30
+ /**
31
+ * Run an interactive transaction. Optional — if absent, the SqliteBackend
32
+ * throws on `runTransaction()`. D1 has no interactive transactions.
33
+ */
34
+ transaction?<T>(fn: (tx: SqliteTxExecutor) => Promise<T>): Promise<T>;
35
+ /**
36
+ * Maximum statements the driver will accept in a single `batch()` call.
37
+ * The backend uses this to chunk large bulk operations (cascade delete,
38
+ * bulkRemoveEdges) so a hub node with thousands of edges doesn't trip the
39
+ * driver's hard limit. D1 caps at ~100 statements per batch; DO SQLite has
40
+ * no documented cap (a single `transactionSync` over many statements is
41
+ * fine). When `undefined`, the backend submits all statements in one batch
42
+ * (preserving cross-batch atomicity for drivers that support it).
43
+ */
44
+ readonly maxBatchSize?: number;
45
+ /**
46
+ * Maximum total bound parameters the driver will accept across one
47
+ * `batch()` call. D1 caps at ~1000 bound parameters per batch — separate
48
+ * from `maxBatchSize`. Most cascade/bulk batches consist of 2-param
49
+ * `DELETE` statements so this rarely triggers, but driver authors should
50
+ * declare it for safety. When `undefined`, the backend doesn't split on
51
+ * parameter count.
52
+ */
53
+ readonly maxBatchParams?: number;
54
+ }
55
+ interface SqliteTxExecutor {
56
+ all(sql: string, params: unknown[]): Promise<Record<string, unknown>[]>;
57
+ run(sql: string, params: unknown[]): Promise<void>;
58
+ }
59
+
60
+ /**
61
+ * SQLite implementation of `StorageBackend`.
62
+ *
63
+ * Table-per-graph design: the root graph lives in `tableName`, and each
64
+ * subgraph lives in its own physical table (`<tableName>_g_<mangled scope>`,
65
+ * see `tableForScope`). There is no `scope` column — the table a row lives
66
+ * in *is* its scope, exactly like the Cloudflare DO edition where each
67
+ * subgraph is its own Durable Object.
68
+ *
69
+ * A small catalog table (`<tableName>_graphs`) records every graph's
70
+ * storage scope → table mapping. Cascade delete prefix-matches descendant
71
+ * scopes in the catalog and drops each listed table — no registry topology
72
+ * required.
73
+ *
74
+ * Schema is ensured lazily: the first operation on a backend instance runs
75
+ * `CREATE TABLE IF NOT EXISTS` for the graph's table, its indexes, and the
76
+ * catalog, then registers the graph in the catalog. Callers no longer
77
+ * pre-create tables.
78
+ */
79
+
80
+ interface SqliteBackendOptions {
81
+ /** Logical scope path (chained subgraph names) — used for `allowedIn` matching. */
82
+ scopePath?: string;
83
+ /**
84
+ * Internal storage scope (interleaved parent-uid/name path). Determines
85
+ * which physical table this backend reads and writes — `''` (the default)
86
+ * is the root graph in `tableName` itself.
87
+ *
88
+ * @internal Used by `subgraph()` to derive child backends. Setting it
89
+ * directly bypasses catalog registration consistency checks (the graph
90
+ * still self-registers, but ancestors are not validated) — always derive
91
+ * subgraph backends via `subgraph()` instead.
92
+ */
93
+ storageScope?: string;
94
+ /**
95
+ * Registry contributing per-entry `indexes` declarations, applied to
96
+ * every graph table this backend (and its subgraphs) lazily creates.
97
+ */
98
+ registry?: GraphRegistry;
99
+ /**
100
+ * Replaces the built-in core index preset for lazily created tables.
101
+ * Pass `[]` to disable core indexes entirely.
102
+ */
103
+ coreIndexes?: IndexSpec[];
104
+ /**
105
+ * Extra DDL statements appended to every graph table's lazy bootstrap.
106
+ * Called with the physical table name; the returned statements run after
107
+ * the core table/index DDL inside the same chunked batch. Statements MUST
108
+ * be idempotent (`IF NOT EXISTS` / `INSERT OR IGNORE` / re-runnable DML)
109
+ * — the bootstrap re-runs after self-heal and once per backend instance,
110
+ * including every lazily created subgraph table.
111
+ *
112
+ * @internal Used by `firegraph/sqlite-local` to install FTS5 index
113
+ * tables, sync triggers, and backfill statements. Propagated to subgraph
114
+ * backends derived via `subgraph()`.
115
+ */
116
+ extraTableDDL?: (tableName: string) => string[];
117
+ }
118
+ /**
119
+ * The shape `createSqliteBackend` actually returns: `StorageBackend` plus
120
+ * internal hooks the `firegraph/sqlite-local` search wrapper needs.
121
+ */
122
+ interface SqliteStorageBackend extends StorageBackend<SqliteCapability> {
123
+ /**
124
+ * Force the lazy schema bootstrap (table + indexes + catalog + any
125
+ * `extraTableDDL` artifacts). Pass `force: true` to reset the bootstrap
126
+ * cache first — mirrors the self-heal path in `withSchema` for callers
127
+ * that issue their own SQL against this graph's table and hit a
128
+ * "no such table" error after a parent cascade dropped it.
129
+ *
130
+ * @internal Used by the `firegraph/sqlite-local` search wrapper.
131
+ */
132
+ ensureReady(force?: boolean): Promise<void>;
133
+ subgraph(parentNodeUid: string, name: string): SqliteStorageBackend;
134
+ }
135
+ /**
136
+ * Capability union declared by the SQLite-backed `StorageBackend`.
137
+ *
138
+ * `core.transactions` is part of the static union because `runTransaction`
139
+ * is always present as a method on the class. The runtime cap-set determines
140
+ * whether that method is *functional*: D1 leaves `executor.transaction`
141
+ * undefined and the call throws `UNSUPPORTED_OPERATION`; DO SQLite and
142
+ * better-sqlite3 wire the executor and the call works. The static type
143
+ * therefore promises only that the method exists — callers that care about
144
+ * portability check `client.capabilities.has('core.transactions')` before
145
+ * opening a tx, and code that runs against an unknown driver can rely on the
146
+ * runtime guard inside `runTransaction`.
147
+ *
148
+ * The `query.*` extension capabilities follow the same conservative
149
+ * declaration rule as the cap descriptor itself — only land in the union
150
+ * when the corresponding method is actually wired up. Today that's
151
+ * `query.aggregate` (Phase 4), `query.dml` (Phase 5), `query.join`
152
+ * (Phase 6 — fan-out via `IN (…)` in one statement), and `query.select`
153
+ * (Phase 7 — server-side projection via `json_extract`).
154
+ */
155
+ type SqliteCapability = 'core.read' | 'core.write' | 'core.transactions' | 'core.batch' | 'core.subgraph' | 'query.aggregate' | 'query.dml' | 'query.join' | 'query.select' | 'raw.sql';
156
+ /**
157
+ * Create a SQLite-backed `StorageBackend`.
158
+ *
159
+ * `tableName` is the root graph's table; subgraphs get their own tables
160
+ * derived from it (see `tableForScope`). Schema (tables, indexes, and the
161
+ * graph catalog) is created lazily on first use — no manual DDL step.
162
+ * Pass `options.registry` so per-entry `indexes` declarations land in
163
+ * every lazily created table.
164
+ */
165
+ declare function createSqliteBackend(executor: SqliteExecutor, tableName: string, options?: SqliteBackendOptions): SqliteStorageBackend;
166
+
167
+ export { type SqliteBackendOptions as S, type SqliteCapability as a, type SqliteExecutor as b, createSqliteBackend as c };
@@ -1095,9 +1095,14 @@ interface FullTextSearchParams {
1095
1095
  * Enterprise product feature, not a free-tier feature).
1096
1096
  * - **Firestore Standard** — not supported. FTS is an Enterprise-only
1097
1097
  * product feature; this row will never become "✓".
1098
- * - **SQLite / Cloudflare DO** not supported. No native FTS index;
1099
- * emulating it over `json_extract` is not viable for any realistic
1100
- * dataset.
1098
+ * - **Local SQLite (`firegraph/sqlite-local`)** backed by one FTS5
1099
+ * table per graph table, kept in sync by pure-SQL triggers and ranked
1100
+ * by `bm25()`. The whole `data` payload is indexed as one combined
1101
+ * text column, so a non-empty `fields` list is rejected with
1102
+ * `INVALID_QUERY`.
1103
+ * - **Shared SQLite (D1) / Cloudflare DO** — not supported. No FTS5
1104
+ * trigger infrastructure on those runtimes; emulating FTS over
1105
+ * `json_extract` is not viable for any realistic dataset.
1101
1106
  *
1102
1107
  * Migrations are NOT applied to the result. The search index walked
1103
1108
  * the raw stored shape; rehydrating each row through the migration
@@ -1311,12 +1316,15 @@ interface FindNearestParams {
1311
1316
  * Native vector / nearest-neighbour search.
1312
1317
  *
1313
1318
  * Backends declaring `search.vector` translate the call into a single
1314
- * server-side `findNearest` query. The SQLite-shaped backends (shared
1315
- * SQLite, Cloudflare DO) do not declare this capability — they have no
1316
- * native vector index, and emulating ANN on top of `json_extract` is a
1317
- * non-starter for any realistic dataset. Firestore Standard and
1318
- * Enterprise both implement it via the classic `Query.findNearest(...)`
1319
- * API; the pipeline `findNearest` stage is a future optimisation.
1319
+ * server-side `findNearest` query. Firestore Standard and Enterprise
1320
+ * both implement it via the classic `Query.findNearest(...)` API; the
1321
+ * pipeline `findNearest` stage is a future optimisation. The local
1322
+ * better-sqlite3 backend (`firegraph/sqlite-local`) implements it as a
1323
+ * brute-force scan scored by a deterministic SQL UDF — exact rather
1324
+ * than approximate, fine for local files at local-file scale. The D1
1325
+ * and Cloudflare DO editions do not declare the capability: those
1326
+ * runtimes expose no UDF registration surface, and emulating ANN on top
1327
+ * of `json_extract` alone is a non-starter for any realistic dataset.
1320
1328
  *
1321
1329
  * Migrations are NOT applied to the result. The vector query selects
1322
1330
  * documents by similarity, not by query plan — applying migrations
@@ -1827,11 +1835,14 @@ interface StorageBackend<C extends Capability = Capability> {
1827
1835
  findEdgesProjected?(select: ReadonlyArray<string>, filters: QueryFilter[], options?: QueryOptions): Promise<Array<Record<string, unknown>>>;
1828
1836
  /**
1829
1837
  * Run a vector / nearest-neighbour query. Present only on backends that
1830
- * declare `search.vector`. There is no client-side fallback — the
1831
- * SQLite-shaped backends (shared SQLite, Cloudflare DO) genuinely have
1832
- * no native ANN index, and a JS-side k-NN sweep over `findEdges()` would
1833
- * scale catastrophically. Backends without the cap throw
1834
- * `UNSUPPORTED_OPERATION` from the client wrapper.
1838
+ * declare `search.vector`. There is no client-side fallback — backends
1839
+ * without the cap throw `UNSUPPORTED_OPERATION` from the client wrapper.
1840
+ * In-tree: both Firestore editions (native ANN via `Query.findNearest`)
1841
+ * and the local better-sqlite3 backend (`firegraph/sqlite-local`, a
1842
+ * brute-force UDF-scored scan exact, not approximate). The D1 and
1843
+ * Cloudflare DO editions stay without the cap: no UDF registration
1844
+ * surface, and a JS-side k-NN sweep over `findEdges()` would scale
1845
+ * catastrophically.
1835
1846
  *
1836
1847
  * `params` carries the user-facing shape (vector field path, query
1837
1848
  * vector, distance metric, optional threshold and result-field). The
@@ -1859,11 +1870,14 @@ interface StorageBackend<C extends Capability = Capability> {
1859
1870
  findNearest?(params: FindNearestParams): Promise<StoredGraphRecord[]>;
1860
1871
  /**
1861
1872
  * Run a full-text search query. Present only on backends that declare
1862
- * `search.fullText`. There is no client-side fallback — the only
1863
- * in-tree backend that supports it is Firestore Enterprise (via
1864
- * Pipeline `search({ query: documentMatches(...) })`); Standard and
1865
- * the SQLite-shaped backends throw `UNSUPPORTED_OPERATION` from the
1866
- * client wrapper.
1873
+ * `search.fullText`. There is no client-side fallback — backends
1874
+ * without the cap throw `UNSUPPORTED_OPERATION` from the client
1875
+ * wrapper. In-tree: Firestore Enterprise (via Pipeline
1876
+ * `search({ query: documentMatches(...) })`) and the local
1877
+ * better-sqlite3 backend (`firegraph/sqlite-local`, via a
1878
+ * trigger-synced FTS5 index ranked by `bm25()`). Firestore Standard
1879
+ * never gets it (Enterprise-only product feature); D1 and the
1880
+ * Cloudflare DO edition don't ship FTS5 trigger infrastructure.
1867
1881
  *
1868
1882
  * The backend is responsible for path normalisation (rewriting
1869
1883
  * bare `fields` entries to `data.<name>`, rejecting envelope fields
@@ -1894,4 +1908,4 @@ interface StorageBackend<C extends Capability = Capability> {
1894
1908
  geoSearch?(params: GeoSearchParams): Promise<StoredGraphRecord[]>;
1895
1909
  }
1896
1910
 
1897
- export { type DynamicRegistryConfig as $, type AggregateExtension as A, type BackendCapabilities as B, type BulkBatchError as C, DELETE_FIELD as D, type ExpandParams as E, type FindEdgesParams as F, type GraphRegistry as G, type BulkOptions as H, type IndexSpec as I, type JoinExtension as J, type BulkProgress as K, type BulkResult as L, type MigrationWriteBack as M, type Capability as N, type CascadeResult as O, type CoreGraphClient as P, type QueryPlan as Q, type RegistryEntry as R, type StorageBackend as S, type TransactionBackend as T, type UpdatePayload as U, type DefineTypeOptions as V, type WritableRecord as W, type DiscoveredEntity as X, type DistanceMeasure as Y, type DynamicGraphClient as Z, type DynamicGraphMethods as _, type BatchBackend as a, type EdgeTopology as a0, type EdgeTypeData as a1, type FindEdgesProjectedParams as a2, type FindNearestParams as a3, type FiregraphConfig as a4, type FullTextSearchExtension as a5, type GeoExtension as a6, type GraphBatch as a7, type GraphClientOptions as a8, type GraphRecord as a9, type GraphTransaction as aa, type GraphWriter as ab, type HopDefinition as ac, type HopResult as ad, type IndexFieldSpec as ae, type NodeTypeData as af, type ProjectedRow as ag, type QueryMode as ah, type QueryOptions as ai, type RawFirestoreExtension as aj, type RawSqlExtension as ak, type RealtimeListenExtension as al, type ScanProtection as am, type SelectExtension as an, type TraversalOptions as ao, type TraversalResult as ap, type VectorExtension as aq, type ViewContext as ar, type ViewDefaultsConfig as as, type ViewResolverConfig as at, type WhereClause as au, defineConfig as av, resolveView as aw, type BulkUpdatePatch as b, type DataPathOp as c, type DmlExtension as d, type ExpandResult as e, type WriteMode as f, createCapabilities as g, deleteField as h, flattenPatch as i, intersectCapabilities as j, isDeleteSentinel as k, type DiscoveryResult as l, type StoredGraphRecord as m, type MigrationStep as n, type FindNodesParams as o, type QueryFilter as p, type MigrationExecutor as q, type MigrationFn as r, type StoredMigrationStep as s, type GraphClient as t, type GraphReader as u, type TraversalBuilder as v, type AggregateField as w, type AggregateOp as x, type AggregateResult as y, type AggregateSpec as z };
1911
+ export { type DiscoveredEntity as $, type AggregateSpec as A, type BackendCapabilities as B, type Capability as C, DELETE_FIELD as D, type ExpandParams as E, type FindEdgesParams as F, type GraphClientOptions as G, type MigrationWriteBack as H, type IndexSpec as I, type JoinExtension as J, type MigrationStep as K, type QueryPlan as L, type MigrationExecutor as M, type FindNodesParams as N, type MigrationFn as O, type StoredMigrationStep as P, type QueryFilter as Q, type RegistryEntry as R, type StorageBackend as S, type TransactionBackend as T, type UpdatePayload as U, type TraversalBuilder as V, type WritableRecord as W, type BulkBatchError as X, type BulkProgress as Y, type CoreGraphClient as Z, type DefineTypeOptions as _, type BatchBackend as a, type DistanceMeasure as a0, type DynamicGraphMethods as a1, type EdgeTopology as a2, type EdgeTypeData as a3, type FindEdgesProjectedParams as a4, type FindNearestParams as a5, type FiregraphConfig as a6, type FullTextSearchExtension as a7, type GeoExtension as a8, type GraphBatch as a9, type GraphRecord as aa, type GraphTransaction as ab, type GraphWriter as ac, type HopDefinition as ad, type HopResult as ae, type IndexFieldSpec as af, type NodeTypeData as ag, type ProjectedRow as ah, type QueryMode as ai, type RawFirestoreExtension as aj, type RawSqlExtension as ak, type RealtimeListenExtension as al, type ScanProtection as am, type SelectExtension as an, type TraversalOptions as ao, type TraversalResult as ap, type VectorExtension as aq, type ViewContext as ar, type ViewDefaultsConfig as as, type ViewResolverConfig as at, type WhereClause as au, defineConfig as av, resolveView as aw, type BulkUpdatePatch as b, type DataPathOp as c, type DmlExtension as d, type ExpandResult as e, type WriteMode as f, createCapabilities as g, deleteField as h, flattenPatch as i, intersectCapabilities as j, isDeleteSentinel as k, type DiscoveryResult as l, type DynamicRegistryConfig as m, type DynamicGraphClient as n, type GraphClient as o, type GraphRegistry as p, type GraphReader as q, type QueryOptions as r, type CascadeResult as s, type BulkOptions as t, type BulkResult as u, type StoredGraphRecord as v, type AggregateExtension as w, type AggregateField as x, type AggregateOp as y, type AggregateResult as z };
@@ -1095,9 +1095,14 @@ interface FullTextSearchParams {
1095
1095
  * Enterprise product feature, not a free-tier feature).
1096
1096
  * - **Firestore Standard** — not supported. FTS is an Enterprise-only
1097
1097
  * product feature; this row will never become "✓".
1098
- * - **SQLite / Cloudflare DO** not supported. No native FTS index;
1099
- * emulating it over `json_extract` is not viable for any realistic
1100
- * dataset.
1098
+ * - **Local SQLite (`firegraph/sqlite-local`)** backed by one FTS5
1099
+ * table per graph table, kept in sync by pure-SQL triggers and ranked
1100
+ * by `bm25()`. The whole `data` payload is indexed as one combined
1101
+ * text column, so a non-empty `fields` list is rejected with
1102
+ * `INVALID_QUERY`.
1103
+ * - **Shared SQLite (D1) / Cloudflare DO** — not supported. No FTS5
1104
+ * trigger infrastructure on those runtimes; emulating FTS over
1105
+ * `json_extract` is not viable for any realistic dataset.
1101
1106
  *
1102
1107
  * Migrations are NOT applied to the result. The search index walked
1103
1108
  * the raw stored shape; rehydrating each row through the migration
@@ -1311,12 +1316,15 @@ interface FindNearestParams {
1311
1316
  * Native vector / nearest-neighbour search.
1312
1317
  *
1313
1318
  * Backends declaring `search.vector` translate the call into a single
1314
- * server-side `findNearest` query. The SQLite-shaped backends (shared
1315
- * SQLite, Cloudflare DO) do not declare this capability — they have no
1316
- * native vector index, and emulating ANN on top of `json_extract` is a
1317
- * non-starter for any realistic dataset. Firestore Standard and
1318
- * Enterprise both implement it via the classic `Query.findNearest(...)`
1319
- * API; the pipeline `findNearest` stage is a future optimisation.
1319
+ * server-side `findNearest` query. Firestore Standard and Enterprise
1320
+ * both implement it via the classic `Query.findNearest(...)` API; the
1321
+ * pipeline `findNearest` stage is a future optimisation. The local
1322
+ * better-sqlite3 backend (`firegraph/sqlite-local`) implements it as a
1323
+ * brute-force scan scored by a deterministic SQL UDF — exact rather
1324
+ * than approximate, fine for local files at local-file scale. The D1
1325
+ * and Cloudflare DO editions do not declare the capability: those
1326
+ * runtimes expose no UDF registration surface, and emulating ANN on top
1327
+ * of `json_extract` alone is a non-starter for any realistic dataset.
1320
1328
  *
1321
1329
  * Migrations are NOT applied to the result. The vector query selects
1322
1330
  * documents by similarity, not by query plan — applying migrations
@@ -1827,11 +1835,14 @@ interface StorageBackend<C extends Capability = Capability> {
1827
1835
  findEdgesProjected?(select: ReadonlyArray<string>, filters: QueryFilter[], options?: QueryOptions): Promise<Array<Record<string, unknown>>>;
1828
1836
  /**
1829
1837
  * Run a vector / nearest-neighbour query. Present only on backends that
1830
- * declare `search.vector`. There is no client-side fallback — the
1831
- * SQLite-shaped backends (shared SQLite, Cloudflare DO) genuinely have
1832
- * no native ANN index, and a JS-side k-NN sweep over `findEdges()` would
1833
- * scale catastrophically. Backends without the cap throw
1834
- * `UNSUPPORTED_OPERATION` from the client wrapper.
1838
+ * declare `search.vector`. There is no client-side fallback — backends
1839
+ * without the cap throw `UNSUPPORTED_OPERATION` from the client wrapper.
1840
+ * In-tree: both Firestore editions (native ANN via `Query.findNearest`)
1841
+ * and the local better-sqlite3 backend (`firegraph/sqlite-local`, a
1842
+ * brute-force UDF-scored scan exact, not approximate). The D1 and
1843
+ * Cloudflare DO editions stay without the cap: no UDF registration
1844
+ * surface, and a JS-side k-NN sweep over `findEdges()` would scale
1845
+ * catastrophically.
1835
1846
  *
1836
1847
  * `params` carries the user-facing shape (vector field path, query
1837
1848
  * vector, distance metric, optional threshold and result-field). The
@@ -1859,11 +1870,14 @@ interface StorageBackend<C extends Capability = Capability> {
1859
1870
  findNearest?(params: FindNearestParams): Promise<StoredGraphRecord[]>;
1860
1871
  /**
1861
1872
  * Run a full-text search query. Present only on backends that declare
1862
- * `search.fullText`. There is no client-side fallback — the only
1863
- * in-tree backend that supports it is Firestore Enterprise (via
1864
- * Pipeline `search({ query: documentMatches(...) })`); Standard and
1865
- * the SQLite-shaped backends throw `UNSUPPORTED_OPERATION` from the
1866
- * client wrapper.
1873
+ * `search.fullText`. There is no client-side fallback — backends
1874
+ * without the cap throw `UNSUPPORTED_OPERATION` from the client
1875
+ * wrapper. In-tree: Firestore Enterprise (via Pipeline
1876
+ * `search({ query: documentMatches(...) })`) and the local
1877
+ * better-sqlite3 backend (`firegraph/sqlite-local`, via a
1878
+ * trigger-synced FTS5 index ranked by `bm25()`). Firestore Standard
1879
+ * never gets it (Enterprise-only product feature); D1 and the
1880
+ * Cloudflare DO edition don't ship FTS5 trigger infrastructure.
1867
1881
  *
1868
1882
  * The backend is responsible for path normalisation (rewriting
1869
1883
  * bare `fields` entries to `data.<name>`, rejecting envelope fields
@@ -1894,4 +1908,4 @@ interface StorageBackend<C extends Capability = Capability> {
1894
1908
  geoSearch?(params: GeoSearchParams): Promise<StoredGraphRecord[]>;
1895
1909
  }
1896
1910
 
1897
- export { type DynamicRegistryConfig as $, type AggregateExtension as A, type BackendCapabilities as B, type BulkBatchError as C, DELETE_FIELD as D, type ExpandParams as E, type FindEdgesParams as F, type GraphRegistry as G, type BulkOptions as H, type IndexSpec as I, type JoinExtension as J, type BulkProgress as K, type BulkResult as L, type MigrationWriteBack as M, type Capability as N, type CascadeResult as O, type CoreGraphClient as P, type QueryPlan as Q, type RegistryEntry as R, type StorageBackend as S, type TransactionBackend as T, type UpdatePayload as U, type DefineTypeOptions as V, type WritableRecord as W, type DiscoveredEntity as X, type DistanceMeasure as Y, type DynamicGraphClient as Z, type DynamicGraphMethods as _, type BatchBackend as a, type EdgeTopology as a0, type EdgeTypeData as a1, type FindEdgesProjectedParams as a2, type FindNearestParams as a3, type FiregraphConfig as a4, type FullTextSearchExtension as a5, type GeoExtension as a6, type GraphBatch as a7, type GraphClientOptions as a8, type GraphRecord as a9, type GraphTransaction as aa, type GraphWriter as ab, type HopDefinition as ac, type HopResult as ad, type IndexFieldSpec as ae, type NodeTypeData as af, type ProjectedRow as ag, type QueryMode as ah, type QueryOptions as ai, type RawFirestoreExtension as aj, type RawSqlExtension as ak, type RealtimeListenExtension as al, type ScanProtection as am, type SelectExtension as an, type TraversalOptions as ao, type TraversalResult as ap, type VectorExtension as aq, type ViewContext as ar, type ViewDefaultsConfig as as, type ViewResolverConfig as at, type WhereClause as au, defineConfig as av, resolveView as aw, type BulkUpdatePatch as b, type DataPathOp as c, type DmlExtension as d, type ExpandResult as e, type WriteMode as f, createCapabilities as g, deleteField as h, flattenPatch as i, intersectCapabilities as j, isDeleteSentinel as k, type DiscoveryResult as l, type StoredGraphRecord as m, type MigrationStep as n, type FindNodesParams as o, type QueryFilter as p, type MigrationExecutor as q, type MigrationFn as r, type StoredMigrationStep as s, type GraphClient as t, type GraphReader as u, type TraversalBuilder as v, type AggregateField as w, type AggregateOp as x, type AggregateResult as y, type AggregateSpec as z };
1911
+ export { type DiscoveredEntity as $, type AggregateSpec as A, type BackendCapabilities as B, type Capability as C, DELETE_FIELD as D, type ExpandParams as E, type FindEdgesParams as F, type GraphClientOptions as G, type MigrationWriteBack as H, type IndexSpec as I, type JoinExtension as J, type MigrationStep as K, type QueryPlan as L, type MigrationExecutor as M, type FindNodesParams as N, type MigrationFn as O, type StoredMigrationStep as P, type QueryFilter as Q, type RegistryEntry as R, type StorageBackend as S, type TransactionBackend as T, type UpdatePayload as U, type TraversalBuilder as V, type WritableRecord as W, type BulkBatchError as X, type BulkProgress as Y, type CoreGraphClient as Z, type DefineTypeOptions as _, type BatchBackend as a, type DistanceMeasure as a0, type DynamicGraphMethods as a1, type EdgeTopology as a2, type EdgeTypeData as a3, type FindEdgesProjectedParams as a4, type FindNearestParams as a5, type FiregraphConfig as a6, type FullTextSearchExtension as a7, type GeoExtension as a8, type GraphBatch as a9, type GraphRecord as aa, type GraphTransaction as ab, type GraphWriter as ac, type HopDefinition as ad, type HopResult as ae, type IndexFieldSpec as af, type NodeTypeData as ag, type ProjectedRow as ah, type QueryMode as ai, type RawFirestoreExtension as aj, type RawSqlExtension as ak, type RealtimeListenExtension as al, type ScanProtection as am, type SelectExtension as an, type TraversalOptions as ao, type TraversalResult as ap, type VectorExtension as aq, type ViewContext as ar, type ViewDefaultsConfig as as, type ViewResolverConfig as at, type WhereClause as au, defineConfig as av, resolveView as aw, type BulkUpdatePatch as b, type DataPathOp as c, type DmlExtension as d, type ExpandResult as e, type WriteMode as f, createCapabilities as g, deleteField as h, flattenPatch as i, intersectCapabilities as j, isDeleteSentinel as k, type DiscoveryResult as l, type DynamicRegistryConfig as m, type DynamicGraphClient as n, type GraphClient as o, type GraphRegistry as p, type GraphReader as q, type QueryOptions as r, type CascadeResult as s, type BulkOptions as t, type BulkResult as u, type StoredGraphRecord as v, type AggregateExtension as w, type AggregateField as x, type AggregateOp as y, type AggregateResult as z };