@aigne/afs-index 1.12.0-beta.5

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 (186) hide show
  1. package/LICENSE.md +26 -0
  2. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
  4. package/dist/backend/d1-index-backend.cjs +656 -0
  5. package/dist/backend/d1-index-backend.d.cts +149 -0
  6. package/dist/backend/d1-index-backend.d.cts.map +1 -0
  7. package/dist/backend/d1-index-backend.d.mts +149 -0
  8. package/dist/backend/d1-index-backend.d.mts.map +1 -0
  9. package/dist/backend/d1-index-backend.mjs +657 -0
  10. package/dist/backend/d1-index-backend.mjs.map +1 -0
  11. package/dist/backend/d1-vector-backend.cjs +83 -0
  12. package/dist/backend/d1-vector-backend.d.cts +21 -0
  13. package/dist/backend/d1-vector-backend.d.cts.map +1 -0
  14. package/dist/backend/d1-vector-backend.d.mts +21 -0
  15. package/dist/backend/d1-vector-backend.d.mts.map +1 -0
  16. package/dist/backend/d1-vector-backend.mjs +84 -0
  17. package/dist/backend/d1-vector-backend.mjs.map +1 -0
  18. package/dist/backend/memory-backend.cjs +304 -0
  19. package/dist/backend/memory-backend.d.cts +66 -0
  20. package/dist/backend/memory-backend.d.cts.map +1 -0
  21. package/dist/backend/memory-backend.d.mts +66 -0
  22. package/dist/backend/memory-backend.d.mts.map +1 -0
  23. package/dist/backend/memory-backend.mjs +305 -0
  24. package/dist/backend/memory-backend.mjs.map +1 -0
  25. package/dist/backend/prefix-range.cjs +25 -0
  26. package/dist/backend/prefix-range.mjs +25 -0
  27. package/dist/backend/prefix-range.mjs.map +1 -0
  28. package/dist/backend/schema.cjs +187 -0
  29. package/dist/backend/schema.mjs +188 -0
  30. package/dist/backend/schema.mjs.map +1 -0
  31. package/dist/backend/sqlite-backend.cjs +889 -0
  32. package/dist/backend/sqlite-backend.d.cts +96 -0
  33. package/dist/backend/sqlite-backend.d.cts.map +1 -0
  34. package/dist/backend/sqlite-backend.d.mts +96 -0
  35. package/dist/backend/sqlite-backend.d.mts.map +1 -0
  36. package/dist/backend/sqlite-backend.mjs +890 -0
  37. package/dist/backend/sqlite-backend.mjs.map +1 -0
  38. package/dist/backend/types.d.cts +209 -0
  39. package/dist/backend/types.d.cts.map +1 -0
  40. package/dist/backend/types.d.mts +209 -0
  41. package/dist/backend/types.d.mts.map +1 -0
  42. package/dist/backend/upstash-vector-backend.cjs +98 -0
  43. package/dist/backend/upstash-vector-backend.d.cts +21 -0
  44. package/dist/backend/upstash-vector-backend.d.cts.map +1 -0
  45. package/dist/backend/upstash-vector-backend.d.mts +21 -0
  46. package/dist/backend/upstash-vector-backend.d.mts.map +1 -0
  47. package/dist/backend/upstash-vector-backend.mjs +98 -0
  48. package/dist/backend/upstash-vector-backend.mjs.map +1 -0
  49. package/dist/backend/vector-backend.d.cts +29 -0
  50. package/dist/backend/vector-backend.d.cts.map +1 -0
  51. package/dist/backend/vector-backend.d.mts +29 -0
  52. package/dist/backend/vector-backend.d.mts.map +1 -0
  53. package/dist/dirty-queue.d.cts +41 -0
  54. package/dist/dirty-queue.d.cts.map +1 -0
  55. package/dist/dirty-queue.d.mts +41 -0
  56. package/dist/dirty-queue.d.mts.map +1 -0
  57. package/dist/embed.cjs +45 -0
  58. package/dist/embed.d.cts +20 -0
  59. package/dist/embed.d.cts.map +1 -0
  60. package/dist/embed.d.mts +20 -0
  61. package/dist/embed.d.mts.map +1 -0
  62. package/dist/embed.mjs +46 -0
  63. package/dist/embed.mjs.map +1 -0
  64. package/dist/extraction/defaults.cjs +242 -0
  65. package/dist/extraction/defaults.d.cts +14 -0
  66. package/dist/extraction/defaults.d.cts.map +1 -0
  67. package/dist/extraction/defaults.d.mts +14 -0
  68. package/dist/extraction/defaults.d.mts.map +1 -0
  69. package/dist/extraction/defaults.mjs +242 -0
  70. package/dist/extraction/defaults.mjs.map +1 -0
  71. package/dist/extraction/field-map.cjs +162 -0
  72. package/dist/extraction/field-map.d.cts +70 -0
  73. package/dist/extraction/field-map.d.cts.map +1 -0
  74. package/dist/extraction/field-map.d.mts +70 -0
  75. package/dist/extraction/field-map.d.mts.map +1 -0
  76. package/dist/extraction/field-map.mjs +157 -0
  77. package/dist/extraction/field-map.mjs.map +1 -0
  78. package/dist/extraction/manager.cjs +74 -0
  79. package/dist/extraction/manager.d.cts +47 -0
  80. package/dist/extraction/manager.d.cts.map +1 -0
  81. package/dist/extraction/manager.d.mts +47 -0
  82. package/dist/extraction/manager.d.mts.map +1 -0
  83. package/dist/extraction/manager.mjs +74 -0
  84. package/dist/extraction/manager.mjs.map +1 -0
  85. package/dist/extraction/path-anchors.cjs +86 -0
  86. package/dist/extraction/path-anchors.d.cts +72 -0
  87. package/dist/extraction/path-anchors.d.cts.map +1 -0
  88. package/dist/extraction/path-anchors.d.mts +72 -0
  89. package/dist/extraction/path-anchors.d.mts.map +1 -0
  90. package/dist/extraction/path-anchors.mjs +86 -0
  91. package/dist/extraction/path-anchors.mjs.map +1 -0
  92. package/dist/follower-scheduler.cjs +92 -0
  93. package/dist/follower-scheduler.d.cts +58 -0
  94. package/dist/follower-scheduler.d.cts.map +1 -0
  95. package/dist/follower-scheduler.d.mts +58 -0
  96. package/dist/follower-scheduler.d.mts.map +1 -0
  97. package/dist/follower-scheduler.mjs +92 -0
  98. package/dist/follower-scheduler.mjs.map +1 -0
  99. package/dist/follower.cjs +577 -0
  100. package/dist/follower.d.cts +241 -0
  101. package/dist/follower.d.cts.map +1 -0
  102. package/dist/follower.d.mts +241 -0
  103. package/dist/follower.d.mts.map +1 -0
  104. package/dist/follower.mjs +571 -0
  105. package/dist/follower.mjs.map +1 -0
  106. package/dist/index-provider.cjs +1166 -0
  107. package/dist/index-provider.d.cts +175 -0
  108. package/dist/index-provider.d.cts.map +1 -0
  109. package/dist/index-provider.d.mts +175 -0
  110. package/dist/index-provider.d.mts.map +1 -0
  111. package/dist/index-provider.mjs +1167 -0
  112. package/dist/index-provider.mjs.map +1 -0
  113. package/dist/index.cjs +53 -0
  114. package/dist/index.d.cts +22 -0
  115. package/dist/index.d.mts +22 -0
  116. package/dist/index.mjs +20 -0
  117. package/dist/keys/exact.cjs +20 -0
  118. package/dist/keys/exact.mjs +20 -0
  119. package/dist/keys/exact.mjs.map +1 -0
  120. package/dist/keys/location.cjs +13 -0
  121. package/dist/keys/location.mjs +13 -0
  122. package/dist/keys/location.mjs.map +1 -0
  123. package/dist/keys/normalized.cjs +62 -0
  124. package/dist/keys/normalized.mjs +63 -0
  125. package/dist/keys/normalized.mjs.map +1 -0
  126. package/dist/keys/person.cjs +15 -0
  127. package/dist/keys/person.mjs +15 -0
  128. package/dist/keys/person.mjs.map +1 -0
  129. package/dist/keys/registry.cjs +45 -0
  130. package/dist/keys/registry.d.cts +15 -0
  131. package/dist/keys/registry.d.cts.map +1 -0
  132. package/dist/keys/registry.d.mts +15 -0
  133. package/dist/keys/registry.d.mts.map +1 -0
  134. package/dist/keys/registry.mjs +46 -0
  135. package/dist/keys/registry.mjs.map +1 -0
  136. package/dist/keys/time.cjs +128 -0
  137. package/dist/keys/time.mjs +128 -0
  138. package/dist/keys/time.mjs.map +1 -0
  139. package/dist/keys/topic.cjs +23 -0
  140. package/dist/keys/topic.mjs +23 -0
  141. package/dist/keys/topic.mjs.map +1 -0
  142. package/dist/manifest-index.cjs +29 -0
  143. package/dist/manifest-index.d.cts +24 -0
  144. package/dist/manifest-index.d.cts.map +1 -0
  145. package/dist/manifest-index.d.mts +24 -0
  146. package/dist/manifest-index.d.mts.map +1 -0
  147. package/dist/manifest-index.mjs +28 -0
  148. package/dist/manifest-index.mjs.map +1 -0
  149. package/dist/offline-rebuild.cjs +48 -0
  150. package/dist/offline-rebuild.d.cts +57 -0
  151. package/dist/offline-rebuild.d.cts.map +1 -0
  152. package/dist/offline-rebuild.d.mts +57 -0
  153. package/dist/offline-rebuild.d.mts.map +1 -0
  154. package/dist/offline-rebuild.mjs +49 -0
  155. package/dist/offline-rebuild.mjs.map +1 -0
  156. package/dist/query/engine.cjs +433 -0
  157. package/dist/query/engine.d.cts +34 -0
  158. package/dist/query/engine.d.cts.map +1 -0
  159. package/dist/query/engine.d.mts +34 -0
  160. package/dist/query/engine.d.mts.map +1 -0
  161. package/dist/query/engine.mjs +433 -0
  162. package/dist/query/engine.mjs.map +1 -0
  163. package/dist/query/limits.cjs +12 -0
  164. package/dist/query/limits.mjs +12 -0
  165. package/dist/query/limits.mjs.map +1 -0
  166. package/dist/text/cjk.cjs +30 -0
  167. package/dist/text/cjk.d.cts +24 -0
  168. package/dist/text/cjk.d.cts.map +1 -0
  169. package/dist/text/cjk.d.mts +24 -0
  170. package/dist/text/cjk.d.mts.map +1 -0
  171. package/dist/text/cjk.mjs +30 -0
  172. package/dist/text/cjk.mjs.map +1 -0
  173. package/dist/text/fts.cjs +244 -0
  174. package/dist/text/fts.mjs +245 -0
  175. package/dist/text/fts.mjs.map +1 -0
  176. package/dist/text/host.cjs +39 -0
  177. package/dist/text/host.mjs +38 -0
  178. package/dist/text/host.mjs.map +1 -0
  179. package/dist/types.cjs +44 -0
  180. package/dist/types.d.cts +393 -0
  181. package/dist/types.d.cts.map +1 -0
  182. package/dist/types.d.mts +393 -0
  183. package/dist/types.d.mts.map +1 -0
  184. package/dist/types.mjs +42 -0
  185. package/dist/types.mjs.map +1 -0
  186. package/package.json +59 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1-index-backend.mjs","names":[],"sources":["../../src/backend/d1-index-backend.ts"],"sourcesContent":["/**\n * D1IndexBackend — IndexBackend implementation for Cloudflare D1.\n *\n * Runs in the DID Space D1 (the same edge SQLite as the tree index), so it is\n * reachable from every path that builds a session view — afs-rpc, MCP, and the\n * WebSocket/DO — because all three funnel through `buildCfSessionView`.\n *\n * Isolation is by SCOPE (a `scope` column on every table, mirroring how the DID\n * Space tree index partitions its D1 rows). One backend instance is bound to one\n * scope: per-user index → `deriveScope(callerDid)`, per-instance index →\n * `deriveScope(installerDid)`. Different scopes never see each other's rows.\n *\n * Notes vs the (removed) DO-SQLite backend:\n * - db is built via `initDatabase({ d1 })` (drizzle-orm/d1), not ctx.storage.sql.\n * - D1 returns BLOB columns as `number[]`; `decodeStoredEmbedding` handles that\n * alongside ArrayBuffer/Uint8Array.\n * - No explicit `BEGIN TRANSACTION`: D1 auto-commits each statement and the\n * index is a derived, rebuildable read model (a partial batch self-heals on\n * the next reconcile).\n */\nimport { initDatabase, type SqliteDatabase } from \"@aigne/afs-sqlite\";\nimport { sql } from \"drizzle-orm\";\nimport { KeyGeneratorRegistry } from \"../keys/registry.js\";\nimport { segmentCJK } from \"../text/cjk.js\";\nimport type {\n Anchor,\n AnchorData,\n AnchorQuery,\n AnchorResult,\n CombinedResult,\n EntryFilter,\n FTSResult,\n IndexedEntry,\n QueryOpts,\n StaleFilter,\n VectorResult,\n} from \"../types.js\";\nimport { D1VectorBackend } from \"./d1-vector-backend.js\";\nimport { entryPathPrefixRange } from \"./prefix-range.js\";\nimport type {\n CursorRow,\n CursorScopeRegistration,\n IndexBackend,\n IndexedDocumentWrite,\n RemoveIndexOptions,\n} from \"./types.js\";\nimport type { VectorBackend } from \"./vector-backend.js\";\n\nconst DEFAULT_FTS_TOKENIZER = \"unicode61 remove_diacritics 2\";\n\n/** D1 handles whose scope-partitioned schema has been ensured this isolate. */\nconst schemaEnsured = new WeakSet<object>();\nconst cursorServedAtSupported = new WeakMap<object, boolean>();\n\ntype D1BindValue = string | number | null | ArrayBuffer | Uint8Array;\ntype D1PreparedStatementLike = object;\n\ninterface D1StatementBuilderLike {\n bind(...values: D1BindValue[]): D1PreparedStatementLike;\n}\n\ninterface D1BatchDatabaseLike {\n prepare(query: string): D1StatementBuilderLike;\n batch(statements: D1PreparedStatementLike[]): Promise<unknown>;\n}\n\nexport class D1IndexBackend implements IndexBackend {\n private db!: SqliteDatabase;\n private readonly d1: unknown;\n private readonly scope: string;\n private keyRegistry: KeyGeneratorRegistry;\n private readonly ftsTokenizer: string;\n private readonly _injectedVectorBackend?: VectorBackend;\n private _vectorBackend!: VectorBackend;\n\n constructor(\n d1: unknown,\n scope: string,\n keyRegistry?: KeyGeneratorRegistry,\n opts: { ftsTokenizer?: string; vectorBackend?: VectorBackend } = {},\n ) {\n this.d1 = d1;\n this.scope = scope;\n this.keyRegistry = keyRegistry ?? new KeyGeneratorRegistry();\n this.ftsTokenizer = opts.ftsTokenizer ?? DEFAULT_FTS_TOKENIZER;\n this._injectedVectorBackend = opts.vectorBackend;\n }\n\n /** See `IndexBackend.setKeyRegistry` — wires an injected backend to the provider's shared registry. */\n setKeyRegistry(registry: KeyGeneratorRegistry): void {\n this.keyRegistry = registry;\n }\n\n async initialize(): Promise<void> {\n this.db = await initDatabase({ d1: this.d1 });\n // Schema is scope-independent (scope is a column), so ensure it once per D1\n // handle per isolate — the tables persist in D1 across requests/isolates.\n if (!this.d1 || typeof this.d1 !== \"object\" || !schemaEnsured.has(this.d1 as object)) {\n await this.ensureSchema();\n if (this.d1 && typeof this.d1 === \"object\") schemaEnsured.add(this.d1 as object);\n }\n this._vectorBackend = this._injectedVectorBackend ?? new D1VectorBackend(this.db, this.scope);\n }\n\n async close(): Promise<void> {\n this.db = undefined!;\n }\n\n private d1BatchBinding(): D1BatchDatabaseLike | null {\n if (!this.d1 || typeof this.d1 !== \"object\") return null;\n const candidate = this.d1 as Partial<D1BatchDatabaseLike>;\n return typeof candidate.prepare === \"function\" && typeof candidate.batch === \"function\"\n ? (candidate as D1BatchDatabaseLike)\n : null;\n }\n\n /**\n * Rename the anchor tables `arc_anchor_*` → `idx_anchor_*` in place, preserving\n * rows. Probe-guarded (renames only when the legacy table exists and the target\n * does not) so it is idempotent and safe on fresh / already-migrated / racing\n * isolates. Concurrent isolates may both attempt the ALTER on the same D1 —\n * the loser's `no such table` / `already exists` is swallowed.\n *\n * ensureSchema runs on the cold-start (first-request-per-isolate) path, so the\n * existence check is a SINGLE `sqlite_master` read for all four names rather\n * than one per pair — one O(1) probe, no per-request cost (steady state is\n * gated out by the `schemaEnsured` WeakSet).\n */\n private async renameAnchorTables(): Promise<void> {\n const pairs: Array<[legacy: string, next: string]> = [\n [\"arc_anchor_data\", \"idx_anchor_data\"],\n [\"arc_anchor_key\", \"idx_anchor_key\"],\n ];\n const names = pairs.flat().map((n) => `'${n}'`);\n const rows = await this.db.all<{ name: string }>(\n sql.raw(\n `SELECT name FROM sqlite_master WHERE type='table' AND name IN (${names.join(\", \")})`,\n ),\n );\n const present = new Set(rows.map((r) => r.name));\n for (const [legacy, next] of pairs) {\n if (!present.has(legacy)) continue; // fresh db or already renamed\n try {\n if (present.has(next)) {\n await this.db.run(sql.raw(`DROP TABLE IF EXISTS ${legacy}`)); // interrupted — idx_ wins\n } else {\n await this.db.run(sql.raw(`ALTER TABLE ${legacy} RENAME TO ${next}`));\n }\n } catch {\n // Concurrent isolate already renamed/dropped it — the target table is\n // the source of truth from here on; nothing to recover.\n }\n }\n }\n\n private async ensureSchema(): Promise<void> {\n const tokenizer = this.ftsTokenizer.replace(/'/g, \"''\");\n // One-time rename of the anchor tables from the mis-applied `arc_` prefix to\n // the correct `idx_` module prefix (they are index-provider tables, like\n // idx_fts / idx_embedding_vector). Data-preserving ALTER RENAME, run BEFORE\n // the CREATE TABLE IF NOT EXISTS below so an existing arc_ table is renamed\n // rather than shadowed by an empty idx_ one. A numbered .sql migration can't\n // do this safely: these tables are created lazily here (not by a numbered\n // migration), so a bare `ALTER … RENAME` under raw wrangler would abort the\n // whole deploy on any D1 where the index backend was never used.\n await this.renameAnchorTables();\n await this.db.run(\n sql.raw(`CREATE TABLE IF NOT EXISTS ds_indexed_entry (\n scope TEXT NOT NULL,\n entry_path TEXT NOT NULL,\n domain TEXT,\n state TEXT,\n indexed_at TEXT,\n updated_at TEXT,\n anchor_count INTEGER DEFAULT 0,\n source_path TEXT,\n source_hash TEXT,\n extractor_id TEXT,\n meta TEXT,\n PRIMARY KEY (scope, entry_path)\n )`),\n );\n await this.db.run(\n sql.raw(`CREATE TABLE IF NOT EXISTS idx_anchor_data (\n scope TEXT NOT NULL,\n entry_path TEXT NOT NULL,\n anchor_type TEXT NOT NULL,\n confidence REAL,\n raw_anchor TEXT\n )`),\n );\n await this.db.run(\n sql.raw(`CREATE TABLE IF NOT EXISTS idx_anchor_key (\n scope TEXT NOT NULL,\n index_key TEXT NOT NULL,\n entry_path TEXT NOT NULL,\n anchor_type TEXT NOT NULL,\n confidence REAL,\n PRIMARY KEY (scope, index_key, entry_path, anchor_type)\n )`),\n );\n await this.db.run(\n sql.raw(`CREATE INDEX IF NOT EXISTS idx_ad_scope_path ON idx_anchor_data(scope, entry_path)`),\n );\n // issue #1120: (scope, anchor_type) index for markStale's anchor_type\n // filter. Without ANALYZE stats the planner still prefers the covering\n // idx_ad_scope_path, so markStale hints this one explicitly (INDEXED BY).\n await this.db.run(\n sql.raw(\n `CREATE INDEX IF NOT EXISTS idx_ad_scope_anchor_type ON idx_anchor_data(scope, anchor_type)`,\n ),\n );\n await this.db.run(\n sql.raw(`CREATE INDEX IF NOT EXISTS idx_ak_scope_path ON idx_anchor_key(scope, entry_path)`),\n );\n // No idx_ak_lookup(scope, index_key): idx_anchor_key is WITHOUT ROWID with\n // PK (scope, index_key, entry_path, anchor_type). A secondary index appends\n // the missing PK columns, so idx_ak_lookup would be a byte-for-byte copy of\n // the PK — it only doubles anchor_key write cost, and the planner ignores it\n // anyway (measured). queryAnchors seeks the PK directly by rewriting\n // `index_key IN (...)` into a UNION of equalities (issue #1120).\n await this.db.run(\n sql.raw(`CREATE TABLE IF NOT EXISTS idx_embedding_vector (\n scope TEXT NOT NULL,\n entry_path TEXT NOT NULL,\n embedding TEXT,\n embedding_blob BLOB,\n dimension INTEGER,\n PRIMARY KEY (scope, entry_path)\n )`),\n );\n await this.db.run(\n sql.raw(`CREATE VIRTUAL TABLE IF NOT EXISTS idx_fts USING fts5(\n scope UNINDEXED,\n entry_path UNINDEXED,\n summary,\n tokenize='${tokenizer}'\n )`),\n );\n // (scope, entry_path) -> idx_fts rowid mapping (issue #1103). See the\n // `writeFtsRow`/`deleteFtsRow` helper block near the bottom of this file\n // for the full rationale: `scope`/`entry_path` are UNINDEXED on idx_fts,\n // so deleting by them is a full-table scan (measured 67% of a production\n // D1's 7-day read budget); writing/deleting by rowid via this mapping is O(1).\n await this.ensureFtsRowidMapping();\n await this.db.run(\n sql.raw(`CREATE TABLE IF NOT EXISTS idx_index_cursor (\n scope TEXT NOT NULL,\n consumer_id TEXT NOT NULL,\n last_seq INTEGER NOT NULL DEFAULT 0,\n watermarks TEXT,\n updated_at INTEGER NOT NULL,\n\tserved_at INTEGER,\n instance_did TEXT,\n domains TEXT,\n PRIMARY KEY (scope, consumer_id)\n )`),\n );\n }\n\n /**\n * Create `idx_fts_rowid` the first time this D1 sees it, backfilling from\n * any pre-existing `idx_fts` rows (issue #1103). The backfill maps every\n * existing FTS row to its own rowid so a later rewrite REPLACEs in place\n * instead of leaving a duplicate for the same (scope, entry_path).\n *\n * Race-safe across concurrent cold isolates — D1 auto-commits each statement\n * (no transaction), so two isolates can run this at once on a fresh D1:\n * - `CREATE TABLE IF NOT EXISTS` never throws on a concurrent create.\n * - The one-time backfill runs only while the mapping is still empty (a\n * cheap LIMIT-1 probe, NOT a scan), so steady state costs nothing extra.\n * - `INSERT OR IGNORE` makes the backfill itself idempotent, so two\n * isolates racing it (or a re-run) can't collide on the PK/UNIQUE.\n */\n private async ensureFtsRowidMapping(): Promise<void> {\n await this.db.run(\n sql.raw(`CREATE TABLE IF NOT EXISTS idx_fts_rowid (\n fts_rowid INTEGER PRIMARY KEY AUTOINCREMENT,\n scope TEXT NOT NULL,\n entry_path TEXT NOT NULL,\n UNIQUE (scope, entry_path)\n )`),\n );\n const populated = await this.db.all<{ one: number }>(\n sql.raw(\"SELECT 1 AS one FROM idx_fts_rowid LIMIT 1\"),\n );\n if (populated.length > 0) return; // already backfilled / in use — no scan.\n // Preserve existing FTS rowids as the owned fts_rowid; explicit inserts\n // into the AUTOINCREMENT column also seed sqlite_sequence to MAX(rowid) so\n // newly-allocated ids never collide with a live idx_fts row.\n await this.db.run(\n sql.raw(\n `INSERT OR IGNORE INTO idx_fts_rowid (fts_rowid, scope, entry_path) SELECT rowid, scope, entry_path FROM idx_fts`,\n ),\n );\n }\n\n private async supportsCursorServedAt(): Promise<boolean> {\n if (this.d1 && typeof this.d1 === \"object\") {\n const cached = cursorServedAtSupported.get(this.d1);\n if (cached !== undefined) return cached;\n }\n const cols = await this.db.all<{ name: string }>(\n sql.raw(\"PRAGMA table_info(idx_index_cursor)\"),\n );\n const supported = cols.some((c) => c.name === \"served_at\");\n if (supported && this.d1 && typeof this.d1 === \"object\") {\n cursorServedAtSupported.set(this.d1, supported);\n }\n return supported;\n }\n\n // --- Entry metadata ---\n\n async writeEntry(entry: IndexedEntry): Promise<void> {\n const e = entryToRow(entry);\n await this.db.run(\n sql`INSERT OR REPLACE INTO ds_indexed_entry (scope, entry_path, domain, state, indexed_at, updated_at, anchor_count, source_path, source_hash, extractor_id, meta)\n VALUES (${this.scope}, ${e.entry_path}, ${e.domain}, ${e.state}, ${e.indexed_at}, ${e.updated_at}, ${e.anchor_count}, ${e.source_path ?? null}, ${e.source_hash ?? null}, ${e.extractor_id ?? null}, ${e.meta ?? null})`,\n );\n }\n\n async writeIndexedDocument(document: IndexedDocumentWrite): Promise<void> {\n const d1 = this.d1BatchBinding();\n if (!d1) {\n await this.writeEntry(document.entry);\n await this.writeAnchors(document.entry.entryPath, document.anchors ?? []);\n if (document.summary) await this.writeSummary(document.entry.entryPath, document.summary);\n return;\n }\n\n const entry = entryToRow(document.entry);\n const entryPath = document.entry.entryPath;\n const statements: D1PreparedStatementLike[] = [\n d1\n .prepare(\n `INSERT OR REPLACE INTO ds_indexed_entry (scope, entry_path, domain, state, indexed_at, updated_at, anchor_count, source_path, source_hash, extractor_id, meta)\n\t VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .bind(\n this.scope,\n entry.entry_path as string,\n entry.domain as string,\n entry.state as string,\n entry.indexed_at as string,\n entry.updated_at as string,\n entry.anchor_count as number,\n entry.source_path as string | null,\n entry.source_hash as string | null,\n entry.extractor_id as string | null,\n entry.meta as string | null,\n ),\n d1\n .prepare(\"DELETE FROM idx_anchor_data WHERE scope = ? AND entry_path = ?\")\n .bind(this.scope, entryPath),\n d1\n .prepare(\"DELETE FROM idx_anchor_key WHERE scope = ? AND entry_path = ?\")\n .bind(this.scope, entryPath),\n ];\n\n const anchors = document.anchors ?? [];\n for (const chunk of chunkArray(anchors, 18)) {\n statements.push(\n d1\n .prepare(\n `INSERT INTO idx_anchor_data (scope, entry_path, anchor_type, confidence, raw_anchor) VALUES ${chunk\n .map(() => \"(?, ?, ?, ?, ?)\")\n .join(\", \")}`,\n )\n .bind(\n ...chunk.flatMap((a) => [\n this.scope,\n entryPath,\n a.type,\n a.confidence,\n JSON.stringify(a),\n ]),\n ),\n );\n }\n\n const keyRows = anchors.flatMap((a) =>\n this.keyRegistry.generateKeys(a).map((key) => ({\n anchor: a,\n key,\n })),\n );\n for (const chunk of chunkArray(keyRows, 18)) {\n statements.push(\n d1\n .prepare(\n `INSERT OR REPLACE INTO idx_anchor_key (scope, index_key, entry_path, anchor_type, confidence) VALUES ${chunk\n .map(() => \"(?, ?, ?, ?, ?)\")\n .join(\", \")}`,\n )\n .bind(\n ...chunk.flatMap(({ anchor, key }) => [\n this.scope,\n key,\n entryPath,\n anchor.type,\n anchor.confidence,\n ]),\n ),\n );\n }\n\n if (document.summary) {\n statements.push(\n d1\n .prepare(\n \"INSERT INTO idx_fts_rowid (scope, entry_path) VALUES (?, ?) ON CONFLICT(scope, entry_path) DO NOTHING\",\n )\n .bind(this.scope, entryPath),\n d1\n .prepare(\n `INSERT OR REPLACE INTO idx_fts (rowid, scope, entry_path, summary)\n\t VALUES ((SELECT fts_rowid FROM idx_fts_rowid WHERE scope = ? AND entry_path = ?), ?, ?, ?)`,\n )\n .bind(this.scope, entryPath, this.scope, entryPath, segmentCJK(document.summary)),\n );\n }\n\n await d1.batch(statements);\n }\n\n async getEntry(path: string): Promise<IndexedEntry | null> {\n const rows = await this.db.all<EntryRow>(\n sql`SELECT * FROM ds_indexed_entry WHERE scope = ${this.scope} AND entry_path = ${path} LIMIT 1`,\n );\n if (rows.length === 0) return null;\n return rowToEntry(rows[0]!);\n }\n\n async removeEntry(path: string): Promise<void> {\n await this.db.run(\n sql`DELETE FROM ds_indexed_entry WHERE scope = ${this.scope} AND entry_path = ${path}`,\n );\n }\n\n async removeIndexedDocument(path: string, opts?: RemoveIndexOptions): Promise<void> {\n // Phase 4 offline reset: `skipVector` deletes relational/FTS/anchor but never\n // touches embeddings (D1-native idx_embedding_vector OR injected Upstash).\n const skipVector = opts?.skipVector === true;\n const d1 = this.d1BatchBinding();\n if (!d1) {\n await this.removeAnchors(path);\n await this.removeSummary(path);\n if (!skipVector) await this.removeEmbedding(path);\n await this.removeEntry(path);\n return;\n }\n if (this._injectedVectorBackend && !skipVector) {\n await this.removeEmbedding(path);\n }\n const statements = [\n d1\n .prepare(\"DELETE FROM idx_anchor_data WHERE scope = ? AND entry_path = ?\")\n .bind(this.scope, path),\n d1\n .prepare(\"DELETE FROM idx_anchor_key WHERE scope = ? AND entry_path = ?\")\n .bind(this.scope, path),\n d1\n .prepare(\n \"DELETE FROM idx_fts WHERE rowid = (SELECT fts_rowid FROM idx_fts_rowid WHERE scope = ? AND entry_path = ?)\",\n )\n .bind(this.scope, path),\n d1\n .prepare(\"DELETE FROM idx_fts_rowid WHERE scope = ? AND entry_path = ?\")\n .bind(this.scope, path),\n d1\n .prepare(\"DELETE FROM ds_indexed_entry WHERE scope = ? AND entry_path = ?\")\n .bind(this.scope, path),\n ];\n if (!this._injectedVectorBackend && !skipVector) {\n statements.splice(\n 4,\n 0,\n d1\n .prepare(\"DELETE FROM idx_embedding_vector WHERE scope = ? AND entry_path = ?\")\n .bind(this.scope, path),\n );\n }\n await d1.batch(statements);\n }\n\n async listEntries(filter: EntryFilter): Promise<IndexedEntry[]> {\n const conditions = this.entryConditions(filter);\n const rows = await this.db.all<EntryRow>(\n sql`SELECT * FROM ds_indexed_entry WHERE ${sql.join(conditions, sql` AND `)}`,\n );\n return rows.map(rowToEntry);\n }\n\n private entryConditions(filter: EntryFilter): ReturnType<typeof sql>[] {\n const conditions: ReturnType<typeof sql>[] = [sql`scope = ${this.scope}`];\n if (filter.domain) conditions.push(sql`domain = ${filter.domain}`);\n if (filter.state) conditions.push(sql`state = ${filter.state}`);\n if (filter.extractorId) conditions.push(sql`extractor_id = ${filter.extractorId}`);\n if (filter.scope) conditions.push(sql`entry_path LIKE ${`${filter.scope}%`}`);\n // Cross-blocklet boundary: same-scope tenants share `scope` but declare\n // different domains, so `reindex`/`cleanup`/`verify` must stay inside the\n // caller's own bound domains (the query path's `domains` counterpart).\n if (filter.boundDomains && filter.boundDomains.length > 0) {\n const ps = filter.boundDomains.map((d) => sql`${d}`);\n conditions.push(sql`domain IN (${sql.join(ps, sql`, `)})`);\n }\n return conditions;\n }\n\n async markStale(filter: StaleFilter): Promise<number> {\n const conditions: ReturnType<typeof sql>[] = [\n sql`scope = ${this.scope}`,\n sql`state = 'active'`,\n ];\n if (filter.domain) conditions.push(sql`domain = ${filter.domain}`);\n if (filter.extractorId) conditions.push(sql`extractor_id = ${filter.extractorId}`);\n if (filter.scope) conditions.push(sql`entry_path LIKE ${`${filter.scope}%`}`);\n if (filter.boundDomains && filter.boundDomains.length > 0) {\n const ps = filter.boundDomains.map((d) => sql`${d}`);\n conditions.push(sql`domain IN (${sql.join(ps, sql`, `)})`);\n }\n\n if (filter.anchorType) {\n const anchorRows = await this.db.all<{ entry_path: string }>(\n sql`SELECT DISTINCT entry_path FROM idx_anchor_data INDEXED BY idx_ad_scope_anchor_type WHERE scope = ${this.scope} AND anchor_type = ${filter.anchorType}`,\n );\n if (anchorRows.length === 0) return 0;\n const placeholders = anchorRows.map((r) => sql`${r.entry_path}`);\n conditions.push(sql`entry_path IN (${sql.join(placeholders, sql`, `)})`);\n }\n\n const where = sql.join(conditions, sql` AND `);\n const countRows = await this.db.all<{ count: number }>(\n sql`SELECT COUNT(*) as count FROM ds_indexed_entry WHERE ${where}`,\n );\n const count = countRows[0]?.count ?? 0;\n if (count === 0) return 0;\n\n await this.db.run(sql`UPDATE ds_indexed_entry SET state = 'stale' WHERE ${where}`);\n return count;\n }\n\n // --- Count ---\n\n async countEntries(filter: EntryFilter): Promise<number> {\n const conditions = this.entryConditions(filter);\n const rows = await this.db.all<{ count: number }>(\n sql`SELECT COUNT(*) as count FROM ds_indexed_entry WHERE ${sql.join(conditions, sql` AND `)}`,\n );\n return rows[0]?.count ?? 0;\n }\n\n // --- Anchor operations ---\n\n async writeAnchors(path: string, anchors: Anchor[]): Promise<void> {\n // Clear the entry's prior anchors first (ordered before the inserts), then\n // re-insert them as MULTI-ROW statements — one INSERT carries many rows,\n // collapsing the old 2 awaits + Promise.all fan-out of ~18 per-statement\n // round-trips per entry (issue #1120's N+1 write) into a handful. Multi-row\n // INSERT rather than db.batch: drizzle's `db.batch([db.run(sql\\`\\`), …])`\n // works on the libsql driver but throws `preparedQuery.stmt.bind` on the D1\n // driver this backend actually runs on — a plain multi-row `db.run` is\n // portable across D1 / libsql / sqlite-proxy. A partial failure self-heals on\n // the next reconcile (the index is a rebuildable derived model).\n await this.db.run(\n sql`DELETE FROM idx_anchor_data WHERE scope = ${this.scope} AND entry_path = ${path}`,\n );\n await this.db.run(\n sql`DELETE FROM idx_anchor_key WHERE scope = ${this.scope} AND entry_path = ${path}`,\n );\n if (anchors.length === 0) return;\n\n const dataRows = anchors.map(\n (a) => sql`(${this.scope}, ${path}, ${a.type}, ${a.confidence}, ${JSON.stringify(a)})`,\n );\n await this.multiRowInsert(\n sql`INSERT INTO idx_anchor_data (scope, entry_path, anchor_type, confidence, raw_anchor) VALUES `,\n dataRows,\n );\n\n const keyRows = anchors.flatMap((a) =>\n this.keyRegistry\n .generateKeys(a)\n .map((key) => sql`(${this.scope}, ${key}, ${path}, ${a.type}, ${a.confidence})`),\n );\n await this.multiRowInsert(\n sql`INSERT OR REPLACE INTO idx_anchor_key (scope, index_key, entry_path, anchor_type, confidence) VALUES `,\n keyRows,\n );\n }\n\n /**\n * Run `<prefix>(row),(row),…` as multi-row INSERT statement(s), chunked so\n * each stays under D1's 100-bound-parameter-per-statement limit (5 columns per\n * row → 18 rows ≈ 90 params). No-op on an empty row list. Portable across all\n * db backends (plain `db.run`, unlike the driver-specific db.batch).\n */\n private async multiRowInsert(\n prefix: ReturnType<typeof sql>,\n rows: Array<ReturnType<typeof sql>>,\n ): Promise<void> {\n const CHUNK = 18;\n for (let i = 0; i < rows.length; i += CHUNK) {\n await this.db.run(sql`${prefix}${sql.join(rows.slice(i, i + CHUNK), sql`, `)}`);\n }\n }\n\n async queryAnchors(query: AnchorQuery): Promise<AnchorResult[]> {\n if (query.keys.length === 0) return [];\n\n const keyGroups = query.keys.map((kg) => {\n const values = new Set<string>();\n for (const raw of kg.values) {\n const synthetic = { type: kg.type ?? \"exact\", values: [raw], confidence: 1 } as Anchor;\n for (const k of this.keyRegistry.generateKeys(synthetic)) values.add(k);\n values.add(raw);\n }\n return { values: [...values] };\n });\n\n const cteNames: string[] = [];\n const cteParts: ReturnType<typeof sql>[] = [];\n\n for (let i = 0; i < keyGroups.length; i++) {\n const cteName = `g${i}`;\n cteNames.push(cteName);\n // Non-index_key predicates, shared by every UNION arm below.\n const extraConds: ReturnType<typeof sql>[] = [];\n // entry_path prefixes as half-open ranges so the (scope, index_key,\n // entry_path) PK bounds the seek. `entry_path LIKE 'p%'` is NOT pushed\n // into the seek (SQLite won't range-optimise a case-insensitive LIKE on a\n // BINARY column), so it read every row for the index_key — measured 3,494\n // vs 1 rows_read for a selective prefix on staging D1 (issue #1120).\n for (const prefix of [query.scope, query.pathPrefix]) {\n if (prefix) {\n const [lo, hi] = entryPathPrefixRange(prefix);\n extraConds.push(sql`entry_path >= ${lo} AND entry_path < ${hi}`);\n }\n }\n if (query.minConfidence != null) extraConds.push(sql`confidence >= ${query.minConfidence}`);\n\n // One equality seek per key value instead of `index_key IN (...)`.\n // idx_anchor_key is WITHOUT ROWID with PK (scope, index_key, ...), so\n // `index_key = ?` seeks the PK. `index_key IN (...)` instead makes the\n // planner (no ANALYZE stats) fall back to a full-scope scan via\n // idx_ak_scope_path — measured 44k rows_read on staging D1 vs ~7k for this\n // UNION (issue #1120). UNION ALL is semantics-preserving: a row matches\n // exactly one index_key (no duplicates) and MIN(confidence) folds per\n // entry_path exactly as the old GROUP BY did.\n const arms = keyGroups[i]!.values.map(\n (v) =>\n sql`SELECT entry_path, confidence FROM idx_anchor_key WHERE ${sql.join(\n [sql`scope = ${this.scope}`, sql`index_key = ${v}`, ...extraConds],\n sql` AND `,\n )}`,\n );\n cteParts.push(\n sql`${sql.raw(cteName)} AS (SELECT entry_path, MIN(confidence) as min_conf FROM (${sql.join(\n arms,\n sql` UNION ALL `,\n )}) GROUP BY entry_path)`,\n );\n }\n\n const cteSql = sql`WITH ${sql.join(cteParts, sql`, `)}`;\n\n const cteJoinParts: string[] = [];\n let scoreExpr: string;\n if (cteNames.length === 1) {\n scoreExpr = `${cteNames[0]!}.min_conf`;\n } else {\n for (let i = 1; i < cteNames.length; i++) {\n cteJoinParts.push(\n `INNER JOIN ${cteNames[i]} ON ${cteNames[0]}.entry_path = ${cteNames[i]}.entry_path`,\n );\n }\n scoreExpr = `MIN(${cteNames.map((n) => `${n}.min_conf`).join(\", \")})`;\n }\n\n const metaFilter = query.metaFilter;\n const domains = query.domains;\n const entryJoinPart: string[] = [];\n let entryWhereSql: ReturnType<typeof sql> | null = null;\n if ((domains && domains.length > 0) || (metaFilter && Object.keys(metaFilter).length > 0)) {\n // Join includes scope so meta/domain come from the same-scope entry\n // row. D1 is already scope-partitioned (every table carries this\n // backend's bound `scope`), so `domains` here is defense-in-depth\n // symmetry with Node's shared-file backend, not a required isolation\n // boundary — but harmless and consistent to support.\n entryJoinPart.push(\n `INNER JOIN ds_indexed_entry ie ON ${cteNames[0]!}.entry_path = ie.entry_path AND ie.scope = '${sqlEscape(this.scope)}'`,\n );\n const entryConditions: ReturnType<typeof sql>[] = [];\n if (domains && domains.length > 0) {\n const ps = domains.map((d) => sql`${d}`);\n entryConditions.push(sql`ie.domain IN (${sql.join(ps, sql`, `)})`);\n }\n for (const [key, expected] of Object.entries(metaFilter ?? {})) {\n if (Array.isArray(expected)) {\n if (expected.length === 0) return [];\n const ps = expected.map((v) => sql`${v}`);\n entryConditions.push(\n sql`json_extract(ie.meta, ${`$.${key}`}) IN (${sql.join(ps, sql`, `)})`,\n );\n } else {\n entryConditions.push(sql`json_extract(ie.meta, ${`$.${key}`}) = ${expected}`);\n }\n }\n entryWhereSql = sql.join(entryConditions, sql` AND `);\n }\n\n const allJoins = [...cteJoinParts, ...entryJoinPart].join(\" \");\n let selectSql: ReturnType<typeof sql>;\n if (entryWhereSql) {\n selectSql = sql`${cteSql} SELECT ${sql.raw(cteNames[0]!)}.entry_path, ${sql.raw(scoreExpr)} as score FROM ${sql.raw(cteNames[0]!)} ${sql.raw(allJoins)} WHERE ${entryWhereSql}`;\n } else {\n selectSql = sql`${cteSql} SELECT ${sql.raw(cteNames[0]!)}.entry_path, ${sql.raw(scoreExpr)} as score FROM ${sql.raw(cteNames[0]!)} ${sql.raw(allJoins)}`;\n }\n\n // Secondary sort key stabilizes top-k ordering: ties in `score` would\n // otherwise leave row order undefined across runs (#1368).\n const orderSql =\n query.limit != null\n ? sql`${selectSql} ORDER BY score DESC, ${sql.raw(cteNames[0]!)}.entry_path LIMIT ${query.limit}`\n : sql`${selectSql} ORDER BY score DESC, ${sql.raw(cteNames[0]!)}.entry_path`;\n\n const rows = await this.db.all<{ entry_path: string; score: number }>(orderSql);\n return rows.map((r) => ({ entryPath: r.entry_path, score: r.score }));\n }\n\n async removeAnchors(path: string): Promise<void> {\n await this.db.run(\n sql`DELETE FROM idx_anchor_data WHERE scope = ${this.scope} AND entry_path = ${path}`,\n );\n await this.db.run(\n sql`DELETE FROM idx_anchor_key WHERE scope = ${this.scope} AND entry_path = ${path}`,\n );\n }\n\n async getAnchorData(path: string): Promise<AnchorData[]> {\n const rows = await this.db.all<AnchorDataRow>(\n sql`SELECT * FROM idx_anchor_data WHERE scope = ${this.scope} AND entry_path = ${path}`,\n );\n return rows.map((r) => ({\n entryPath: r.entry_path,\n anchorType: r.anchor_type,\n confidence: r.confidence,\n rawAnchor: r.raw_anchor,\n }));\n }\n\n // --- FTS operations ---\n\n async writeSummary(path: string, summary: string): Promise<void> {\n // issue #1103: atomic insert-or-replace by the stable mapped rowid — the\n // old `DELETE ... WHERE scope=? AND entry_path=?` full-scanned idx_fts\n // (UNINDEXED columns). See the helper block at the bottom of the file.\n await writeFtsRow(this.db, this.scope, path, segmentCJK(summary));\n }\n\n async getSummary(path: string): Promise<string | null> {\n const rows = await this.db.all<{ summary: string }>(\n sql`SELECT summary FROM idx_fts WHERE scope = ${this.scope} AND entry_path = ${path} LIMIT 1`,\n );\n return rows[0]?.summary ?? null;\n }\n\n async queryFTS(\n text: string,\n limit?: number,\n metaFilter?: Record<string, unknown>,\n opts?: { domain?: string; domains?: string[]; scope?: string; pathPrefix?: string },\n ): Promise<FTSResult[]> {\n try {\n const sanitized = sanitizeFTS5(text);\n if (!sanitized) return [];\n\n const needsJoin =\n (metaFilter && Object.keys(metaFilter).length > 0) ||\n (typeof opts?.domain === \"string\" && opts.domain.length > 0) ||\n (Array.isArray(opts?.domains) && opts.domains.length > 0) ||\n (typeof opts?.pathPrefix === \"string\" && opts.pathPrefix.length > 0);\n\n let query: ReturnType<typeof sql>;\n\n if (needsJoin) {\n const conditions: ReturnType<typeof sql>[] = [\n sql`idx_fts MATCH ${sanitized}`,\n sql`idx_fts.scope = ${this.scope}`,\n sql`ie.scope = ${this.scope}`,\n ];\n if (opts?.domain) conditions.push(sql`ie.domain = ${opts.domain}`);\n if (opts?.domains && opts.domains.length > 0) {\n const ps = opts.domains.map((d) => sql`${d}`);\n conditions.push(sql`ie.domain IN (${sql.join(ps, sql`, `)})`);\n }\n // Subtree scope (entry_path prefix): applied to the MATCH-bounded rows\n // over the ds_indexed_entry `(scope, entry_path)` PK — index-friendly,\n // no full scan. See QueryOpts.pathPrefix.\n if (opts?.pathPrefix) conditions.push(sql`ie.entry_path LIKE ${`${opts.pathPrefix}%`}`);\n for (const [key, expected] of Object.entries(metaFilter ?? {})) {\n if (Array.isArray(expected)) {\n if (expected.length === 0) return [];\n const ps = expected.map((v) => sql`${v}`);\n conditions.push(\n sql`json_extract(ie.meta, ${`$.${key}`}) IN (${sql.join(ps, sql`, `)})`,\n );\n } else {\n conditions.push(sql`json_extract(ie.meta, ${`$.${key}`}) = ${expected}`);\n }\n }\n const where = sql.join(conditions, sql` AND `);\n query =\n limit != null\n ? sql`SELECT idx_fts.entry_path, idx_fts.rank FROM idx_fts INNER JOIN ds_indexed_entry ie ON idx_fts.entry_path = ie.entry_path WHERE ${where} ORDER BY idx_fts.rank LIMIT ${limit}`\n : sql`SELECT idx_fts.entry_path, idx_fts.rank FROM idx_fts INNER JOIN ds_indexed_entry ie ON idx_fts.entry_path = ie.entry_path WHERE ${where} ORDER BY idx_fts.rank`;\n } else {\n query =\n limit != null\n ? sql`SELECT entry_path, rank FROM idx_fts WHERE idx_fts MATCH ${sanitized} AND scope = ${this.scope} ORDER BY rank LIMIT ${limit}`\n : sql`SELECT entry_path, rank FROM idx_fts WHERE idx_fts MATCH ${sanitized} AND scope = ${this.scope} ORDER BY rank`;\n }\n\n const rows = await this.db.all<{ entry_path: string; rank: number }>(query);\n return rows.map((r) => ({ entryPath: r.entry_path, rank: -r.rank }));\n } catch {\n // FTS5 query syntax error — return empty results rather than crashing (aligned with SQLite backend)\n return [];\n }\n }\n\n async removeSummary(path: string): Promise<void> {\n // issue #1103: delete by mapped rowid (O(1)), not full-table scan.\n await deleteFtsRow(this.db, this.scope, path);\n }\n\n // --- Embedding operations (delegated to VectorBackend) ---\n\n async writeEmbedding(path: string, embedding: number[], domain?: string): Promise<void> {\n await this._vectorBackend.upsert(path, embedding, domain);\n }\n\n async queryVector(\n embedding: number[],\n limit?: number,\n opts?: { domain?: string; domains?: string[]; scope?: string; pathPrefix?: string },\n ): Promise<VectorResult[]> {\n return this._vectorBackend.query(\n embedding,\n { domain: opts?.domain, domains: opts?.domains, pathPrefix: opts?.pathPrefix },\n limit,\n );\n }\n\n async removeEmbedding(path: string): Promise<void> {\n await this._vectorBackend.delete(path);\n }\n\n embeddingCapability(): { supported: boolean; dimension: number } | null {\n return this._vectorBackend.capability();\n }\n\n // --- Changelog follower cursor ---\n\n async readCursor(consumerId: string): Promise<CursorRow | null> {\n const rows = await this.db.all<{\n consumer_id: string;\n last_seq: number;\n watermarks: string | null;\n updated_at: number;\n }>(\n sql`SELECT consumer_id, last_seq, watermarks, updated_at FROM idx_index_cursor WHERE scope = ${this.scope} AND consumer_id = ${consumerId} LIMIT 1`,\n );\n if (rows.length === 0) return null;\n const row = rows[0]!;\n return {\n consumerId: row.consumer_id,\n lastSeq: row.last_seq,\n watermarks: row.watermarks\n ? (JSON.parse(row.watermarks) as Record<string, number>)\n : undefined,\n updatedAt: row.updated_at,\n };\n }\n\n /**\n * Set a cursor row's `last_seq` (+ watermarks/updated_at/served_at).\n *\n * NOTE (index-follower-dirty-path §4.2 cutover): the index FOLLOWER no longer\n * calls this — after the changelog replay was deleted, `last_seq` is a DEAD\n * COLUMN (nothing advances it during drain; scheduling rides `served_at` via\n * `markCursorServed`). `writeCursor` now has NO production caller (the ops\n * scripts write cursor rows via raw SQL, not this method); it is retained for\n * the monotonic-guard test suite + as a stable backend primitive, so the\n * `MAX(last_seq, excluded.last_seq)` guard (issue #935 BLOCK fix #2) stays\n * correct there; watermarks/updated_at/served_at update unconditionally.\n */\n async writeCursor(\n consumerId: string,\n lastSeq: number,\n watermarks?: Record<string, number>,\n ): Promise<void> {\n const now = Date.now();\n const watermarksJson = watermarks ? JSON.stringify(watermarks) : null;\n if (await this.supportsCursorServedAt()) {\n await this.db.run(\n sql`INSERT INTO idx_index_cursor (scope, consumer_id, last_seq, watermarks, updated_at, served_at)\n\t VALUES (${this.scope}, ${consumerId}, ${lastSeq}, ${watermarksJson}, ${now}, ${now})\n\t ON CONFLICT(scope, consumer_id) DO UPDATE SET\n\t last_seq = MAX(last_seq, excluded.last_seq),\n\t watermarks = excluded.watermarks,\n\t updated_at = excluded.updated_at,\n\t served_at = excluded.served_at`,\n );\n return;\n }\n await this.db.run(\n sql`INSERT INTO idx_index_cursor (scope, consumer_id, last_seq, watermarks, updated_at)\n\t VALUES (${this.scope}, ${consumerId}, ${lastSeq}, ${watermarksJson}, ${now})\n ON CONFLICT(scope, consumer_id) DO UPDATE SET\n last_seq = MAX(last_seq, excluded.last_seq),\n watermarks = excluded.watermarks,\n updated_at = excluded.updated_at`,\n );\n }\n\n async markCursorServed(consumerId: string): Promise<void> {\n if (!(await this.supportsCursorServedAt())) return;\n await this.db.run(\n sql`UPDATE idx_index_cursor SET served_at = ${Date.now()} WHERE scope = ${this.scope} AND consumer_id = ${consumerId}`,\n );\n }\n\n async initCursor(\n consumerId: string,\n registration?: { instanceDid?: string; domainsJson?: string },\n ): Promise<void> {\n const now = Date.now();\n if (await this.supportsCursorServedAt()) {\n await this.db.run(\n sql`INSERT OR IGNORE INTO idx_index_cursor (scope, consumer_id, last_seq, watermarks, updated_at, served_at, instance_did, domains)\n\t VALUES (${this.scope}, ${consumerId}, 0, NULL, ${now}, ${now}, ${registration?.instanceDid ?? null}, ${registration?.domainsJson ?? null})`,\n );\n return;\n }\n await this.db.run(\n sql`INSERT OR IGNORE INTO idx_index_cursor (scope, consumer_id, last_seq, watermarks, updated_at, instance_did, domains)\n VALUES (${this.scope}, ${consumerId}, 0, NULL, ${now}, ${registration?.instanceDid ?? null}, ${registration?.domainsJson ?? null})`,\n );\n }\n\n /**\n * See {@link IndexBackend.listCursorScopes}. Deliberately NOT filtered by\n * `this.scope` — this is the one cursor operation that must see EVERY\n * scope in the D1, so the CF cron can discover work registered by ANY\n * caller in ANY prior isolate, not just the scope this backend instance\n * happens to be constructed for.\n *\n * `consumerId` is a FAMILY prefix, not an exact match (design.md §4.4\n * addendum, owner decision 2026-07-05): every real cursor row's\n * `consumer_id` is scope-qualified via `deriveFollowerConsumerId(scope)`\n * (\"index:{scope}\") so a single shared backend/database can host multiple\n * scopes without their cursor rows colliding — see follower.ts. Discovery\n * doesn't know the scope in advance (that's its whole job), so it matches\n * every row whose `consumer_id` starts with `\"{consumerId}:\"` instead of\n * one exact value. Callers keep passing the bare `FOLLOWER_CONSUMER_ID`\n * (\"index\") — this method appends the `:` separator internally.\n */\n async listCursorScopes(consumerId: string): Promise<CursorScopeRegistration[]> {\n const rows = await this.db.all<{\n scope: string;\n consumer_id: string;\n instance_did: string | null;\n domains: string | null;\n updated_at: number;\n served_at?: number | null;\n }>(\n (await this.supportsCursorServedAt())\n ? sql`SELECT scope, consumer_id, instance_did, domains, updated_at, served_at FROM idx_index_cursor WHERE consumer_id LIKE ${`${consumerId}:%`}`\n : sql`SELECT scope, consumer_id, instance_did, domains, updated_at FROM idx_index_cursor WHERE consumer_id LIKE ${`${consumerId}:%`}`,\n );\n return rows.map((r) => ({\n scope: r.scope,\n consumerId: r.consumer_id,\n instanceDid: r.instance_did,\n domainsJson: r.domains,\n updatedAt: r.updated_at,\n servedAt: r.served_at ?? r.updated_at,\n }));\n }\n\n // --- Batch operations ---\n //\n // No explicit BEGIN/COMMIT: D1 auto-commits each statement, and the index is a\n // derived, rebuildable read model (a partial write self-heals on reconcile).\n\n async writeEntriesBatch(entries: IndexedEntry[]): Promise<void> {\n for (const entry of entries) await this.writeEntry(entry);\n }\n\n async writeAnchorsBatch(items: { path: string; anchors: Anchor[] }[]): Promise<void> {\n for (const item of items) await this.writeAnchors(item.path, item.anchors);\n }\n\n async writeSummariesBatch(items: { path: string; summary: string }[]): Promise<void> {\n for (const item of items) await this.writeSummary(item.path, item.summary);\n }\n\n async purgeEntries(paths: string[], opts?: RemoveIndexOptions): Promise<number> {\n const skipVector = opts?.skipVector === true;\n // Batched fast path (offline rebuild): collapse the ~5 per-entry DELETEs into\n // `d1.batch()` chunks — ONE round-trip per chunk instead of per statement.\n // This is the dominant cost of a REMOTE rebuild (getPlatformProxy → each query\n // is a network hop). Only when vectors are skipped: removing an injected\n // (Upstash) vector is an async non-D1 call that can't ride the D1 batch.\n const d1 = this.d1BatchBinding();\n if (d1 && skipVector && paths.length > 0) {\n const stmts: D1PreparedStatementLike[] = [];\n for (const path of paths) {\n stmts.push(\n d1\n .prepare(\"DELETE FROM idx_anchor_data WHERE scope = ? AND entry_path = ?\")\n .bind(this.scope, path),\n d1\n .prepare(\"DELETE FROM idx_anchor_key WHERE scope = ? AND entry_path = ?\")\n .bind(this.scope, path),\n d1\n .prepare(\n \"DELETE FROM idx_fts WHERE rowid = (SELECT fts_rowid FROM idx_fts_rowid WHERE scope = ? AND entry_path = ?)\",\n )\n .bind(this.scope, path),\n d1\n .prepare(\"DELETE FROM idx_fts_rowid WHERE scope = ? AND entry_path = ?\")\n .bind(this.scope, path),\n d1\n .prepare(\"DELETE FROM ds_indexed_entry WHERE scope = ? AND entry_path = ?\")\n .bind(this.scope, path),\n );\n }\n for (const chunk of chunkArray(stmts, 50)) await d1.batch(chunk);\n return paths.length;\n }\n for (const path of paths) {\n await this.removeAnchors(path);\n await this.removeSummary(path);\n if (!skipVector) await this.removeEmbedding(path);\n await this.removeEntry(path);\n }\n return paths.length;\n }\n\n // --- Combined query ---\n\n async queryCombined(\n keys: { type: string; values: string[] }[],\n text?: string,\n opts?: QueryOpts,\n ): Promise<CombinedResult[]> {\n const anchorResults =\n keys.length > 0\n ? await this.queryAnchors({\n keys,\n scope: opts?.scope,\n minConfidence: opts?.minConfidence,\n limit: opts?.limit,\n })\n : [];\n\n const anchorPaths = new Set(anchorResults.map((r) => r.entryPath));\n const ftsResults = text ? await this.queryFTS(text, opts?.limit) : [];\n\n const results: CombinedResult[] = anchorResults.map((r) => {\n const fts = ftsResults.find((f) => f.entryPath === r.entryPath);\n return {\n entryPath: r.entryPath,\n anchorScore: r.score,\n ftsRank: fts?.rank,\n score: r.score + (fts?.rank ?? 0),\n };\n });\n\n for (const f of ftsResults) {\n if (!anchorPaths.has(f.entryPath)) {\n results.push({ entryPath: f.entryPath, ftsRank: f.rank, score: f.rank });\n }\n }\n\n results.sort((a, b) => b.score - a.score);\n if (opts?.limit != null) return results.slice(0, opts.limit);\n return results;\n }\n}\n\n// --- idx_fts rowid mapping helpers (issue #1103) ---\n//\n// Byte-for-byte mirror of the Node/libsql backend's helpers in\n// sqlite-backend.ts (see the full rationale there). `idx_fts_rowid` owns a\n// stable AUTOINCREMENT `fts_rowid` per (scope, entry_path); we write/delete\n// `idx_fts` BY THAT ROWID (O(1)) instead of by the UNINDEXED (scope,\n// entry_path) columns (which full-scanned the FTS5 table). The two-statement\n// write — create-once mapping row, then INSERT OR REPLACE at that rowid — is\n// race-safe under D1's auto-commit (no transaction): concurrent writers for\n// the same entry share one rowid and last-writer-wins, so they converge to a\n// single row rather than duplicating or colliding on the PRIMARY KEY.\n\n/** Get-or-create the stable idx_fts rowid for (scope, path) via the mapping table. */\nasync function ensureFtsRowid(db: SqliteDatabase, scope: string, path: string): Promise<number> {\n await db.run(\n sql`INSERT INTO idx_fts_rowid (scope, entry_path) VALUES (${scope}, ${path})\n ON CONFLICT(scope, entry_path) DO NOTHING`,\n );\n const rows = await db.all<{ fts_rowid: number }>(\n sql`SELECT fts_rowid FROM idx_fts_rowid WHERE scope = ${scope} AND entry_path = ${path} LIMIT 1`,\n );\n return rows[0]!.fts_rowid;\n}\n\n/** Atomically insert-or-replace an entry's FTS row at its stable mapped rowid. */\nasync function writeFtsRow(\n db: SqliteDatabase,\n scope: string,\n path: string,\n summary: string,\n): Promise<void> {\n const rowid = await ensureFtsRowid(db, scope, path);\n await db.run(\n sql`INSERT OR REPLACE INTO idx_fts (rowid, scope, entry_path, summary) VALUES (${rowid}, ${scope}, ${path}, ${summary})`,\n );\n}\n\n/** Delete an entry's idx_fts row (by mapped rowid, O(1)) and its mapping row. */\nasync function deleteFtsRow(db: SqliteDatabase, scope: string, path: string): Promise<void> {\n await db.run(\n sql`DELETE FROM idx_fts WHERE rowid = (SELECT fts_rowid FROM idx_fts_rowid WHERE scope = ${scope} AND entry_path = ${path})`,\n );\n await db.run(sql`DELETE FROM idx_fts_rowid WHERE scope = ${scope} AND entry_path = ${path}`);\n}\n\n// --- Internal row types ---\n\ninterface EntryRow {\n entry_path: string;\n source_path: string | null;\n source_hash: string | null;\n domain: string;\n state: string;\n extractor_id: string | null;\n indexed_at: string;\n updated_at: string;\n anchor_count: number;\n meta: string | null;\n}\n\ninterface AnchorDataRow {\n entry_path: string;\n anchor_type: string;\n confidence: number;\n raw_anchor: string;\n}\n\nfunction entryToRow(entry: IndexedEntry): Record<string, unknown> {\n return {\n entry_path: entry.entryPath,\n domain: entry.domain,\n state: entry.state,\n indexed_at: entry.indexedAt,\n updated_at: entry.updatedAt,\n anchor_count: entry.anchorCount,\n source_path: entry.sourcePath ?? null,\n source_hash: entry.sourceHash ?? null,\n extractor_id: entry.extractorId ?? null,\n meta: entry.meta != null ? JSON.stringify(entry.meta) : null,\n };\n}\n\nfunction rowToEntry(row: EntryRow): IndexedEntry {\n return {\n entryPath: row.entry_path,\n sourcePath: row.source_path ?? undefined,\n sourceHash: row.source_hash ?? undefined,\n domain: row.domain,\n state: row.state as IndexedEntry[\"state\"],\n extractorId: row.extractor_id ?? undefined,\n indexedAt: row.indexed_at,\n updatedAt: row.updated_at,\n anchorCount: row.anchor_count,\n meta: row.meta ? JSON.parse(row.meta) : undefined,\n };\n}\n\nfunction sqlEscape(value: string): string {\n return value.replace(/'/g, \"''\");\n}\n\nfunction chunkArray<T>(items: T[], size: number): T[][] {\n const chunks: T[][] = [];\n for (let i = 0; i < items.length; i += size) {\n chunks.push(items.slice(i, i + size));\n }\n return chunks;\n}\n\n// Does NOT strip AND/OR/NOT: query text is pre-built by transformFtsQuery\n// (#933), which re-emits OR as an intentional infix operator for memory's\n// or-recall mode (#1002). Stripping it again collapsed or-mode back to AND.\n// See sqlite-backend.ts sanitizeFTS5 for the full rationale.\nfunction sanitizeFTS5(text: string): string {\n return text\n .replace(/[\"*^()\\-+{}:]/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n"],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ let drizzle_orm = require("drizzle-orm");
2
+
3
+ //#region src/backend/d1-vector-backend.ts
4
+ var D1VectorBackend = class {
5
+ dimension = 0;
6
+ constructor(db, scope) {
7
+ this.db = db;
8
+ this.scope = scope;
9
+ }
10
+ async upsert(entryPath, vector, _domain) {
11
+ const blob = embeddingToBlob(vector);
12
+ await this.db.run(drizzle_orm.sql`INSERT INTO idx_embedding_vector (scope, entry_path, embedding, embedding_blob, dimension) VALUES (${this.scope}, ${entryPath}, '', ${blob}, ${vector.length})
13
+ ON CONFLICT(scope, entry_path) DO UPDATE SET
14
+ embedding = excluded.embedding,
15
+ embedding_blob = excluded.embedding_blob,
16
+ dimension = excluded.dimension`);
17
+ if (this.dimension === 0 && vector.length > 0) this.dimension = vector.length;
18
+ }
19
+ async query(vector, filter, topK) {
20
+ const conditions = [drizzle_orm.sql`idx_embedding_vector.scope = ${this.scope}`];
21
+ if (filter.domain) conditions.push(drizzle_orm.sql`ds_indexed_entry.domain = ${filter.domain}`);
22
+ if (filter.domains && filter.domains.length > 0) {
23
+ const ps = filter.domains.map((d) => drizzle_orm.sql`${d}`);
24
+ conditions.push(drizzle_orm.sql`ds_indexed_entry.domain IN (${drizzle_orm.sql.join(ps, drizzle_orm.sql`, `)})`);
25
+ }
26
+ if (filter.pathPrefix) conditions.push(drizzle_orm.sql`idx_embedding_vector.entry_path LIKE ${`${filter.pathPrefix}%`}`);
27
+ const where = drizzle_orm.sql`WHERE ${drizzle_orm.sql.join(conditions, drizzle_orm.sql` AND `)}`;
28
+ const rows = await this.db.all(drizzle_orm.sql`SELECT idx_embedding_vector.entry_path, idx_embedding_vector.embedding, idx_embedding_vector.embedding_blob
29
+ FROM idx_embedding_vector
30
+ JOIN ds_indexed_entry ON ds_indexed_entry.scope = idx_embedding_vector.scope AND ds_indexed_entry.entry_path = idx_embedding_vector.entry_path
31
+ ${where}`);
32
+ const queryVec = Float32Array.from(vector);
33
+ const results = rows.map((r) => ({
34
+ entryPath: r.entry_path,
35
+ similarity: cosineSimilarityF32(queryVec, decodeStoredEmbedding(r))
36
+ }));
37
+ results.sort((a, b) => b.similarity - a.similarity);
38
+ if (topK != null) return results.slice(0, topK);
39
+ return results;
40
+ }
41
+ async delete(entryPath) {
42
+ await this.db.run(drizzle_orm.sql`DELETE FROM idx_embedding_vector WHERE scope = ${this.scope} AND entry_path = ${entryPath}`);
43
+ }
44
+ capability() {
45
+ return {
46
+ supported: true,
47
+ dimension: this.dimension
48
+ };
49
+ }
50
+ };
51
+ function cosineSimilarityF32(a, b) {
52
+ const len = Math.min(a.length, b.length);
53
+ let dot = 0;
54
+ let magA = 0;
55
+ let magB = 0;
56
+ for (let i = 0; i < len; i++) {
57
+ const ai = a[i];
58
+ const bi = b[i];
59
+ dot += ai * bi;
60
+ magA += ai * ai;
61
+ magB += bi * bi;
62
+ }
63
+ const denom = Math.sqrt(magA) * Math.sqrt(magB);
64
+ return denom === 0 ? 0 : dot / denom;
65
+ }
66
+ function embeddingToBlob(embedding) {
67
+ const f32 = Float32Array.from(embedding);
68
+ return new Uint8Array(f32.buffer, f32.byteOffset, f32.byteLength);
69
+ }
70
+ function decodeStoredEmbedding(row) {
71
+ const blob = row.embedding_blob;
72
+ if (blob != null) {
73
+ if (Array.isArray(blob)) return new Float32Array(new Uint8Array(blob).buffer);
74
+ if (blob instanceof ArrayBuffer) return new Float32Array(blob.slice(0));
75
+ const view = new Float32Array(blob.buffer, blob.byteOffset, blob.byteLength / Float32Array.BYTES_PER_ELEMENT);
76
+ return new Float32Array(view);
77
+ }
78
+ if (row.embedding != null && row.embedding !== "") return Float32Array.from(JSON.parse(row.embedding));
79
+ return new Float32Array(0);
80
+ }
81
+
82
+ //#endregion
83
+ exports.D1VectorBackend = D1VectorBackend;
@@ -0,0 +1,21 @@
1
+ import { VectorResult } from "../types.cjs";
2
+ import { VectorBackend, VectorFilter } from "./vector-backend.cjs";
3
+ import { SqliteDatabase } from "@aigne/afs-sqlite";
4
+
5
+ //#region src/backend/d1-vector-backend.d.ts
6
+ declare class D1VectorBackend implements VectorBackend {
7
+ private readonly db;
8
+ private readonly scope;
9
+ private dimension;
10
+ constructor(db: SqliteDatabase, scope: string);
11
+ upsert(entryPath: string, vector: number[], _domain?: string): Promise<void>;
12
+ query(vector: number[], filter: VectorFilter, topK?: number): Promise<VectorResult[]>;
13
+ delete(entryPath: string): Promise<void>;
14
+ capability(): {
15
+ supported: boolean;
16
+ dimension: number;
17
+ } | null;
18
+ }
19
+ //#endregion
20
+ export { D1VectorBackend };
21
+ //# sourceMappingURL=d1-vector-backend.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1-vector-backend.d.cts","names":[],"sources":["../../src/backend/d1-vector-backend.ts"],"mappings":";;;;;cAea,eAAA,YAA2B,aAAA;EAAA,iBAInB,EAAA;EAAA,iBACA,KAAA;EAAA,QAJX,SAAA;cAGW,EAAA,EAAI,cAAA,EACJ,KAAA;EAKb,MAAA,CAAO,SAAA,UAAmB,MAAA,YAAkB,OAAA,YAAmB,OAAA;EAmB/D,KAAA,CAAM,MAAA,YAAkB,MAAA,EAAQ,YAAA,EAAc,IAAA,YAAgB,OAAA,CAAQ,YAAA;EAoCtE,MAAA,CAAO,SAAA,WAAoB,OAAA;EAMjC,UAAA,CAAA;IAAgB,SAAA;IAAoB,SAAA;EAAA;AAAA"}
@@ -0,0 +1,21 @@
1
+ import { VectorResult } from "../types.mjs";
2
+ import { VectorBackend, VectorFilter } from "./vector-backend.mjs";
3
+ import { SqliteDatabase } from "@aigne/afs-sqlite";
4
+
5
+ //#region src/backend/d1-vector-backend.d.ts
6
+ declare class D1VectorBackend implements VectorBackend {
7
+ private readonly db;
8
+ private readonly scope;
9
+ private dimension;
10
+ constructor(db: SqliteDatabase, scope: string);
11
+ upsert(entryPath: string, vector: number[], _domain?: string): Promise<void>;
12
+ query(vector: number[], filter: VectorFilter, topK?: number): Promise<VectorResult[]>;
13
+ delete(entryPath: string): Promise<void>;
14
+ capability(): {
15
+ supported: boolean;
16
+ dimension: number;
17
+ } | null;
18
+ }
19
+ //#endregion
20
+ export { D1VectorBackend };
21
+ //# sourceMappingURL=d1-vector-backend.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1-vector-backend.d.mts","names":[],"sources":["../../src/backend/d1-vector-backend.ts"],"mappings":";;;;;cAea,eAAA,YAA2B,aAAA;EAAA,iBAInB,EAAA;EAAA,iBACA,KAAA;EAAA,QAJX,SAAA;cAGW,EAAA,EAAI,cAAA,EACJ,KAAA;EAKb,MAAA,CAAO,SAAA,UAAmB,MAAA,YAAkB,OAAA,YAAmB,OAAA;EAmB/D,KAAA,CAAM,MAAA,YAAkB,MAAA,EAAQ,YAAA,EAAc,IAAA,YAAgB,OAAA,CAAQ,YAAA;EAoCtE,MAAA,CAAO,SAAA,WAAoB,OAAA;EAMjC,UAAA,CAAA;IAAgB,SAAA;IAAoB,SAAA;EAAA;AAAA"}
@@ -0,0 +1,84 @@
1
+ import { sql } from "drizzle-orm";
2
+
3
+ //#region src/backend/d1-vector-backend.ts
4
+ var D1VectorBackend = class {
5
+ dimension = 0;
6
+ constructor(db, scope) {
7
+ this.db = db;
8
+ this.scope = scope;
9
+ }
10
+ async upsert(entryPath, vector, _domain) {
11
+ const blob = embeddingToBlob(vector);
12
+ await this.db.run(sql`INSERT INTO idx_embedding_vector (scope, entry_path, embedding, embedding_blob, dimension) VALUES (${this.scope}, ${entryPath}, '', ${blob}, ${vector.length})
13
+ ON CONFLICT(scope, entry_path) DO UPDATE SET
14
+ embedding = excluded.embedding,
15
+ embedding_blob = excluded.embedding_blob,
16
+ dimension = excluded.dimension`);
17
+ if (this.dimension === 0 && vector.length > 0) this.dimension = vector.length;
18
+ }
19
+ async query(vector, filter, topK) {
20
+ const conditions = [sql`idx_embedding_vector.scope = ${this.scope}`];
21
+ if (filter.domain) conditions.push(sql`ds_indexed_entry.domain = ${filter.domain}`);
22
+ if (filter.domains && filter.domains.length > 0) {
23
+ const ps = filter.domains.map((d) => sql`${d}`);
24
+ conditions.push(sql`ds_indexed_entry.domain IN (${sql.join(ps, sql`, `)})`);
25
+ }
26
+ if (filter.pathPrefix) conditions.push(sql`idx_embedding_vector.entry_path LIKE ${`${filter.pathPrefix}%`}`);
27
+ const where = sql`WHERE ${sql.join(conditions, sql` AND `)}`;
28
+ const rows = await this.db.all(sql`SELECT idx_embedding_vector.entry_path, idx_embedding_vector.embedding, idx_embedding_vector.embedding_blob
29
+ FROM idx_embedding_vector
30
+ JOIN ds_indexed_entry ON ds_indexed_entry.scope = idx_embedding_vector.scope AND ds_indexed_entry.entry_path = idx_embedding_vector.entry_path
31
+ ${where}`);
32
+ const queryVec = Float32Array.from(vector);
33
+ const results = rows.map((r) => ({
34
+ entryPath: r.entry_path,
35
+ similarity: cosineSimilarityF32(queryVec, decodeStoredEmbedding(r))
36
+ }));
37
+ results.sort((a, b) => b.similarity - a.similarity);
38
+ if (topK != null) return results.slice(0, topK);
39
+ return results;
40
+ }
41
+ async delete(entryPath) {
42
+ await this.db.run(sql`DELETE FROM idx_embedding_vector WHERE scope = ${this.scope} AND entry_path = ${entryPath}`);
43
+ }
44
+ capability() {
45
+ return {
46
+ supported: true,
47
+ dimension: this.dimension
48
+ };
49
+ }
50
+ };
51
+ function cosineSimilarityF32(a, b) {
52
+ const len = Math.min(a.length, b.length);
53
+ let dot = 0;
54
+ let magA = 0;
55
+ let magB = 0;
56
+ for (let i = 0; i < len; i++) {
57
+ const ai = a[i];
58
+ const bi = b[i];
59
+ dot += ai * bi;
60
+ magA += ai * ai;
61
+ magB += bi * bi;
62
+ }
63
+ const denom = Math.sqrt(magA) * Math.sqrt(magB);
64
+ return denom === 0 ? 0 : dot / denom;
65
+ }
66
+ function embeddingToBlob(embedding) {
67
+ const f32 = Float32Array.from(embedding);
68
+ return new Uint8Array(f32.buffer, f32.byteOffset, f32.byteLength);
69
+ }
70
+ function decodeStoredEmbedding(row) {
71
+ const blob = row.embedding_blob;
72
+ if (blob != null) {
73
+ if (Array.isArray(blob)) return new Float32Array(new Uint8Array(blob).buffer);
74
+ if (blob instanceof ArrayBuffer) return new Float32Array(blob.slice(0));
75
+ const view = new Float32Array(blob.buffer, blob.byteOffset, blob.byteLength / Float32Array.BYTES_PER_ELEMENT);
76
+ return new Float32Array(view);
77
+ }
78
+ if (row.embedding != null && row.embedding !== "") return Float32Array.from(JSON.parse(row.embedding));
79
+ return new Float32Array(0);
80
+ }
81
+
82
+ //#endregion
83
+ export { D1VectorBackend };
84
+ //# sourceMappingURL=d1-vector-backend.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1-vector-backend.mjs","names":[],"sources":["../../src/backend/d1-vector-backend.ts"],"sourcesContent":["/**\n * D1VectorBackend — VectorBackend backed by Cloudflare D1.\n *\n * Stores embeddings in the `idx_embedding_vector` table (shared with the rest\n * of the D1 index schema) and performs JS-side cosine similarity after pulling\n * the candidate set for the caller's scope.\n *\n * Scope-partitioning is enforced by the `scope` column on every row; one\n * D1VectorBackend instance is bound to exactly one scope.\n */\nimport type { SqliteDatabase } from \"@aigne/afs-sqlite\";\nimport { sql } from \"drizzle-orm\";\nimport type { VectorResult } from \"../types.js\";\nimport type { VectorBackend, VectorFilter } from \"./vector-backend.js\";\n\nexport class D1VectorBackend implements VectorBackend {\n private dimension = 0;\n\n constructor(\n private readonly db: SqliteDatabase,\n private readonly scope: string,\n ) {}\n\n // `_domain` is ignored: this backend joins `ds_indexed_entry` (which stores\n // domain) at query time, so it needs no domain copy on the vector row.\n async upsert(entryPath: string, vector: number[], _domain?: string): Promise<void> {\n const blob = embeddingToBlob(vector);\n // Single atomic upsert, not DELETE-then-INSERT: two non-atomic statements\n // let concurrent upsert() calls for the same (scope, entry_path) interleave\n // — both DELETEs succeed, then the second INSERT collides on the PRIMARY KEY\n // (observed on production D1: SQLITE_CONSTRAINT_PRIMARYKEY, silently\n // dropping that embedding write).\n await this.db.run(\n sql`INSERT INTO idx_embedding_vector (scope, entry_path, embedding, embedding_blob, dimension) VALUES (${this.scope}, ${entryPath}, '', ${blob}, ${vector.length})\n ON CONFLICT(scope, entry_path) DO UPDATE SET\n embedding = excluded.embedding,\n embedding_blob = excluded.embedding_blob,\n dimension = excluded.dimension`,\n );\n if (this.dimension === 0 && vector.length > 0) {\n this.dimension = vector.length;\n }\n }\n\n async query(vector: number[], filter: VectorFilter, topK?: number): Promise<VectorResult[]> {\n const conditions: ReturnType<typeof sql>[] = [sql`idx_embedding_vector.scope = ${this.scope}`];\n if (filter.domain) conditions.push(sql`ds_indexed_entry.domain = ${filter.domain}`);\n if (filter.domains && filter.domains.length > 0) {\n const ps = filter.domains.map((d) => sql`${d}`);\n conditions.push(sql`ds_indexed_entry.domain IN (${sql.join(ps, sql`, `)})`);\n }\n // Subtree scope: prune the cosine candidate set BEFORE the O(N) similarity\n // pass over the `(scope, entry_path)` index. See QueryOpts.pathPrefix.\n if (filter.pathPrefix) {\n conditions.push(sql`idx_embedding_vector.entry_path LIKE ${`${filter.pathPrefix}%`}`);\n }\n const where = sql`WHERE ${sql.join(conditions, sql` AND `)}`;\n\n const rows = await this.db.all<{\n entry_path: string;\n embedding: string | null;\n embedding_blob: ArrayBuffer | Uint8Array | number[] | null;\n }>(\n sql`SELECT idx_embedding_vector.entry_path, idx_embedding_vector.embedding, idx_embedding_vector.embedding_blob\n FROM idx_embedding_vector\n JOIN ds_indexed_entry ON ds_indexed_entry.scope = idx_embedding_vector.scope AND ds_indexed_entry.entry_path = idx_embedding_vector.entry_path\n ${where}`,\n );\n\n const queryVec = Float32Array.from(vector);\n const results: VectorResult[] = rows.map((r) => ({\n entryPath: r.entry_path,\n similarity: cosineSimilarityF32(queryVec, decodeStoredEmbedding(r)),\n }));\n\n results.sort((a, b) => b.similarity - a.similarity);\n if (topK != null) return results.slice(0, topK);\n return results;\n }\n\n async delete(entryPath: string): Promise<void> {\n await this.db.run(\n sql`DELETE FROM idx_embedding_vector WHERE scope = ${this.scope} AND entry_path = ${entryPath}`,\n );\n }\n\n capability(): { supported: boolean; dimension: number } | null {\n return { supported: true, dimension: this.dimension };\n }\n}\n\nfunction cosineSimilarityF32(a: Float32Array, b: Float32Array): number {\n const len = Math.min(a.length, b.length);\n let dot = 0;\n let magA = 0;\n let magB = 0;\n for (let i = 0; i < len; i++) {\n const ai = a[i]!;\n const bi = b[i]!;\n dot += ai * bi;\n magA += ai * ai;\n magB += bi * bi;\n }\n const denom = Math.sqrt(magA) * Math.sqrt(magB);\n return denom === 0 ? 0 : dot / denom;\n}\n\nfunction embeddingToBlob(embedding: number[]): Uint8Array {\n const f32 = Float32Array.from(embedding);\n return new Uint8Array(f32.buffer, f32.byteOffset, f32.byteLength);\n}\n\nfunction decodeStoredEmbedding(row: {\n embedding: string | null;\n embedding_blob: ArrayBuffer | Uint8Array | number[] | null;\n}): Float32Array {\n const blob = row.embedding_blob;\n if (blob != null) {\n // D1 returns BLOB columns as number[]; DO/libsql return ArrayBuffer/Uint8Array.\n if (Array.isArray(blob)) {\n return new Float32Array(new Uint8Array(blob).buffer);\n }\n if (blob instanceof ArrayBuffer) {\n return new Float32Array(blob.slice(0));\n }\n const view = new Float32Array(\n blob.buffer,\n blob.byteOffset,\n blob.byteLength / Float32Array.BYTES_PER_ELEMENT,\n );\n return new Float32Array(view);\n }\n if (row.embedding != null && row.embedding !== \"\") {\n return Float32Array.from(JSON.parse(row.embedding) as number[]);\n }\n return new Float32Array(0);\n}\n"],"mappings":";;;AAeA,IAAa,kBAAb,MAAsD;CACpD,AAAQ,YAAY;CAEpB,YACE,AAAiB,IACjB,AAAiB,OACjB;EAFiB;EACA;;CAKnB,MAAM,OAAO,WAAmB,QAAkB,SAAiC;EACjF,MAAM,OAAO,gBAAgB,OAAO;AAMpC,QAAM,KAAK,GAAG,IACZ,GAAG,sGAAsG,KAAK,MAAM,IAAI,UAAU,QAAQ,KAAK,IAAI,OAAO,OAAO;;;;4CAKlK;AACD,MAAI,KAAK,cAAc,KAAK,OAAO,SAAS,EAC1C,MAAK,YAAY,OAAO;;CAI5B,MAAM,MAAM,QAAkB,QAAsB,MAAwC;EAC1F,MAAM,aAAuC,CAAC,GAAG,gCAAgC,KAAK,QAAQ;AAC9F,MAAI,OAAO,OAAQ,YAAW,KAAK,GAAG,6BAA6B,OAAO,SAAS;AACnF,MAAI,OAAO,WAAW,OAAO,QAAQ,SAAS,GAAG;GAC/C,MAAM,KAAK,OAAO,QAAQ,KAAK,MAAM,GAAG,GAAG,IAAI;AAC/C,cAAW,KAAK,GAAG,+BAA+B,IAAI,KAAK,IAAI,GAAG,KAAK,CAAC,GAAG;;AAI7E,MAAI,OAAO,WACT,YAAW,KAAK,GAAG,wCAAwC,GAAG,OAAO,WAAW,KAAK;EAEvF,MAAM,QAAQ,GAAG,SAAS,IAAI,KAAK,YAAY,GAAG,QAAQ;EAE1D,MAAM,OAAO,MAAM,KAAK,GAAG,IAKzB,GAAG;;;YAGG,QACP;EAED,MAAM,WAAW,aAAa,KAAK,OAAO;EAC1C,MAAM,UAA0B,KAAK,KAAK,OAAO;GAC/C,WAAW,EAAE;GACb,YAAY,oBAAoB,UAAU,sBAAsB,EAAE,CAAC;GACpE,EAAE;AAEH,UAAQ,MAAM,GAAG,MAAM,EAAE,aAAa,EAAE,WAAW;AACnD,MAAI,QAAQ,KAAM,QAAO,QAAQ,MAAM,GAAG,KAAK;AAC/C,SAAO;;CAGT,MAAM,OAAO,WAAkC;AAC7C,QAAM,KAAK,GAAG,IACZ,GAAG,kDAAkD,KAAK,MAAM,oBAAoB,YACrF;;CAGH,aAA+D;AAC7D,SAAO;GAAE,WAAW;GAAM,WAAW,KAAK;GAAW;;;AAIzD,SAAS,oBAAoB,GAAiB,GAAyB;CACrE,MAAM,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,OAAO;CACxC,IAAI,MAAM;CACV,IAAI,OAAO;CACX,IAAI,OAAO;AACX,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,KAAK;EAC5B,MAAM,KAAK,EAAE;EACb,MAAM,KAAK,EAAE;AACb,SAAO,KAAK;AACZ,UAAQ,KAAK;AACb,UAAQ,KAAK;;CAEf,MAAM,QAAQ,KAAK,KAAK,KAAK,GAAG,KAAK,KAAK,KAAK;AAC/C,QAAO,UAAU,IAAI,IAAI,MAAM;;AAGjC,SAAS,gBAAgB,WAAiC;CACxD,MAAM,MAAM,aAAa,KAAK,UAAU;AACxC,QAAO,IAAI,WAAW,IAAI,QAAQ,IAAI,YAAY,IAAI,WAAW;;AAGnE,SAAS,sBAAsB,KAGd;CACf,MAAM,OAAO,IAAI;AACjB,KAAI,QAAQ,MAAM;AAEhB,MAAI,MAAM,QAAQ,KAAK,CACrB,QAAO,IAAI,aAAa,IAAI,WAAW,KAAK,CAAC,OAAO;AAEtD,MAAI,gBAAgB,YAClB,QAAO,IAAI,aAAa,KAAK,MAAM,EAAE,CAAC;EAExC,MAAM,OAAO,IAAI,aACf,KAAK,QACL,KAAK,YACL,KAAK,aAAa,aAAa,kBAChC;AACD,SAAO,IAAI,aAAa,KAAK;;AAE/B,KAAI,IAAI,aAAa,QAAQ,IAAI,cAAc,GAC7C,QAAO,aAAa,KAAK,KAAK,MAAM,IAAI,UAAU,CAAa;AAEjE,QAAO,IAAI,aAAa,EAAE"}