@fgv/ts-agent-memory-sqlite-vec 5.1.0-50 → 5.1.0-52

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 (25) hide show
  1. package/README.md +49 -1
  2. package/dist/packlets/sqlite-vec-index/connection.js +54 -0
  3. package/dist/packlets/sqlite-vec-index/connection.js.map +1 -0
  4. package/dist/packlets/sqlite-vec-index/model.js.map +1 -1
  5. package/dist/packlets/sqlite-vec-index/sqliteVecFragmentIndex.js +162 -8
  6. package/dist/packlets/sqlite-vec-index/sqliteVecFragmentIndex.js.map +1 -1
  7. package/dist/packlets/sqlite-vec-index/sqliteVecVectorIndex.js +115 -4
  8. package/dist/packlets/sqlite-vec-index/sqliteVecVectorIndex.js.map +1 -1
  9. package/dist/ts-agent-memory-sqlite-vec.d.ts +258 -10
  10. package/lib/packlets/sqlite-vec-index/connection.d.ts +42 -0
  11. package/lib/packlets/sqlite-vec-index/connection.d.ts.map +1 -0
  12. package/lib/packlets/sqlite-vec-index/connection.js +91 -0
  13. package/lib/packlets/sqlite-vec-index/connection.js.map +1 -0
  14. package/lib/packlets/sqlite-vec-index/model.d.ts +92 -0
  15. package/lib/packlets/sqlite-vec-index/model.d.ts.map +1 -1
  16. package/lib/packlets/sqlite-vec-index/model.js.map +1 -1
  17. package/lib/packlets/sqlite-vec-index/sqliteVecFragmentIndex.d.ts +89 -8
  18. package/lib/packlets/sqlite-vec-index/sqliteVecFragmentIndex.d.ts.map +1 -1
  19. package/lib/packlets/sqlite-vec-index/sqliteVecFragmentIndex.js +162 -8
  20. package/lib/packlets/sqlite-vec-index/sqliteVecFragmentIndex.js.map +1 -1
  21. package/lib/packlets/sqlite-vec-index/sqliteVecVectorIndex.d.ts +78 -5
  22. package/lib/packlets/sqlite-vec-index/sqliteVecVectorIndex.d.ts.map +1 -1
  23. package/lib/packlets/sqlite-vec-index/sqliteVecVectorIndex.js +115 -4
  24. package/lib/packlets/sqlite-vec-index/sqliteVecVectorIndex.js.map +1 -1
  25. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"sqliteVecFragmentIndex.js","sourceRoot":"","sources":["../../../src/packlets/sqlite-vec-index/sqliteVecFragmentIndex.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { load as loadSqliteVec } from 'sqlite-vec';\nimport {\n DetailedResult,\n Result,\n captureResult,\n fail,\n failWithDetail,\n succeed,\n succeedWithDetail\n} from '@fgv/ts-utils';\nimport {\n FragmentEmbedder,\n IEdgeTarget,\n IEmbeddedFragment,\n IFragmentLocator,\n IFragmentVectorIndex,\n IFragmentVectorRebuildReport,\n IMemoryRecordListing,\n IMemoryRecordSource,\n ISkippedVectorRecord,\n IVectorQueryHit,\n IVectorRebuildOptions,\n Kind,\n MemoryId,\n MemoryScopeKey,\n edgeTargetKey\n} from '@fgv/ts-agent-memory';\nimport { invokeHook, tally, withRollbackNote } from './rebuildHelpers';\nimport { ISqliteVecFragmentIndexCreateParams } from './model';\n\n/** Default name for the fragment `vec0` virtual table. */\nconst DEFAULT_TABLE_NAME: string = 'memory_fragments';\n\n/** A simple SQL identifier — the only shape allowed for the table name (it is interpolated into DDL). */\nconst IDENTIFIER_RE: RegExp = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * The auxiliary (`+`-prefixed) columns this version of the index writes. A table\n * created by an earlier version carries a different set; see\n * {@link SqliteVecFragmentIndex._readExistingDimension} for why that has to be\n * detected explicitly rather than migrated.\n */\nconst AUXILIARY_COLUMNS: ReadonlyArray<string> = ['start_off', 'end_off', 'fragment_id'];\n\n/**\n * Matches one `+name` auxiliary-column declaration in a `vec0` `CREATE VIRTUAL TABLE`\n * statement. Only ever consumed via `String.matchAll`, which iterates a clone rather\n * than advancing this instance's `lastIndex`, so the shared `/g` regex is reusable.\n */\nconst AUXILIARY_COLUMN_RE: RegExp = /\\+\\s*([A-Za-z_][A-Za-z0-9_]*)/g;\n\n/**\n * One KNN row as returned by the fragment `vec0` MATCH query. The offset columns are\n * typed `number | bigint` because `better-sqlite3` returns integer columns as\n * `bigint` when a consumer enables its safe-integer mode (`defaultSafeIntegers`);\n * {@link SqliteVecFragmentIndex._toOffset} coerces them to a plain `number` (and\n * fails loudly on an out-of-safe-range value) before they reach the public locator.\n * All three identity columns are nullable: a fragment stored without a locator has\n * `NULL` offsets, and one stored without a `fragmentId` has a `NULL` `fragment_id`.\n */\ninterface IKnnRow {\n readonly target_key: string;\n // eslint-disable-next-line @rushstack/no-new-null -- SQLite returns NULL (not undefined) for an absent locator offset\n readonly start_off: number | bigint | null;\n // eslint-disable-next-line @rushstack/no-new-null -- SQLite returns NULL (not undefined) for an absent locator offset\n readonly end_off: number | bigint | null;\n // eslint-disable-next-line @rushstack/no-new-null -- SQLite returns NULL (not undefined) for an absent fragment id\n readonly fragment_id: string | null;\n readonly distance: number;\n}\n\n/**\n * The identity fields of a fragment hit, in `IVectorQueryHit` shape: a field the\n * stored fragment did not carry is *absent*, never present-but-`undefined`, so a hit\n * for a fragment stored without a `fragmentId` is structurally identical to one this\n * index produced before `fragment_id` existed.\n */\ntype FragmentIdentity = Pick<IVectorQueryHit, 'locator' | 'fragmentId'>;\n\n/**\n * A persistent, `sqlite-vec`-backed `IFragmentVectorIndex` (from\n * `@fgv/ts-agent-memory`) — the fragment-granular sibling of\n * {@link SqliteVecVectorIndex}, and the **durable** counterpart to the in-memory\n * `InMemoryFragmentCosineIndex`.\n *\n * @remarks\n * Where {@link SqliteVecVectorIndex} keys one vector per record on a\n * `target_key` primary key, this index holds **many** vectors per record — one per\n * fragment — so it keys the `vec0` table on `target_key` as a **`PARTITION KEY`**\n * (many rows may share it) and stores each fragment's identity in three auxiliary\n * columns (`+start_off`, `+end_off`, `+fragment_id`) that ride alongside the vector\n * and are returned on query but never filtered — in particular `fragment_id` is\n * stored and returned verbatim, never parsed and never part of the query path. A\n * query is a brute-force `vec0` KNN scan across all partitions returning per-fragment\n * hits, each carrying its record `target` plus whichever identity fields the stored\n * fragment was added with (a fragment must carry at least one).\n *\n * **`vec0` schema changes require a drop-and-re-index.** A\n * `CREATE VIRTUAL TABLE IF NOT EXISTS` is a no-op against an existing table (SQLite\n * does not compare schemas) and `vec0` has no `ALTER TABLE ADD COLUMN`, so a database written by an\n * earlier version of this package keeps its old auxiliary columns. `create` detects\n * that by parsing the stored `CREATE VIRTUAL TABLE` SQL and fails with an actionable\n * message naming the expected and found columns, rather than letting a widened\n * `INSERT` surface an opaque `no such column` at statement-prepare time. There are no\n * in-place migrations: drop the table (or use a fresh `tableName`) and re-index.\n * Fragment vectors are re-derivable from the records, so this costs embedding time,\n * never data.\n *\n * Semantics match `InMemoryFragmentCosineIndex` exactly: `addFragments` is\n * whole-record-replace (a single transaction deletes every prior fragment of the\n * target, then inserts the new set), `remove` drops every fragment of a target,\n * and `query` applies the optional `maxPerRecord` cap **during selection, before\n * the topK cut** — so one long document cannot crowd others out. The dimension is\n * established by the first `addFragments` (the `vec0` column is fixed-width) and\n * recovered from the table schema when a persistent file is reopened; similarity is\n * cosine (`score = 1 - cosineDistance`), byte-identical to the in-memory index.\n * Large-N ANN indexing is explicitly out of scope, same regime as the record index.\n *\n * The `better-sqlite3` `Database` is consumer-owned (bring-your-own): this index\n * loads the `sqlite-vec` extension onto it and reads/writes the table, but never\n * opens or closes the connection.\n * @public\n */\nexport class SqliteVecFragmentIndex implements IFragmentVectorIndex {\n private readonly _db: BetterSqlite3.Database;\n private readonly _table: string;\n /** The dimension of every stored fragment vector; `undefined` until the table exists. */\n private _dimension: number | undefined;\n /** Prepared statements; created once the table exists (established or recovered). */\n private _stmts: IFragmentStatements | undefined;\n\n private constructor(db: BetterSqlite3.Database, table: string, dimension: number | undefined) {\n this._db = db;\n this._table = table;\n this._dimension = dimension;\n this._stmts = dimension === undefined ? undefined : this._prepare();\n }\n\n /** The number of records that currently have at least one stored fragment. Zero before the first add. */\n public get recordCount(): number {\n if (this._stmts === undefined) {\n return 0;\n }\n // `Number(...)` narrows the count in case the consumer enabled better-sqlite3\n // safe-integer mode (which returns `count(*)` as a `bigint`).\n return Number((this._stmts.recordCount.get() as { c: number | bigint }).c);\n }\n\n /** The total number of fragments currently held across all records. Zero before the first add. */\n public get fragmentCount(): number {\n if (this._stmts === undefined) {\n return 0;\n }\n return Number((this._stmts.fragmentCount.get() as { c: number | bigint }).c);\n }\n\n /**\n * Family-convention factory. Loads the `sqlite-vec` extension onto the supplied\n * `better-sqlite3` connection and, if the fragment table already exists (a\n * reopened persistent file), verifies its auxiliary-column set matches this\n * version's and recovers its established dimension so no re-embedding is needed on\n * open.\n *\n * @param params - See {@link ISqliteVecFragmentIndexCreateParams}.\n * @returns `Success` with the index, or `Failure` if the table name is not a\n * simple identifier, the extension fails to load, or the existing table was\n * written by a version with a different auxiliary-column set (which requires a\n * drop-and-re-index — `vec0` cannot be altered in place).\n */\n public static create(params: ISqliteVecFragmentIndexCreateParams): Promise<Result<SqliteVecFragmentIndex>> {\n const table: string = params.tableName ?? DEFAULT_TABLE_NAME;\n if (!IDENTIFIER_RE.test(table)) {\n return Promise.resolve(\n fail(`sqlite-vec fragment index: table name '${table}' is not a simple SQL identifier`)\n );\n }\n return Promise.resolve(\n captureResult(() => {\n loadSqliteVec(params.database);\n const dimension: number | undefined = SqliteVecFragmentIndex._readExistingDimension(\n params.database,\n table\n );\n return new SqliteVecFragmentIndex(params.database, table, dimension);\n }).withErrorFormat((e) => `sqlite-vec fragment index: failed to initialize: ${e}`)\n );\n }\n\n /** {@inheritDoc IFragmentVectorIndex.addFragments} */\n public addFragments(\n target: IEdgeTarget,\n fragments: ReadonlyArray<IEmbeddedFragment>\n ): Promise<Result<number>> {\n const key: string = edgeTargetKey(target);\n // Validate every fragment before touching the database, so a bad fragment never\n // leaves the record half-replaced or the dimension half-established (whole-record\n // replace is all-or-nothing). The effective dimension is the established one, or —\n // on a still-dimensionless index — the first fragment's length; it is committed\n // (via table creation) only once the whole batch validates.\n let dimension: number | undefined = this._dimension;\n for (const fragment of fragments) {\n if (fragment.vector.length === 0) {\n return Promise.resolve(fail(`fragment index: cannot add '${key}': empty fragment vector`));\n }\n // A fragment carrying neither identity cannot be resolved back to anything by a\n // consumer holding the hit — the same invariant `embeddedFragmentConverter`\n // enforces at the untyped boundary, re-checked here at the index seam.\n if (fragment.locator === undefined && fragment.fragmentId === undefined) {\n return Promise.resolve(\n fail(\n `fragment index: cannot add '${key}': fragment requires at least one of 'locator' or 'fragmentId'`\n )\n );\n }\n // Locator offsets are persisted as SQLite integers (bound via BigInt). Reject a\n // non-safe-integer offset up front with a clear message, rather than letting\n // `BigInt(nonInteger)` throw cryptically inside the write transaction OR storing\n // a value the read-side `_toOffset` guard would later reject on every query.\n // An absent locator persists as a NULL offset pair and skips the check.\n if (\n fragment.locator !== undefined &&\n (!Number.isSafeInteger(fragment.locator.start) || !Number.isSafeInteger(fragment.locator.end))\n ) {\n return Promise.resolve(\n fail(\n `fragment index: cannot add '${key}': locator [${fragment.locator.start}, ${fragment.locator.end}) offsets must be safe integers`\n )\n );\n }\n if (dimension === undefined) {\n dimension = fragment.vector.length;\n } else if (fragment.vector.length !== dimension) {\n return Promise.resolve(\n fail(\n `fragment index: cannot add '${key}': fragment dimension ${fragment.vector.length} does not match index dimension ${dimension}`\n )\n );\n }\n }\n return Promise.resolve(\n captureResult(() => {\n // A same-target re-author (or an empty batch) still needs the table to exist\n // to delete prior fragments; create it lazily on the first non-empty add.\n if (this._stmts === undefined) {\n if (fragments.length === 0) {\n // Nothing stored yet and nothing to store: no table, no work.\n return 0;\n }\n // `fragments` is non-empty here (the empty case returned above), so the\n // validation loop proved every fragment shares `fragments[0]`'s length —\n // which IS the dimension to establish. Read it straight from the first\n // fragment: no cast, no invariant-dependent narrowing.\n const established: number = fragments[0].vector.length;\n this._createTable(established);\n this._dimension = established;\n this._stmts = this._prepare();\n }\n this._stmts.replace(key, fragments);\n return fragments.length;\n }).withErrorFormat((e) => `fragment index: cannot add '${key}': ${e}`)\n );\n }\n\n /** {@inheritDoc IFragmentVectorIndex.remove} */\n public remove(target: IEdgeTarget): Promise<Result<IEdgeTarget>> {\n return Promise.resolve(\n captureResult(() => {\n // Idempotent: removing a target with no fragments (or before any add created\n // the table) still succeeds.\n if (this._stmts !== undefined) {\n this._stmts.deleteByTarget.run(edgeTargetKey(target));\n }\n return target;\n }).withErrorFormat((e) => `fragment index: cannot remove '${edgeTargetKey(target)}': ${e}`)\n );\n }\n\n /** {@inheritDoc IFragmentVectorIndex.has} */\n public has(target: IEdgeTarget): Promise<Result<boolean>> {\n return Promise.resolve(\n captureResult(() => {\n // Before any add has created the table there is nothing held — a truthful\n // `false`, matching `remove`'s idempotence and the zero counts.\n if (this._stmts === undefined) {\n return false;\n }\n return this._stmts.has.get(edgeTargetKey(target)) !== undefined;\n }).withErrorFormat((e) => `fragment index: cannot check '${edgeTargetKey(target)}': ${e}`)\n );\n }\n\n /** {@inheritDoc IFragmentVectorIndex.rebuild} */\n public async rebuild(\n source: IMemoryRecordSource,\n embed: FragmentEmbedder,\n options?: IVectorRebuildOptions\n ): Promise<DetailedResult<IFragmentVectorRebuildReport, IFragmentVectorRebuildReport>> {\n const lenient: boolean = (options?.onRecordError ?? 'fail') === 'skip';\n // `source` is consumer-supplied, so a throw or rejection becomes a `Failure`\n // here rather than escaping as an exception.\n const listed: Result<IMemoryRecordListing> = await invokeHook(() => source.list());\n if (listed.isFailure()) {\n // Deliberately BEFORE any clear, matching both siblings: a failed list is no\n // evidence about the fragments already held, and clearing here would destroy\n // a healthy PERSISTED index over a transient read error. No detail — there is\n // nothing this call disturbed to describe.\n return failWithDetail(`fragment index rebuild: failed to list records: ${listed.message}`);\n }\n const cleared: Result<true> = this._clear();\n if (cleared.isFailure()) {\n // Also nothing established: the table still holds whatever it held.\n return failWithDetail(`fragment index rebuild: failed to clear the index: ${cleared.message}`);\n }\n const indexed: Map<Kind, number> = new Map<Kind, number>();\n const fragments: Map<Kind, number> = new Map<Kind, number>();\n const declined: Map<Kind, number> = new Map<Kind, number>();\n const skipped: ISkippedVectorRecord[] = [];\n // Absent stays absent — only the source knows whether it filtered anything.\n const report = (): IFragmentVectorRebuildReport => ({\n indexed,\n fragments,\n declined,\n excluded: listed.value.excluded,\n skipped\n });\n for (const scoped of listed.value.records) {\n const kind: Kind = scoped.record.envelope.kind;\n // Capture-wrapped: an embedder that throws mid-loop would otherwise escape\n // past the `'fail'` rollback below, leaving this DURABLE table holding a\n // partial index that survives the process.\n const embedded: Result<ReadonlyArray<IEmbeddedFragment>> = await invokeHook(() => embed(scoped.record));\n if (embedded.isFailure()) {\n const error: string = `fragment index rebuild: embedding '${edgeTargetKey(scoped.target)}' failed: ${\n embedded.message\n }`;\n if (!lenient) {\n // A rollback that also fails is said out loud: the `'fail'` path\n // promises an empty index, and on a DURABLE table a botched rollback\n // survives the process.\n return failWithDetail(withRollbackNote(error, this._clear()), report());\n }\n skipped.push({ target: scoped.target, error });\n continue;\n }\n // An empty array is this lane's decline, and it is still WRITTEN — the\n // whole-record-replace is what clears any stale fragments.\n const added: Result<number> = await this.addFragments(scoped.target, embedded.value);\n if (added.isFailure()) {\n const error: string = `fragment index rebuild: ${added.message}`;\n if (!lenient) {\n return failWithDetail(withRollbackNote(error, this._clear()), report());\n }\n skipped.push({ target: scoped.target, error });\n continue;\n }\n if (added.value === 0) {\n tally(declined, kind);\n continue;\n }\n tally(indexed, kind);\n tally(fragments, kind, added.value);\n }\n return succeedWithDetail(report());\n }\n\n /**\n * **Empties the rows; does NOT release the table's declared dimension.** That\n * is a `vec0` constraint rather than a choice — the dimension is schema, and\n * there is no `ALTER TABLE` for it — so a rebuild at a new dimension fails\n * here where it would succeed on the in-memory sibling, which forgets its\n * dimension on reset. Changing dimension needs a drop-and-re-index; see the\n * note on `IVectorIndex.rebuild`. Tolerates a table that does not exist yet.\n */\n private _clear(): Result<true> {\n if (this._stmts === undefined) {\n return succeed(true);\n }\n // Capture-wrapped like every other statement path: a closed connection or an\n // I/O error is a `Failure`, not an exception out of a `Result`-returning method.\n return captureResult(() => this._db.prepare(`DELETE FROM \"${this._table}\"`).run()).onSuccess(() =>\n succeed(true)\n );\n }\n\n /** {@inheritDoc IFragmentVectorIndex.query} */\n public query(\n vector: Float32Array,\n topK: number,\n maxPerRecord?: number\n ): Promise<Result<ReadonlyArray<IVectorQueryHit>>> {\n if (topK <= 0 || this._stmts === undefined) {\n return Promise.resolve(succeed([]));\n }\n if (vector.length !== this._dimension) {\n return Promise.resolve(\n fail(\n `fragment index: query dimension ${vector.length} does not match index dimension ${this._dimension}`\n )\n );\n }\n const stmts: IFragmentStatements = this._stmts;\n return Promise.resolve(\n captureResult<ReadonlyArray<IVectorQueryHit>>(() => {\n // With a per-record cap the topK winners may lie past the first topK rows (a\n // capped record's later fragments are skipped), so fetch the full ranked set\n // and apply the cap + topK cut here — exactly as the in-memory index does.\n // Uncapped, KNN's own `k = topK` is already the answer.\n const fetchK: number =\n maxPerRecord === undefined ? topK : Number((stmts.fragmentCount.get() as { c: number | bigint }).c);\n if (fetchK <= 0) {\n return [];\n }\n const rows: ReadonlyArray<IKnnRow> = stmts.query.all(\n SqliteVecFragmentIndex._toBlob(vector),\n fetchK\n ) as ReadonlyArray<IKnnRow>;\n // sqlite-vec returns rows ascending by distance (nearest first); score is\n // `1 - cosineDistance`, so this order is already descending score.\n const hits: IVectorQueryHit[] = [];\n const perRecord: Map<string, number> = new Map<string, number>();\n for (const row of rows) {\n if (hits.length >= topK) {\n break;\n }\n if (maxPerRecord !== undefined) {\n const used: number = perRecord.get(row.target_key) ?? 0;\n if (used >= maxPerRecord) {\n continue;\n }\n perRecord.set(row.target_key, used + 1);\n }\n const key: string = row.target_key;\n hits.push({\n target: SqliteVecFragmentIndex._parseKey(key),\n score: 1 - row.distance,\n ...SqliteVecFragmentIndex._toIdentity(row, key)\n });\n }\n return hits;\n }).withErrorFormat((e) => `fragment index: query failed: ${e}`)\n );\n }\n\n /**\n * Create the fragment `vec0` virtual table with the established dimension. The\n * auxiliary columns must stay in sync with `AUXILIARY_COLUMNS`, which\n * `create` compares against an existing table's stored DDL.\n */\n private _createTable(dimension: number): void {\n this._db.exec(\n `CREATE VIRTUAL TABLE IF NOT EXISTS \"${this._table}\" USING vec0(` +\n `target_key TEXT PARTITION KEY, embedding float[${dimension}] distance_metric=cosine, ` +\n `+start_off integer, +end_off integer, +fragment_id text)`\n );\n }\n\n /** Prepare the statements the index reuses. Requires the table to exist. */\n private _prepare(): IFragmentStatements {\n const del: BetterSqlite3.Statement = this._db.prepare(\n `DELETE FROM \"${this._table}\" WHERE target_key = ?`\n );\n const ins: BetterSqlite3.Statement = this._db.prepare(\n `INSERT INTO \"${this._table}\"(target_key, embedding, start_off, end_off, fragment_id) ` +\n `VALUES (?, ?, ?, ?, ?)`\n );\n // Whole-record replace: drop every prior fragment of the target, then insert the\n // new set, atomically. An empty set collapses to a pure delete.\n const replaceTxn: BetterSqlite3.Transaction<\n (key: string, fragments: ReadonlyArray<IEmbeddedFragment>) => void\n > = this._db.transaction((key: string, fragments: ReadonlyArray<IEmbeddedFragment>) => {\n del.run(key);\n for (const fragment of fragments) {\n ins.run(\n key,\n SqliteVecFragmentIndex._toBlob(fragment.vector),\n // vec0 typed columns reject a JS float; bind the offsets as integers. An\n // absent locator binds the pair as NULL — never a partial pair, so the read\n // side can treat a half-NULL pair as corruption rather than a legal shape.\n fragment.locator === undefined ? null : BigInt(fragment.locator.start),\n fragment.locator === undefined ? null : BigInt(fragment.locator.end),\n // Stored verbatim and never parsed; absent binds as NULL.\n fragment.fragmentId ?? null\n );\n }\n });\n return {\n deleteByTarget: del,\n replace: (key: string, fragments: ReadonlyArray<IEmbeddedFragment>): void => {\n replaceTxn(key, fragments);\n },\n query: this._db.prepare(\n `SELECT target_key, start_off, end_off, fragment_id, distance FROM \"${this._table}\" ` +\n `WHERE embedding MATCH ? AND k = ?`\n ),\n fragmentCount: this._db.prepare(`SELECT count(*) AS c FROM \"${this._table}\"`),\n recordCount: this._db.prepare(`SELECT count(DISTINCT target_key) AS c FROM \"${this._table}\"`),\n // `LIMIT 1`: membership needs existence, not cardinality.\n has: this._db.prepare(`SELECT 1 FROM \"${this._table}\" WHERE target_key = ? LIMIT 1`)\n };\n }\n\n /**\n * Recover the established dimension of an existing fragment `vec0` table from its\n * stored `CREATE VIRTUAL TABLE` SQL (`float[<n>]`), after checking that the table's\n * auxiliary columns match `AUXILIARY_COLUMNS`. Returns `undefined` when the\n * table does not exist yet (a fresh database — dimension is set by the first add).\n *\n * Throws when a table of that name exists but is not a usable fragment index (a\n * mismatched auxiliary-column set, or no `vec0` embedding column); the caller runs\n * this inside `captureResult`, so it surfaces as a loud `Failure` from `create`.\n * The same stored DDL answers every one of those questions, so the checks cost\n * nothing extra.\n */\n private static _readExistingDimension(db: BetterSqlite3.Database, table: string): number | undefined {\n const row: { sql: string } | undefined = db\n .prepare(\"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?\")\n .get(table) as { sql: string } | undefined;\n if (row === undefined) {\n return undefined;\n }\n SqliteVecFragmentIndex._verifyAuxiliaryColumns(row.sql, table);\n const match: RegExpMatchArray | null = row.sql.match(/float\\[(\\d+)\\]/);\n if (match === null) {\n // The auxiliary columns matched but there is no `float[<n>]` embedding column,\n // so this is not a usable fragment index table. Same remedy as a column\n // mismatch — and failing here beats handing back a dimensionless index whose\n // first add would `CREATE VIRTUAL TABLE IF NOT EXISTS` into a no-op.\n throw new Error(\n `existing table '${table}' has no vec0 embedding column, so it is not a usable fragment ` +\n `index table. Drop it (or pass a fresh tableName) and re-add every fragment.`\n );\n }\n return Number(match[1]);\n }\n\n /**\n * Compare an existing table's auxiliary columns against `AUXILIARY_COLUMNS`.\n *\n * `CREATE VIRTUAL TABLE IF NOT EXISTS` is a no-op against an existing table (SQLite\n * never compares schemas) and `vec0` has no `ALTER TABLE ADD COLUMN`, so a table\n * written by an earlier version of this package silently keeps its old columns and\n * only fails later — as an opaque `no such column` when the widened `INSERT` is\n * prepared. Detect it here instead and say what to do about it. Order is not\n * compared: every statement names its columns explicitly, so only the set matters.\n */\n private static _verifyAuxiliaryColumns(sql: string, table: string): void {\n const found: string[] = Array.from(sql.matchAll(AUXILIARY_COLUMN_RE), (m) => m[1]);\n const expected: ReadonlyArray<string> = AUXILIARY_COLUMNS;\n const matches: boolean =\n found.length === expected.length && expected.every((column) => found.includes(column));\n if (!matches) {\n throw new Error(\n `existing table '${table}' has auxiliary columns [${found.join(', ')}] but this index ` +\n `requires [${expected.join(', ')}] — it was written by a different version of ` +\n `@fgv/ts-agent-memory-sqlite-vec, or it is not a fragment index table at all. vec0 virtual ` +\n `tables cannot be altered in place, so this requires a drop-and-re-index: DROP TABLE ` +\n `\"${table}\" (or pass a fresh tableName) and re-add every fragment. Fragment vectors are ` +\n `re-derivable from the records, so this costs embedding time, never data.`\n );\n }\n }\n\n /**\n * Rebuild the identity fields of a hit from a persisted row, omitting each field\n * the stored fragment did not carry (so a hit is structurally identical to one this\n * index produced before `fragment_id` existed).\n *\n * A row carrying neither identity violates the write-side invariant and could not\n * be resolved by the caller, so it fails loudly instead of yielding an anonymous\n * hit.\n */\n private static _toIdentity(row: IKnnRow, key: string): FragmentIdentity {\n const locator: IFragmentLocator | undefined = SqliteVecFragmentIndex._toLocator(row, key);\n if (locator === undefined && row.fragment_id === null) {\n throw new Error(\n `fragment '${key}': row carries neither a locator nor a fragment id (corrupt persisted data)`\n );\n }\n return {\n ...(locator !== undefined ? { locator } : {}),\n ...(row.fragment_id !== null ? { fragmentId: row.fragment_id } : {})\n };\n }\n\n /**\n * Rebuild a fragment's locator from its persisted offsets, or `undefined` when the\n * fragment was stored without one (both offsets `NULL`).\n *\n * The pair is written all-or-nothing, so a half-`NULL` pair can only come from\n * corrupt / externally-edited data. Throw rather than coerce — `Number(null)` is\n * `0`, which would silently fabricate a span starting at the top of the body.\n */\n private static _toLocator(row: IKnnRow, key: string): IFragmentLocator | undefined {\n const start: number | bigint | null = row.start_off;\n const end: number | bigint | null = row.end_off;\n if (start === null && end === null) {\n return undefined;\n }\n if (start === null || end === null) {\n throw new Error(\n `fragment '${key}': locator has only one of its start/end offsets (corrupt persisted data)`\n );\n }\n return {\n start: SqliteVecFragmentIndex._toOffset(start, key),\n end: SqliteVecFragmentIndex._toOffset(end, key)\n };\n }\n\n /** Pack a `Float32Array` as the little-endian byte blob `vec0` stores. Copies, so the caller may reuse its buffer. */\n private static _toBlob(vector: Float32Array): Uint8Array {\n return new Uint8Array(Float32Array.from(vector).buffer);\n }\n\n /**\n * Reverse `edgeTargetKey` — the canonical key is `scope\\0id` with NUL excluded\n * from both components, so the first NUL splits it unambiguously. A key with no\n * NUL cannot have been written by `edgeTargetKey`; rather than fabricate a wrong\n * `(scope, id)` from corrupt / externally-edited table data, throw so the query\n * surfaces it as a loud `Failure`.\n */\n private static _parseKey(key: string): IEdgeTarget {\n const nul: number = key.indexOf('\\0');\n if (nul < 0) {\n throw new Error(`malformed target key '${key}': missing scope/id separator (corrupt persisted data)`);\n }\n return {\n scope: key.slice(0, nul) as unknown as MemoryScopeKey,\n id: key.slice(nul + 1) as unknown as MemoryId\n };\n }\n\n /**\n * Coerce a persisted locator offset to a plain `number`. `better-sqlite3` returns\n * integer columns as `bigint` under safe-integer mode, so an offset can arrive as\n * either; both narrow to `number` here. A value outside the safe-integer range\n * (only reachable via corrupt / externally-edited data — the index only ever\n * writes in-document offsets) throws rather than silently losing precision, so the\n * query surfaces it as a loud `Failure`.\n */\n private static _toOffset(value: number | bigint, key: string): number {\n const n: number = Number(value);\n if (!Number.isSafeInteger(n)) {\n throw new Error(\n `fragment '${key}': locator offset ${String(value)} is not a safe integer (corrupt persisted data)`\n );\n }\n return n;\n }\n}\n\n/** The prepared statements / helpers the fragment index reuses once its table exists. */\ninterface IFragmentStatements {\n readonly deleteByTarget: BetterSqlite3.Statement;\n readonly replace: (key: string, fragments: ReadonlyArray<IEmbeddedFragment>) => void;\n readonly query: BetterSqlite3.Statement;\n readonly fragmentCount: BetterSqlite3.Statement;\n readonly recordCount: BetterSqlite3.Statement;\n readonly has: BetterSqlite3.Statement;\n}\n"]}
1
+ {"version":3,"file":"sqliteVecFragmentIndex.js","sourceRoot":"","sources":["../../../src/packlets/sqlite-vec-index/sqliteVecFragmentIndex.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { load as loadSqliteVec } from 'sqlite-vec';\nimport {\n DetailedResult,\n Result,\n captureResult,\n fail,\n failWithDetail,\n succeed,\n succeedWithDetail\n} from '@fgv/ts-utils';\nimport {\n FragmentEmbedder,\n IEdgeTarget,\n IEmbeddedFragment,\n IFragmentLocator,\n IFragmentVectorIndex,\n IFragmentVectorRebuildReport,\n IMemoryRecordListing,\n IMemoryRecordSource,\n ISkippedVectorRecord,\n IVectorQueryHit,\n IVectorRebuildOptions,\n Kind,\n IFragmentQueryOptions,\n MemoryId,\n MemoryScopeKey,\n edgeTargetKey\n} from '@fgv/ts-agent-memory';\nimport { invokeHook, tally, withRollbackNote } from './rebuildHelpers';\nimport { closeOwnedConnection, openOwnedConnection } from './connection';\nimport {\n ISqliteVecFragmentIndexCreateParams,\n ISqliteVecFragmentIndexHandle,\n ISqliteVecFragmentIndexOpenParams\n} from './model';\n\n/** Default name for the fragment `vec0` virtual table. */\nconst DEFAULT_TABLE_NAME: string = 'memory_fragments';\n\n/** Package-facing prefix for this class's failure messages. */\nconst LABEL: string = 'sqlite-vec fragment index';\n\n/** A simple SQL identifier — the only shape allowed for the table name (it is interpolated into DDL). */\nconst IDENTIFIER_RE: RegExp = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * The auxiliary (`+`-prefixed) columns this version of the index writes. A table\n * created by an earlier version carries a different set; see\n * {@link SqliteVecFragmentIndex._readExistingDimension} for why that has to be\n * detected explicitly rather than migrated.\n */\nconst AUXILIARY_COLUMNS: ReadonlyArray<string> = ['start_off', 'end_off', 'fragment_id'];\n\n/**\n * Matches one `+name` auxiliary-column declaration in a `vec0` `CREATE VIRTUAL TABLE`\n * statement. Only ever consumed via `String.matchAll`, which iterates a clone rather\n * than advancing this instance's `lastIndex`, so the shared `/g` regex is reusable.\n */\nconst AUXILIARY_COLUMN_RE: RegExp = /\\+\\s*([A-Za-z_][A-Za-z0-9_]*)/g;\n\n/**\n * One KNN row as returned by the fragment `vec0` MATCH query. The offset columns are\n * typed `number | bigint` because `better-sqlite3` returns integer columns as\n * `bigint` when a consumer enables its safe-integer mode (`defaultSafeIntegers`);\n * {@link SqliteVecFragmentIndex._toOffset} coerces them to a plain `number` (and\n * fails loudly on an out-of-safe-range value) before they reach the public locator.\n * All three identity columns are nullable: a fragment stored without a locator has\n * `NULL` offsets, and one stored without a `fragmentId` has a `NULL` `fragment_id`.\n */\ninterface IKnnRow {\n readonly target_key: string;\n // eslint-disable-next-line @rushstack/no-new-null -- SQLite returns NULL (not undefined) for an absent locator offset\n readonly start_off: number | bigint | null;\n // eslint-disable-next-line @rushstack/no-new-null -- SQLite returns NULL (not undefined) for an absent locator offset\n readonly end_off: number | bigint | null;\n // eslint-disable-next-line @rushstack/no-new-null -- SQLite returns NULL (not undefined) for an absent fragment id\n readonly fragment_id: string | null;\n readonly distance: number;\n}\n\n/**\n * The identity fields of a fragment hit, in `IVectorQueryHit` shape: a field the\n * stored fragment did not carry is *absent*, never present-but-`undefined`, so a hit\n * for a fragment stored without a `fragmentId` is structurally identical to one this\n * index produced before `fragment_id` existed.\n */\ntype FragmentIdentity = Pick<IVectorQueryHit, 'locator' | 'fragmentId'>;\n\n/**\n * A persistent, `sqlite-vec`-backed `IFragmentVectorIndex` (from\n * `@fgv/ts-agent-memory`) — the fragment-granular sibling of\n * {@link SqliteVecVectorIndex}, and the **durable** counterpart to the in-memory\n * `InMemoryFragmentCosineIndex`.\n *\n * @remarks\n * Where {@link SqliteVecVectorIndex} keys one vector per record on a\n * `target_key` primary key, this index holds **many** vectors per record — one per\n * fragment — so it keys the `vec0` table on `target_key` as a **`PARTITION KEY`**\n * (many rows may share it) and stores each fragment's identity in three auxiliary\n * columns (`+start_off`, `+end_off`, `+fragment_id`) that ride alongside the vector\n * and are returned on query but never filtered — in particular `fragment_id` is\n * stored and returned verbatim, never parsed and never part of the query path. A\n * query is a brute-force `vec0` KNN scan across all partitions returning per-fragment\n * hits, each carrying its record `target` plus whichever identity fields the stored\n * fragment was added with (a fragment must carry at least one).\n *\n * **`vec0` schema changes require a drop-and-re-index.** A\n * `CREATE VIRTUAL TABLE IF NOT EXISTS` is a no-op against an existing table (SQLite\n * does not compare schemas) and `vec0` has no `ALTER TABLE ADD COLUMN`, so a database written by an\n * earlier version of this package keeps its old auxiliary columns. `create` detects\n * that by parsing the stored `CREATE VIRTUAL TABLE` SQL and fails with an actionable\n * message naming the expected and found columns, rather than letting a widened\n * `INSERT` surface an opaque `no such column` at statement-prepare time. There are no\n * in-place migrations: drop the table (or use a fresh `tableName`) and re-index.\n * Fragment vectors are re-derivable from the records, so this costs embedding time,\n * never data.\n *\n * Semantics match `InMemoryFragmentCosineIndex` exactly: `addFragments` is\n * whole-record-replace (a single transaction deletes every prior fragment of the\n * target, then inserts the new set), `remove` drops every fragment of a target,\n * and `query` applies the optional `maxPerRecord` cap **during selection, before\n * the topK cut** — so one long document cannot crowd others out. The dimension is\n * established by the first `addFragments` (the `vec0` column is fixed-width) and\n * recovered from the table schema when a persistent file is reopened; similarity is\n * cosine (`score = 1 - cosineDistance`), byte-identical to the in-memory index.\n * Large-N ANN indexing is explicitly out of scope, same regime as the record index.\n *\n * **Connection ownership depends on which factory you use.** With\n * {@link SqliteVecFragmentIndex.create} the `Database` is consumer-owned\n * (bring-your-own): this index loads the `sqlite-vec` extension onto it and\n * reads/writes the table, but never opens or closes the connection — and that is\n * the seam for backing this index and a record index with one connection. With\n * {@link SqliteVecFragmentIndex.open} this package opens the file itself and hands\n * back a handle carrying the disposer for the connection it created.\n * @public\n */\nexport class SqliteVecFragmentIndex implements IFragmentVectorIndex {\n private readonly _db: BetterSqlite3.Database;\n private readonly _table: string;\n /** The dimension of every stored fragment vector; `undefined` until the table exists. */\n private _dimension: number | undefined;\n /** Prepared statements; created once the table exists (established or recovered). */\n private _stmts: IFragmentStatements | undefined;\n /**\n * Set by {@link SqliteVecFragmentIndex.release}. Distinct from `_stmts === undefined`,\n * which means *no dimension established yet* — see the remarks on `release`.\n */\n private _released: boolean;\n\n private constructor(db: BetterSqlite3.Database, table: string, dimension: number | undefined) {\n this._db = db;\n this._table = table;\n this._dimension = dimension;\n this._released = false;\n this._stmts = dimension === undefined ? undefined : this._prepare();\n }\n\n /**\n * The number of records that currently have at least one stored fragment. Zero\n * before the first add.\n *\n * @remarks\n * **Throws on a released index**, where every other member returns a `Failure` —\n * `IFragmentVectorIndex` declares this a synchronous `number`, so there is no\n * `Result` to fail into, and answering `0` would be a confident lie\n * indistinguishable from an empty index. Same reasoning as\n * {@link SqliteVecFragmentIndex.fragmentCount} and `SqliteVecVectorIndex.size`.\n */\n public get recordCount(): number {\n this._assertUsable('read recordCount');\n if (this._stmts === undefined) {\n return 0;\n }\n // `Number(...)` narrows the count in case the consumer enabled better-sqlite3\n // safe-integer mode (which returns `count(*)` as a `bigint`).\n return Number((this._stmts.recordCount.get() as { c: number | bigint }).c);\n }\n\n /**\n * The total number of fragments currently held across all records. Zero before\n * the first add. **Throws on a released index** — see\n * {@link SqliteVecFragmentIndex.recordCount}.\n */\n public get fragmentCount(): number {\n this._assertUsable('read fragmentCount');\n if (this._stmts === undefined) {\n return 0;\n }\n return Number((this._stmts.fragmentCount.get() as { c: number | bigint }).c);\n }\n\n /**\n * Family-convention factory. Loads the `sqlite-vec` extension onto the supplied\n * `better-sqlite3` connection and, if the fragment table already exists (a\n * reopened persistent file), verifies its auxiliary-column set matches this\n * version's and recovers its established dimension so no re-embedding is needed on\n * open.\n *\n * @param params - See {@link ISqliteVecFragmentIndexCreateParams}.\n * @returns `Success` with the index, or `Failure` if the table name is not a\n * simple identifier, the extension fails to load, or the existing table was\n * written by a version with a different auxiliary-column set (which requires a\n * drop-and-re-index — `vec0` cannot be altered in place).\n */\n public static create(params: ISqliteVecFragmentIndexCreateParams): Promise<Result<SqliteVecFragmentIndex>> {\n const table: string = params.tableName ?? DEFAULT_TABLE_NAME;\n if (!IDENTIFIER_RE.test(table)) {\n return Promise.resolve(\n fail(`sqlite-vec fragment index: table name '${table}' is not a simple SQL identifier`)\n );\n }\n return Promise.resolve(\n captureResult(() => {\n loadSqliteVec(params.database);\n const dimension: number | undefined = SqliteVecFragmentIndex._readExistingDimension(\n params.database,\n table\n );\n return new SqliteVecFragmentIndex(params.database, table, dimension);\n }).withErrorFormat((e) => `sqlite-vec fragment index: failed to initialize: ${e}`)\n );\n }\n\n /**\n * Path-based factory. Opens the database file itself and returns the index\n * together with a disposer for the connection it created.\n *\n * @remarks\n * The fragment-granular sibling of {@link SqliteVecVectorIndex.open}, and present\n * for the same reason: a consumer doing sub-document retrieval only would\n * otherwise still value-import `better-sqlite3` and hand-roll a `captureResult`\n * around a constructor that throws.\n *\n * **Use `create` instead when one connection must back both a fragment index and\n * a record index** — the intended shared-handle case. Two `open` calls on one path\n * give two independent connections, not a shared one.\n *\n * If initialization fails after the file is opened, the connection is closed\n * before returning, so a failed `open` does not leak the descriptor it created.\n * Should that close *itself* fail — the connection is then genuinely leaked — the\n * returned message says so rather than hiding it. That includes the\n * auxiliary-column mismatch failure, which is reported by `create` only after the\n * file is open.\n *\n * @param params - See {@link ISqliteVecFragmentIndexOpenParams}.\n * @returns `Success` with a {@link ISqliteVecFragmentIndexHandle}, or `Failure` if\n * the driver could not be loaded, the file could not be opened, the table name is\n * not a simple identifier, the extension fails to load, or the existing table was\n * written by a version with a different auxiliary-column set.\n */\n public static async open(\n params: ISqliteVecFragmentIndexOpenParams\n ): Promise<Result<ISqliteVecFragmentIndexHandle>> {\n return (await openOwnedConnection(params.path, LABEL)).thenOnSuccess(async (database) =>\n (await SqliteVecFragmentIndex.create({ database, tableName: params.tableName }))\n .onFailure((message) =>\n // This call opened the connection, so a failure to initialize on top of it\n // must not leave the file handle behind. A close that ALSO fails is said out\n // loud rather than swallowed — the same reasoning, and the same helper, as\n // `withRollbackNote`: silently discarding it would make the \"a failed open\n // leaks nothing\" guarantee untrue exactly when it stopped holding, with no\n // way for a caller to detect it.\n fail(withRollbackNote(message, closeOwnedConnection(database, LABEL)))\n )\n .onSuccess((index) =>\n succeed({\n index,\n close: () => {\n // Drop the statements BEFORE closing, so there is never a moment where\n // a closed connection has live `Statement` objects pointing at it —\n // see `release`.\n index.release();\n return closeOwnedConnection(database, LABEL);\n }\n })\n )\n );\n }\n\n /**\n * Drops this index's prepared statements and marks it unusable. Does **not**\n * touch the connection.\n *\n * @remarks\n * The fragment-lane counterpart of `SqliteVecVectorIndex.release`, and it\n * matters here for the same reason plus one more: a shared-connection\n * deployment — the case `create({ database })` exists for — holds a record index\n * *and* a fragment index over one connection, so it carries two instances of the\n * statement-lifetime shape rather than one. Both must be released.\n *\n * `better-sqlite3` exposes no public `finalize()`, so dropping the last\n * reference does not finalize a statement — it makes it collectable *earlier*,\n * while the environment is alive, rather than surviving to process teardown.\n * That narrows the window in which `Statement::~Statement()` runs against a\n * torn-down environment; it is not a proof against it.\n *\n * **Call this before closing a connection you own.**\n * {@link SqliteVecFragmentIndex.open}'s handle does it for you.\n *\n * Idempotent. After it, every member fails (or, for the two counts, throws)\n * rather than answering.\n */\n public release(): void {\n this._released = true;\n this._stmts = undefined;\n }\n\n /**\n * Throw if this index has been released. The members that call it and cannot\n * return a `Result` are the two counts; the rest convert the throw via\n * `captureResult`.\n */\n private _assertUsable(what: string): void {\n if (this._released) {\n throw new Error(`fragment index: cannot ${what}: the index has been released`);\n }\n }\n\n /** {@inheritDoc IFragmentVectorIndex.addFragments} */\n public addFragments(\n target: IEdgeTarget,\n fragments: ReadonlyArray<IEmbeddedFragment>\n ): Promise<Result<number>> {\n const key: string = edgeTargetKey(target);\n if (this._released) {\n return Promise.resolve(\n fail(`fragment index: cannot add fragments for '${key}': the index has been released`)\n );\n }\n // Validate every fragment before touching the database, so a bad fragment never\n // leaves the record half-replaced or the dimension half-established (whole-record\n // replace is all-or-nothing). The effective dimension is the established one, or —\n // on a still-dimensionless index — the first fragment's length; it is committed\n // (via table creation) only once the whole batch validates.\n let dimension: number | undefined = this._dimension;\n for (const fragment of fragments) {\n if (fragment.vector.length === 0) {\n return Promise.resolve(fail(`fragment index: cannot add '${key}': empty fragment vector`));\n }\n // A fragment carrying neither identity cannot be resolved back to anything by a\n // consumer holding the hit — the same invariant `embeddedFragmentConverter`\n // enforces at the untyped boundary, re-checked here at the index seam.\n if (fragment.locator === undefined && fragment.fragmentId === undefined) {\n return Promise.resolve(\n fail(\n `fragment index: cannot add '${key}': fragment requires at least one of 'locator' or 'fragmentId'`\n )\n );\n }\n // Locator offsets are persisted as SQLite integers (bound via BigInt). Reject a\n // non-safe-integer offset up front with a clear message, rather than letting\n // `BigInt(nonInteger)` throw cryptically inside the write transaction OR storing\n // a value the read-side `_toOffset` guard would later reject on every query.\n // An absent locator persists as a NULL offset pair and skips the check.\n if (\n fragment.locator !== undefined &&\n (!Number.isSafeInteger(fragment.locator.start) || !Number.isSafeInteger(fragment.locator.end))\n ) {\n return Promise.resolve(\n fail(\n `fragment index: cannot add '${key}': locator [${fragment.locator.start}, ${fragment.locator.end}) offsets must be safe integers`\n )\n );\n }\n if (dimension === undefined) {\n dimension = fragment.vector.length;\n } else if (fragment.vector.length !== dimension) {\n return Promise.resolve(\n fail(\n `fragment index: cannot add '${key}': fragment dimension ${fragment.vector.length} does not match index dimension ${dimension}`\n )\n );\n }\n }\n return Promise.resolve(\n captureResult(() => {\n // A same-target re-author (or an empty batch) still needs the table to exist\n // to delete prior fragments; create it lazily on the first non-empty add.\n if (this._stmts === undefined) {\n if (fragments.length === 0) {\n // Nothing stored yet and nothing to store: no table, no work.\n return 0;\n }\n // `fragments` is non-empty here (the empty case returned above), so the\n // validation loop proved every fragment shares `fragments[0]`'s length —\n // which IS the dimension to establish. Read it straight from the first\n // fragment: no cast, no invariant-dependent narrowing.\n const established: number = fragments[0].vector.length;\n this._createTable(established);\n this._dimension = established;\n this._stmts = this._prepare();\n }\n this._stmts.replace(key, fragments);\n return fragments.length;\n }).withErrorFormat((e) => `fragment index: cannot add '${key}': ${e}`)\n );\n }\n\n /** {@inheritDoc IFragmentVectorIndex.remove} */\n public remove(target: IEdgeTarget): Promise<Result<IEdgeTarget>> {\n return Promise.resolve(\n captureResult(() => {\n this._assertUsable(`remove '${edgeTargetKey(target)}'`);\n // Idempotent: removing a target with no fragments (or before any add created\n // the table) still succeeds.\n if (this._stmts !== undefined) {\n this._stmts.deleteByTarget.run(edgeTargetKey(target));\n }\n return target;\n }).withErrorFormat((e) => `fragment index: cannot remove '${edgeTargetKey(target)}': ${e}`)\n );\n }\n\n /** {@inheritDoc IFragmentVectorIndex.has} */\n public has(target: IEdgeTarget): Promise<Result<boolean>> {\n return Promise.resolve(\n captureResult(() => {\n this._assertUsable(`check '${edgeTargetKey(target)}'`);\n // Before any add has created the table there is nothing held — a truthful\n // `false`, matching `remove`'s idempotence and the zero counts.\n if (this._stmts === undefined) {\n return false;\n }\n return this._stmts.has.get(edgeTargetKey(target)) !== undefined;\n }).withErrorFormat((e) => `fragment index: cannot check '${edgeTargetKey(target)}': ${e}`)\n );\n }\n\n /** {@inheritDoc IFragmentVectorIndex.rebuild} */\n public async rebuild(\n source: IMemoryRecordSource,\n embed: FragmentEmbedder,\n options?: IVectorRebuildOptions\n ): Promise<DetailedResult<IFragmentVectorRebuildReport, IFragmentVectorRebuildReport>> {\n const lenient: boolean = (options?.onRecordError ?? 'fail') === 'skip';\n // `source` is consumer-supplied, so a throw or rejection becomes a `Failure`\n // here rather than escaping as an exception.\n const listed: Result<IMemoryRecordListing> = await invokeHook(() => source.list());\n if (listed.isFailure()) {\n // Deliberately BEFORE any clear, matching both siblings: a failed list is no\n // evidence about the fragments already held, and clearing here would destroy\n // a healthy PERSISTED index over a transient read error. No detail — there is\n // nothing this call disturbed to describe.\n return failWithDetail(`fragment index rebuild: failed to list records: ${listed.message}`);\n }\n const cleared: Result<true> = this._clear();\n if (cleared.isFailure()) {\n // Also nothing established: the table still holds whatever it held.\n return failWithDetail(`fragment index rebuild: failed to clear the index: ${cleared.message}`);\n }\n const indexed: Map<Kind, number> = new Map<Kind, number>();\n const fragments: Map<Kind, number> = new Map<Kind, number>();\n const declined: Map<Kind, number> = new Map<Kind, number>();\n const skipped: ISkippedVectorRecord[] = [];\n // Absent stays absent — only the source knows whether it filtered anything.\n const report = (): IFragmentVectorRebuildReport => ({\n indexed,\n fragments,\n declined,\n excluded: listed.value.excluded,\n skipped\n });\n for (const scoped of listed.value.records) {\n const kind: Kind = scoped.record.envelope.kind;\n // Capture-wrapped: an embedder that throws mid-loop would otherwise escape\n // past the `'fail'` rollback below, leaving this DURABLE table holding a\n // partial index that survives the process.\n const embedded: Result<ReadonlyArray<IEmbeddedFragment>> = await invokeHook(() => embed(scoped.record));\n if (embedded.isFailure()) {\n const error: string = `fragment index rebuild: embedding '${edgeTargetKey(scoped.target)}' failed: ${\n embedded.message\n }`;\n if (!lenient) {\n // A rollback that also fails is said out loud: the `'fail'` path\n // promises an empty index, and on a DURABLE table a botched rollback\n // survives the process.\n return failWithDetail(withRollbackNote(error, this._clear()), report());\n }\n skipped.push({ target: scoped.target, error });\n continue;\n }\n // An empty array is this lane's decline, and it is still WRITTEN — the\n // whole-record-replace is what clears any stale fragments.\n const added: Result<number> = await this.addFragments(scoped.target, embedded.value);\n if (added.isFailure()) {\n const error: string = `fragment index rebuild: ${added.message}`;\n if (!lenient) {\n return failWithDetail(withRollbackNote(error, this._clear()), report());\n }\n skipped.push({ target: scoped.target, error });\n continue;\n }\n if (added.value === 0) {\n tally(declined, kind);\n continue;\n }\n tally(indexed, kind);\n tally(fragments, kind, added.value);\n }\n return succeedWithDetail(report());\n }\n\n /**\n * **Empties the rows; does NOT release the table's declared dimension.** That\n * is a `vec0` constraint rather than a choice — the dimension is schema, and\n * there is no `ALTER TABLE` for it — so a rebuild at a new dimension fails\n * here where it would succeed on the in-memory sibling, which forgets its\n * dimension on reset. Changing dimension needs a drop-and-re-index; see the\n * note on `IVectorIndex.rebuild`. Tolerates a table that does not exist yet.\n */\n private _clear(): Result<true> {\n if (this._released) {\n return fail('fragment index: cannot clear: the index has been released');\n }\n if (this._stmts === undefined) {\n return succeed(true);\n }\n // Capture-wrapped like every other statement path: a closed connection or an\n // I/O error is a `Failure`, not an exception out of a `Result`-returning method.\n return captureResult(() => this._db.prepare(`DELETE FROM \"${this._table}\"`).run()).onSuccess(() =>\n succeed(true)\n );\n }\n\n /** {@inheritDoc IFragmentVectorIndex.query} */\n public query(\n vector: Float32Array,\n topK: number,\n options?: IFragmentQueryOptions\n ): Promise<Result<ReadonlyArray<IVectorQueryHit>>> {\n const maxPerRecord: number | undefined = options?.maxPerRecord;\n const scope: MemoryScopeKey | undefined = options?.scope;\n const id: MemoryId | undefined = options?.id;\n if (this._released) {\n return Promise.resolve(fail('fragment index: cannot query: the index has been released'));\n }\n if (topK <= 0 || this._stmts === undefined) {\n return Promise.resolve(succeed([]));\n }\n if (vector.length !== this._dimension) {\n return Promise.resolve(\n fail(\n `fragment index: query dimension ${vector.length} does not match index dimension ${this._dimension}`\n )\n );\n }\n const stmts: IFragmentStatements = this._stmts;\n return Promise.resolve(\n captureResult<ReadonlyArray<IVectorQueryHit>>(() => {\n // With a per-record cap the topK winners may lie past the first topK rows (a\n // capped record's later fragments are skipped), so fetch the full ranked set\n // and apply the cap + topK cut here — exactly as the in-memory index does.\n // Uncapped, KNN's own `k = topK` is already the answer.\n // A scope-only narrowing (a versioned kind's per-entity subtree) spans several\n // records, and `target_key` equality cannot express a prefix, so it is applied\n // over the full ranked set below. Correct either way — the caller's `topK` is\n // applied to the NARROWED set, which is the property that matters — but only\n // the single-record case gets the partition push-down.\n const recordKey: string | undefined =\n scope !== undefined && id !== undefined ? edgeTargetKey({ scope, id }) : undefined;\n // The cap forces the full ranked set ONLY when other records can fill from\n // behind a capped one. Under a single-record narrowing every row belongs to\n // that record, so the result is exactly `min(topK, maxPerRecord, fragments)`\n // and those are the first rows KNN returns — `k = topK` suffices, and\n // expanding to the table-wide `fragmentCount` would ask an\n // already-partition-restricted query for far more rows than it can use.\n const wholeSet: boolean =\n recordKey === undefined && (maxPerRecord !== undefined || scope !== undefined);\n const fetchK: number = wholeSet\n ? Number((stmts.fragmentCount.get() as { c: number | bigint }).c)\n : topK;\n if (fetchK <= 0) {\n return [];\n }\n const blob: Uint8Array = SqliteVecFragmentIndex._toBlob(vector);\n const rows: ReadonlyArray<IKnnRow> = (\n recordKey !== undefined\n ? stmts.queryScopedToRecord.all(blob, fetchK, recordKey)\n : stmts.query.all(blob, fetchK)\n ) as ReadonlyArray<IKnnRow>;\n // The scope prefix every record in `scope` shares. `edgeTargetKey` joins with\n // a NUL, so this cannot collide with a longer scope that merely starts the\n // same way.\n const scopePrefix: string | undefined =\n scope !== undefined && recordKey === undefined ? `${scope}\\0` : undefined;\n // sqlite-vec returns rows ascending by distance (nearest first); score is\n // `1 - cosineDistance`, so this order is already descending score.\n const hits: IVectorQueryHit[] = [];\n const perRecord: Map<string, number> = new Map<string, number>();\n for (const row of rows) {\n if (hits.length >= topK) {\n break;\n }\n if (scopePrefix !== undefined && !row.target_key.startsWith(scopePrefix)) {\n continue;\n }\n if (maxPerRecord !== undefined) {\n const used: number = perRecord.get(row.target_key) ?? 0;\n if (used >= maxPerRecord) {\n continue;\n }\n perRecord.set(row.target_key, used + 1);\n }\n const key: string = row.target_key;\n hits.push({\n target: SqliteVecFragmentIndex._parseKey(key),\n score: 1 - row.distance,\n ...SqliteVecFragmentIndex._toIdentity(row, key)\n });\n }\n return hits;\n }).withErrorFormat((e) => `fragment index: query failed: ${e}`)\n );\n }\n\n /**\n * Create the fragment `vec0` virtual table with the established dimension. The\n * auxiliary columns must stay in sync with `AUXILIARY_COLUMNS`, which\n * `create` compares against an existing table's stored DDL.\n */\n private _createTable(dimension: number): void {\n this._db.exec(\n `CREATE VIRTUAL TABLE IF NOT EXISTS \"${this._table}\" USING vec0(` +\n `target_key TEXT PARTITION KEY, embedding float[${dimension}] distance_metric=cosine, ` +\n `+start_off integer, +end_off integer, +fragment_id text)`\n );\n }\n\n /** Prepare the statements the index reuses. Requires the table to exist. */\n private _prepare(): IFragmentStatements {\n const del: BetterSqlite3.Statement = this._db.prepare(\n `DELETE FROM \"${this._table}\" WHERE target_key = ?`\n );\n const ins: BetterSqlite3.Statement = this._db.prepare(\n `INSERT INTO \"${this._table}\"(target_key, embedding, start_off, end_off, fragment_id) ` +\n `VALUES (?, ?, ?, ?, ?)`\n );\n // Whole-record replace: drop every prior fragment of the target, then insert the\n // new set, atomically. An empty set collapses to a pure delete.\n const replaceTxn: BetterSqlite3.Transaction<\n (key: string, fragments: ReadonlyArray<IEmbeddedFragment>) => void\n > = this._db.transaction((key: string, fragments: ReadonlyArray<IEmbeddedFragment>) => {\n del.run(key);\n for (const fragment of fragments) {\n ins.run(\n key,\n SqliteVecFragmentIndex._toBlob(fragment.vector),\n // vec0 typed columns reject a JS float; bind the offsets as integers. An\n // absent locator binds the pair as NULL — never a partial pair, so the read\n // side can treat a half-NULL pair as corruption rather than a legal shape.\n fragment.locator === undefined ? null : BigInt(fragment.locator.start),\n fragment.locator === undefined ? null : BigInt(fragment.locator.end),\n // Stored verbatim and never parsed; absent binds as NULL.\n fragment.fragmentId ?? null\n );\n }\n });\n return {\n deleteByTarget: del,\n replace: (key: string, fragments: ReadonlyArray<IEmbeddedFragment>): void => {\n replaceTxn(key, fragments);\n },\n query: this._db.prepare(\n `SELECT target_key, start_off, end_off, fragment_id, distance FROM \"${this._table}\" ` +\n `WHERE embedding MATCH ? AND k = ?`\n ),\n // The single-record narrowing constrains `target_key`, which is the table's\n // PARTITION KEY — so this is a partition-restricted KNN rather than a scan\n // plus a filter. That is the performance reason this narrowing belongs in the\n // library instead of in a bigger over-fetch on the caller's side.\n queryScopedToRecord: this._db.prepare(\n `SELECT target_key, start_off, end_off, fragment_id, distance FROM \"${this._table}\" ` +\n `WHERE embedding MATCH ? AND k = ? AND target_key = ?`\n ),\n fragmentCount: this._db.prepare(`SELECT count(*) AS c FROM \"${this._table}\"`),\n recordCount: this._db.prepare(`SELECT count(DISTINCT target_key) AS c FROM \"${this._table}\"`),\n // `LIMIT 1`: membership needs existence, not cardinality.\n has: this._db.prepare(`SELECT 1 FROM \"${this._table}\" WHERE target_key = ? LIMIT 1`)\n };\n }\n\n /**\n * Recover the established dimension of an existing fragment `vec0` table from its\n * stored `CREATE VIRTUAL TABLE` SQL (`float[<n>]`), after checking that the table's\n * auxiliary columns match `AUXILIARY_COLUMNS`. Returns `undefined` when the\n * table does not exist yet (a fresh database — dimension is set by the first add).\n *\n * Throws when a table of that name exists but is not a usable fragment index (a\n * mismatched auxiliary-column set, or no `vec0` embedding column); the caller runs\n * this inside `captureResult`, so it surfaces as a loud `Failure` from `create`.\n * The same stored DDL answers every one of those questions, so the checks cost\n * nothing extra.\n */\n private static _readExistingDimension(db: BetterSqlite3.Database, table: string): number | undefined {\n const row: { sql: string } | undefined = db\n .prepare(\"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?\")\n .get(table) as { sql: string } | undefined;\n if (row === undefined) {\n return undefined;\n }\n SqliteVecFragmentIndex._verifyAuxiliaryColumns(row.sql, table);\n const match: RegExpMatchArray | null = row.sql.match(/float\\[(\\d+)\\]/);\n if (match === null) {\n // The auxiliary columns matched but there is no `float[<n>]` embedding column,\n // so this is not a usable fragment index table. Same remedy as a column\n // mismatch — and failing here beats handing back a dimensionless index whose\n // first add would `CREATE VIRTUAL TABLE IF NOT EXISTS` into a no-op.\n throw new Error(\n `existing table '${table}' has no vec0 embedding column, so it is not a usable fragment ` +\n `index table. Drop it (or pass a fresh tableName) and re-add every fragment.`\n );\n }\n return Number(match[1]);\n }\n\n /**\n * Compare an existing table's auxiliary columns against `AUXILIARY_COLUMNS`.\n *\n * `CREATE VIRTUAL TABLE IF NOT EXISTS` is a no-op against an existing table (SQLite\n * never compares schemas) and `vec0` has no `ALTER TABLE ADD COLUMN`, so a table\n * written by an earlier version of this package silently keeps its old columns and\n * only fails later — as an opaque `no such column` when the widened `INSERT` is\n * prepared. Detect it here instead and say what to do about it. Order is not\n * compared: every statement names its columns explicitly, so only the set matters.\n */\n private static _verifyAuxiliaryColumns(sql: string, table: string): void {\n const found: string[] = Array.from(sql.matchAll(AUXILIARY_COLUMN_RE), (m) => m[1]);\n const expected: ReadonlyArray<string> = AUXILIARY_COLUMNS;\n const matches: boolean =\n found.length === expected.length && expected.every((column) => found.includes(column));\n if (!matches) {\n throw new Error(\n `existing table '${table}' has auxiliary columns [${found.join(', ')}] but this index ` +\n `requires [${expected.join(', ')}] — it was written by a different version of ` +\n `@fgv/ts-agent-memory-sqlite-vec, or it is not a fragment index table at all. vec0 virtual ` +\n `tables cannot be altered in place, so this requires a drop-and-re-index: DROP TABLE ` +\n `\"${table}\" (or pass a fresh tableName) and re-add every fragment. Fragment vectors are ` +\n `re-derivable from the records, so this costs embedding time, never data.`\n );\n }\n }\n\n /**\n * Rebuild the identity fields of a hit from a persisted row, omitting each field\n * the stored fragment did not carry (so a hit is structurally identical to one this\n * index produced before `fragment_id` existed).\n *\n * A row carrying neither identity violates the write-side invariant and could not\n * be resolved by the caller, so it fails loudly instead of yielding an anonymous\n * hit.\n */\n private static _toIdentity(row: IKnnRow, key: string): FragmentIdentity {\n const locator: IFragmentLocator | undefined = SqliteVecFragmentIndex._toLocator(row, key);\n if (locator === undefined && row.fragment_id === null) {\n throw new Error(\n `fragment '${key}': row carries neither a locator nor a fragment id (corrupt persisted data)`\n );\n }\n return {\n ...(locator !== undefined ? { locator } : {}),\n ...(row.fragment_id !== null ? { fragmentId: row.fragment_id } : {})\n };\n }\n\n /**\n * Rebuild a fragment's locator from its persisted offsets, or `undefined` when the\n * fragment was stored without one (both offsets `NULL`).\n *\n * The pair is written all-or-nothing, so a half-`NULL` pair can only come from\n * corrupt / externally-edited data. Throw rather than coerce — `Number(null)` is\n * `0`, which would silently fabricate a span starting at the top of the body.\n */\n private static _toLocator(row: IKnnRow, key: string): IFragmentLocator | undefined {\n const start: number | bigint | null = row.start_off;\n const end: number | bigint | null = row.end_off;\n if (start === null && end === null) {\n return undefined;\n }\n if (start === null || end === null) {\n throw new Error(\n `fragment '${key}': locator has only one of its start/end offsets (corrupt persisted data)`\n );\n }\n return {\n start: SqliteVecFragmentIndex._toOffset(start, key),\n end: SqliteVecFragmentIndex._toOffset(end, key)\n };\n }\n\n /** Pack a `Float32Array` as the little-endian byte blob `vec0` stores. Copies, so the caller may reuse its buffer. */\n private static _toBlob(vector: Float32Array): Uint8Array {\n return new Uint8Array(Float32Array.from(vector).buffer);\n }\n\n /**\n * Reverse `edgeTargetKey` — the canonical key is `scope\\0id` with NUL excluded\n * from both components, so the first NUL splits it unambiguously. A key with no\n * NUL cannot have been written by `edgeTargetKey`; rather than fabricate a wrong\n * `(scope, id)` from corrupt / externally-edited table data, throw so the query\n * surfaces it as a loud `Failure`.\n */\n private static _parseKey(key: string): IEdgeTarget {\n const nul: number = key.indexOf('\\0');\n if (nul < 0) {\n throw new Error(`malformed target key '${key}': missing scope/id separator (corrupt persisted data)`);\n }\n return {\n scope: key.slice(0, nul) as unknown as MemoryScopeKey,\n id: key.slice(nul + 1) as unknown as MemoryId\n };\n }\n\n /**\n * Coerce a persisted locator offset to a plain `number`. `better-sqlite3` returns\n * integer columns as `bigint` under safe-integer mode, so an offset can arrive as\n * either; both narrow to `number` here. A value outside the safe-integer range\n * (only reachable via corrupt / externally-edited data — the index only ever\n * writes in-document offsets) throws rather than silently losing precision, so the\n * query surfaces it as a loud `Failure`.\n */\n private static _toOffset(value: number | bigint, key: string): number {\n const n: number = Number(value);\n if (!Number.isSafeInteger(n)) {\n throw new Error(\n `fragment '${key}': locator offset ${String(value)} is not a safe integer (corrupt persisted data)`\n );\n }\n return n;\n }\n}\n\n/** The prepared statements / helpers the fragment index reuses once its table exists. */\ninterface IFragmentStatements {\n /** KNN restricted to one record's partition; see `queryScopedToRecord` above. */\n readonly queryScopedToRecord: BetterSqlite3.Statement;\n readonly deleteByTarget: BetterSqlite3.Statement;\n readonly replace: (key: string, fragments: ReadonlyArray<IEmbeddedFragment>) => void;\n readonly query: BetterSqlite3.Statement;\n readonly fragmentCount: BetterSqlite3.Statement;\n readonly recordCount: BetterSqlite3.Statement;\n readonly has: BetterSqlite3.Statement;\n}\n"]}
@@ -6,8 +6,11 @@ import { load as loadSqliteVec } from 'sqlite-vec';
6
6
  import { captureResult, fail, failWithDetail, succeed, succeedWithDetail } from '@fgv/ts-utils';
7
7
  import { edgeTargetKey } from '@fgv/ts-agent-memory';
8
8
  import { invokeHook, tally, withRollbackNote } from './rebuildHelpers';
9
+ import { closeOwnedConnection, openOwnedConnection } from './connection';
9
10
  /** Default name for the `vec0` virtual table. */
10
11
  const DEFAULT_TABLE_NAME = 'memory_vectors';
12
+ /** Package-facing prefix for this class's failure messages. */
13
+ const LABEL = 'sqlite-vec index';
11
14
  /** A simple SQL identifier — the only shape allowed for the table name (it is interpolated into DDL). */
12
15
  const IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
13
16
  /**
@@ -36,9 +39,13 @@ const IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
36
39
  * durable, appropriate for the same "thousands of records" regime the in-memory
37
40
  * index targets. Large-N ANN indexing is explicitly out of scope — see the README.
38
41
  *
39
- * The `better-sqlite3` `Database` is consumer-owned (bring-your-own): this index
40
- * loads the `sqlite-vec` extension onto it and reads/writes the table, but never
41
- * opens or closes the connection.
42
+ * **Connection ownership depends on which factory you use.** With
43
+ * {@link SqliteVecVectorIndex.create} the `Database` is consumer-owned
44
+ * (bring-your-own): this index loads the `sqlite-vec` extension onto it and
45
+ * reads/writes the table, but never opens or closes the connection — and that is
46
+ * the seam for backing a record index and a fragment index with one connection.
47
+ * With {@link SqliteVecVectorIndex.open} this package opens the file itself and
48
+ * hands back a handle carrying the disposer for the connection it created.
42
49
  * @public
43
50
  */
44
51
  export class SqliteVecVectorIndex {
@@ -46,10 +53,22 @@ export class SqliteVecVectorIndex {
46
53
  this._db = db;
47
54
  this._table = table;
48
55
  this._dimension = dimension;
56
+ this._released = false;
49
57
  this._stmts = dimension === undefined ? undefined : this._prepare();
50
58
  }
51
- /** The number of vectors currently held. Zero before the first `add`. */
59
+ /**
60
+ * The number of vectors currently held. Zero before the first `add`.
61
+ *
62
+ * @remarks
63
+ * **Throws on a released index**, where every other member returns a `Failure` —
64
+ * because `IVectorIndex` declares this a synchronous `number` and there is no
65
+ * `Result` to fail into. Throwing preserves the behaviour a released index had
66
+ * before it had an explicit released state (the underlying statement threw
67
+ * against the closed connection); the alternative, answering `0`, would be a
68
+ * confident lie indistinguishable from an empty index.
69
+ */
52
70
  get size() {
71
+ this._assertUsable('read size');
53
72
  if (this._stmts === undefined) {
54
73
  return 0;
55
74
  }
@@ -84,9 +103,93 @@ export class SqliteVecVectorIndex {
84
103
  return new SqliteVecVectorIndex(params.database, table, dimension);
85
104
  }).withErrorFormat((e) => `sqlite-vec index: failed to initialize: ${e}`));
86
105
  }
106
+ /**
107
+ * Path-based factory. Opens the database file itself and returns the index
108
+ * together with a disposer for the connection it created.
109
+ *
110
+ * @remarks
111
+ * The convenience over {@link SqliteVecVectorIndex.create} is that the consumer
112
+ * neither value-imports `better-sqlite3` nor re-establishes `Result` discipline
113
+ * around a constructor that throws — this is the one place the package leaked its
114
+ * own dependency into consumer source.
115
+ *
116
+ * **Use `create` instead when one connection must back more than one index** (a
117
+ * record index and a fragment index in the same file, the intended shared-handle
118
+ * case). Two `open` calls on one path give two independent connections, not a
119
+ * shared one.
120
+ *
121
+ * If initialization fails after the file is opened, the connection is closed
122
+ * before returning, so a failed `open` does not leak the descriptor it created.
123
+ * Should that close *itself* fail — the connection is then genuinely leaked — the
124
+ * returned message says so rather than hiding it.
125
+ *
126
+ * @param params - See {@link ISqliteVecVectorIndexOpenParams}.
127
+ * @returns `Success` with a {@link ISqliteVecVectorIndexHandle}, or `Failure` if
128
+ * the driver could not be loaded, the file could not be opened, the table name is
129
+ * not a simple identifier, or the extension fails to load.
130
+ */
131
+ static async open(params) {
132
+ return (await openOwnedConnection(params.path, LABEL)).thenOnSuccess(async (database) => (await SqliteVecVectorIndex.create({ database, tableName: params.tableName }))
133
+ .onFailure((message) =>
134
+ // This call opened the connection, so a failure to initialize on top of it
135
+ // must not leave the file handle behind. A close that ALSO fails is said out
136
+ // loud rather than swallowed — the same reasoning, and the same helper, as
137
+ // `withRollbackNote`: silently discarding it would make the "a failed open
138
+ // leaks nothing" guarantee untrue exactly when it stopped holding, with no
139
+ // way for a caller to detect it.
140
+ fail(withRollbackNote(message, closeOwnedConnection(database, LABEL))))
141
+ .onSuccess((index) => succeed({
142
+ index,
143
+ close: () => {
144
+ // Drop the statements BEFORE closing, so there is never a moment where
145
+ // a closed connection has live `Statement` objects pointing at it —
146
+ // see `release`.
147
+ index.release();
148
+ return closeOwnedConnection(database, LABEL);
149
+ }
150
+ })));
151
+ }
152
+ /**
153
+ * Drops this index's prepared statements and marks it unusable. Does **not**
154
+ * touch the connection.
155
+ *
156
+ * @remarks
157
+ * `better-sqlite3` exposes no public `finalize()`, so releasing the last
158
+ * reference to a `Statement` does not finalize it — it makes it collectable
159
+ * *earlier*, while the environment is alive, rather than surviving to process
160
+ * teardown. That narrows the window in which `Statement::~Statement()` runs
161
+ * against a torn-down environment; it is not a proof against it.
162
+ *
163
+ * **Call this before closing a connection you own.** {@link
164
+ * SqliteVecVectorIndex.open}'s handle does it for you. A `create()`-made index
165
+ * holds a connection it does not own and stays structurally incapable of
166
+ * closing it — this method drops only what the index itself allocated, which is
167
+ * why it is safe to expose there.
168
+ *
169
+ * Idempotent. After it, every member fails (or, for `size`, throws) rather than
170
+ * answering: a released index is deliberately distinguishable from one that has
171
+ * simply never had an `add`, whose `_stmts` are also absent but which answers
172
+ * `size === 0` truthfully.
173
+ */
174
+ release() {
175
+ this._released = true;
176
+ this._stmts = undefined;
177
+ }
178
+ /**
179
+ * Throw if this index has been released. The one member that calls it and cannot
180
+ * return a `Result` is `size`; the rest convert the throw via `captureResult`.
181
+ */
182
+ _assertUsable(what) {
183
+ if (this._released) {
184
+ throw new Error(`vector index: cannot ${what}: the index has been released`);
185
+ }
186
+ }
87
187
  /** {@inheritDoc IVectorIndex.add} */
88
188
  add(target, vector) {
89
189
  const key = edgeTargetKey(target);
190
+ if (this._released) {
191
+ return Promise.resolve(fail(`vector index: cannot add '${key}': the index has been released`));
192
+ }
90
193
  if (vector.length === 0) {
91
194
  return Promise.resolve(fail(`vector index: cannot add '${key}': empty vector`));
92
195
  }
@@ -106,6 +209,7 @@ export class SqliteVecVectorIndex {
106
209
  /** {@inheritDoc IVectorIndex.has} */
107
210
  has(target) {
108
211
  return Promise.resolve(captureResult(() => {
212
+ this._assertUsable(`check '${edgeTargetKey(target)}'`);
109
213
  // Before any add has created the table there is nothing held, which is a
110
214
  // truthful `false` rather than an error — same posture as `remove`'s
111
215
  // idempotence and `size`'s zero.
@@ -118,6 +222,7 @@ export class SqliteVecVectorIndex {
118
222
  /** {@inheritDoc IVectorIndex.remove} */
119
223
  remove(target) {
120
224
  return Promise.resolve(captureResult(() => {
225
+ this._assertUsable(`remove '${edgeTargetKey(target)}'`);
121
226
  // Idempotent: removing a target with no embedding (or before any `add`
122
227
  // created the table) still succeeds.
123
228
  if (this._stmts !== undefined) {
@@ -211,6 +316,9 @@ export class SqliteVecVectorIndex {
211
316
  * note on `IVectorIndex.rebuild`.
212
317
  */
213
318
  _clear() {
319
+ if (this._released) {
320
+ return fail('vector index: cannot clear: the index has been released');
321
+ }
214
322
  if (this._stmts === undefined) {
215
323
  return succeed(true);
216
324
  }
@@ -221,6 +329,9 @@ export class SqliteVecVectorIndex {
221
329
  }
222
330
  /** {@inheritDoc IVectorIndex.query} */
223
331
  query(vector, topK) {
332
+ if (this._released) {
333
+ return Promise.resolve(fail('vector index: cannot query: the index has been released'));
334
+ }
224
335
  if (topK <= 0 || this._stmts === undefined) {
225
336
  return Promise.resolve(succeed([]));
226
337
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sqliteVecVectorIndex.js","sourceRoot":"","sources":["../../../src/packlets/sqlite-vec-index/sqliteVecVectorIndex.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { load as loadSqliteVec } from 'sqlite-vec';\nimport {\n DetailedResult,\n Result,\n captureResult,\n fail,\n failWithDetail,\n succeed,\n succeedWithDetail\n} from '@fgv/ts-utils';\nimport {\n IEdgeTarget,\n IMemoryRecordListing,\n IMemoryRecordSource,\n ISkippedVectorRecord,\n IVectorIndex,\n IVectorQueryHit,\n IVectorRebuildOptions,\n IVectorRebuildReport,\n Kind,\n MemoryEmbedder,\n MemoryId,\n MemoryScopeKey,\n edgeTargetKey\n} from '@fgv/ts-agent-memory';\nimport { invokeHook, tally, withRollbackNote } from './rebuildHelpers';\nimport { ISqliteVecVectorIndexCreateParams } from './model';\n\n/** Default name for the `vec0` virtual table. */\nconst DEFAULT_TABLE_NAME: string = 'memory_vectors';\n\n/** A simple SQL identifier — the only shape allowed for the table name (it is interpolated into DDL). */\nconst IDENTIFIER_RE: RegExp = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/** One KNN row as returned by the `vec0` MATCH query. */\ninterface IKnnRow {\n readonly target_key: string;\n readonly distance: number;\n}\n\n/**\n * A persistent, `sqlite-vec`-backed `IVectorIndex` for `@fgv/ts-agent-memory`.\n *\n * @remarks\n * This is the **durable** counterpart to the in-memory `InMemoryCosineIndex`:\n * embeddings live in a `sqlite-vec` `vec0` virtual table inside a `better-sqlite3`\n * database, so they survive a process restart. A consumer that wires this index\n * into `FileTreeMemoryStore` (instead of the in-memory index) opens an existing\n * vault **without re-embedding it** — the vectors are already on disk. New writes\n * still flow through the store's incremental embed-on-write path; there is no core\n * store change.\n *\n * The index is keyed by the canonical `edgeTargetKey` of each record's\n * scope-qualified `(scope, id)` address (a `TEXT PRIMARY KEY` on the `vec0` table),\n * so two records that share a filename stem across scopes never collide. The\n * dimension is established by the first `add` (the `vec0` column is fixed-width) and\n * recovered from the table schema when a persistent file is reopened; every later\n * `add`/`query` must match it or fail loudly, exactly as the in-memory index does.\n * Similarity is cosine (`distance_metric=cosine`): the returned `score` is\n * `1 - cosineDistance`, i.e. cosine similarity in `[-1, 1]`, higher = more similar —\n * byte-for-byte the same scoring contract as `InMemoryCosineIndex`.\n *\n * Query is a brute-force `vec0` KNN scan (not an ANN structure): correct and\n * durable, appropriate for the same \"thousands of records\" regime the in-memory\n * index targets. Large-N ANN indexing is explicitly out of scope — see the README.\n *\n * The `better-sqlite3` `Database` is consumer-owned (bring-your-own): this index\n * loads the `sqlite-vec` extension onto it and reads/writes the table, but never\n * opens or closes the connection.\n * @public\n */\nexport class SqliteVecVectorIndex implements IVectorIndex {\n private readonly _db: BetterSqlite3.Database;\n private readonly _table: string;\n /** The dimension of every stored vector; `undefined` until the table exists (first `add` or a reopened non-empty file). */\n private _dimension: number | undefined;\n /** Prepared statements; created once the table exists (established or recovered). */\n private _stmts: ISqliteVecStatements | undefined;\n\n private constructor(db: BetterSqlite3.Database, table: string, dimension: number | undefined) {\n this._db = db;\n this._table = table;\n this._dimension = dimension;\n this._stmts = dimension === undefined ? undefined : this._prepare();\n }\n\n /** The number of vectors currently held. Zero before the first `add`. */\n public get size(): number {\n if (this._stmts === undefined) {\n return 0;\n }\n // `Number(...)` narrows the count in case the consumer enabled better-sqlite3\n // safe-integer mode (`db.defaultSafeIntegers(true)`), which returns `count(*)`\n // as a `bigint`. Without it a `bigint` leaks through a `number`-typed contract\n // member — and now through `IIndexCoverage.indexSize`, which is also declared\n // `number`, so the coverage report would carry a value of the wrong runtime\n // type. `SqliteVecFragmentIndex`'s two counts have always converted; this one\n // was the outlier.\n return Number((this._stmts.count.get() as { c: number | bigint }).c);\n }\n\n /**\n * Family-convention factory. Loads the `sqlite-vec` extension onto the supplied\n * `better-sqlite3` connection and, if the vector table already exists (a reopened\n * persistent file), recovers its established dimension so no re-embedding is\n * needed on open.\n *\n * @param params - See {@link ISqliteVecVectorIndexCreateParams}.\n * @returns `Success` with the index, or `Failure` if the table name is not a\n * simple identifier or the extension fails to load.\n */\n public static create(params: ISqliteVecVectorIndexCreateParams): Promise<Result<SqliteVecVectorIndex>> {\n const table: string = params.tableName ?? DEFAULT_TABLE_NAME;\n if (!IDENTIFIER_RE.test(table)) {\n return Promise.resolve(fail(`sqlite-vec index: table name '${table}' is not a simple SQL identifier`));\n }\n return Promise.resolve(\n captureResult(() => {\n loadSqliteVec(params.database);\n const dimension: number | undefined = SqliteVecVectorIndex._readExistingDimension(\n params.database,\n table\n );\n return new SqliteVecVectorIndex(params.database, table, dimension);\n }).withErrorFormat((e) => `sqlite-vec index: failed to initialize: ${e}`)\n );\n }\n\n /** {@inheritDoc IVectorIndex.add} */\n public add(target: IEdgeTarget, vector: Float32Array): Promise<Result<string>> {\n const key: string = edgeTargetKey(target);\n if (vector.length === 0) {\n return Promise.resolve(fail(`vector index: cannot add '${key}': empty vector`));\n }\n if (this._dimension !== undefined && vector.length !== this._dimension) {\n return Promise.resolve(\n fail(\n `vector index: cannot add '${key}': dimension ${vector.length} does not match index dimension ${this._dimension}`\n )\n );\n }\n return Promise.resolve(\n captureResult(() => {\n if (this._stmts === undefined) {\n this._createTable(vector.length);\n this._dimension = vector.length;\n this._stmts = this._prepare();\n }\n this._stmts.replace(key, SqliteVecVectorIndex._toBlob(vector));\n return key;\n }).withErrorFormat((e) => `vector index: cannot add '${key}': ${e}`)\n );\n }\n\n /** {@inheritDoc IVectorIndex.has} */\n public has(target: IEdgeTarget): Promise<Result<boolean>> {\n return Promise.resolve(\n captureResult(() => {\n // Before any add has created the table there is nothing held, which is a\n // truthful `false` rather than an error — same posture as `remove`'s\n // idempotence and `size`'s zero.\n if (this._stmts === undefined) {\n return false;\n }\n return this._stmts.has.get(edgeTargetKey(target)) !== undefined;\n }).withErrorFormat((e) => `vector index: cannot check '${edgeTargetKey(target)}': ${e}`)\n );\n }\n\n /** {@inheritDoc IVectorIndex.remove} */\n public remove(target: IEdgeTarget): Promise<Result<IEdgeTarget>> {\n return Promise.resolve(\n captureResult(() => {\n // Idempotent: removing a target with no embedding (or before any `add`\n // created the table) still succeeds.\n if (this._stmts !== undefined) {\n this._stmts.delete.run(edgeTargetKey(target));\n }\n return target;\n }).withErrorFormat((e) => `vector index: cannot remove '${edgeTargetKey(target)}': ${e}`)\n );\n }\n\n /**\n * Re-embed every record from `source` and rebuild the persisted index — see\n * `IVectorIndex.rebuild` for the mode semantics, which this implementation\n * matches exactly.\n *\n * @remarks\n * **Not atomic, and cannot be.** `better-sqlite3` transactions are synchronous,\n * so one cannot span the `await embed(...)` calls this loop makes — unlike\n * {@link SqliteVecVectorIndex.add}, which wraps its delete-then-insert. The\n * `'fail'` / `'skip'` modes therefore cover only failures JavaScript can catch:\n * a process kill mid-rebuild leaves the table holding neither the old index nor\n * the complete new one, and the remedy is to run `rebuild` again.\n */\n public async rebuild(\n source: IMemoryRecordSource,\n embed: MemoryEmbedder,\n options?: IVectorRebuildOptions\n ): Promise<DetailedResult<IVectorRebuildReport, IVectorRebuildReport>> {\n const lenient: boolean = (options?.onRecordError ?? 'fail') === 'skip';\n // `source` is consumer-supplied, so a throw or rejection becomes a `Failure`\n // here rather than escaping as an exception.\n const listed: Result<IMemoryRecordListing> = await invokeHook(() => source.list());\n if (listed.isFailure()) {\n // Deliberately BEFORE any clear: a failed list is no evidence about the\n // vectors already held, and no re-embedding has been attempted, so there is\n // no half-rebuilt state to protect against. Clearing here would destroy a\n // healthy persisted index over a transient read error. No report either, for\n // the same reason — there is nothing this call disturbed to describe.\n return failWithDetail(`vector index rebuild: failed to list records: ${listed.message}`);\n }\n const cleared: Result<true> = this._clear();\n if (cleared.isFailure()) {\n // Also nothing established: the table still holds whatever it held.\n return failWithDetail(`vector index rebuild: failed to clear the index: ${cleared.message}`);\n }\n const indexed: Map<Kind, number> = new Map<Kind, number>();\n const declined: Map<Kind, number> = new Map<Kind, number>();\n const skipped: ISkippedVectorRecord[] = [];\n // Absent stays absent — only the source knows whether it filtered anything.\n const report = (): IVectorRebuildReport => ({\n indexed,\n declined,\n excluded: listed.value.excluded,\n skipped\n });\n for (const scoped of listed.value.records) {\n const kind: Kind = scoped.record.envelope.kind;\n // Likewise capture-wrapped: an embedder that throws mid-loop would\n // otherwise escape past the `'fail'` rollback below, leaving this DURABLE\n // table holding a partial index that survives the process.\n const embedded: Result<Float32Array | undefined> = await invokeHook(() => embed(scoped.record));\n if (embedded.isFailure()) {\n const error: string = `vector index rebuild: embedding '${edgeTargetKey(scoped.target)}' failed: ${\n embedded.message\n }`;\n if (!lenient) {\n return failWithDetail(withRollbackNote(error, this._clear()), report());\n }\n skipped.push({ target: scoped.target, error });\n continue;\n }\n if (embedded.value === undefined) {\n tally(declined, kind);\n continue;\n }\n const added: Result<string> = await this.add(scoped.target, embedded.value);\n if (added.isFailure()) {\n const error: string = `vector index rebuild: ${added.message}`;\n if (!lenient) {\n return failWithDetail(withRollbackNote(error, this._clear()), report());\n }\n skipped.push({ target: scoped.target, error });\n continue;\n }\n // Tallied in the loop rather than read back off `size` at the end. That\n // `COUNT` was also the only fallible step in assembling the report, so the\n // per-kind tally removes a failure path as well as a rounding of the answer.\n tally(indexed, kind);\n }\n return succeedWithDetail(report());\n }\n\n /**\n * **Empties the rows; does NOT release the table's declared dimension.** That\n * is a `vec0` constraint rather than a choice — the dimension is schema, and\n * there is no `ALTER TABLE` for it — so a rebuild at a new dimension fails\n * here where it would succeed on the in-memory sibling, which forgets its\n * dimension on reset. Changing dimension needs a drop-and-re-index; see the\n * note on `IVectorIndex.rebuild`.\n */\n private _clear(): Result<true> {\n if (this._stmts === undefined) {\n return succeed(true);\n }\n // Capture-wrapped like `add` / `remove` / `query`: a closed connection or an\n // I/O error here is a `Failure`, not an exception thrown out of a method\n // whose signature promises a `Result`.\n return captureResult(() => this._db.prepare(`DELETE FROM \"${this._table}\"`).run()).onSuccess(() =>\n succeed(true)\n );\n }\n\n /** {@inheritDoc IVectorIndex.query} */\n public query(vector: Float32Array, topK: number): Promise<Result<ReadonlyArray<IVectorQueryHit>>> {\n if (topK <= 0 || this._stmts === undefined) {\n return Promise.resolve(succeed([]));\n }\n if (vector.length !== this._dimension) {\n return Promise.resolve(\n fail(\n `vector index: query dimension ${vector.length} does not match index dimension ${this._dimension}`\n )\n );\n }\n return Promise.resolve(\n captureResult<ReadonlyArray<IVectorQueryHit>>(() => {\n const rows: ReadonlyArray<IKnnRow> = this._stmts!.query.all(\n SqliteVecVectorIndex._toBlob(vector),\n topK\n ) as ReadonlyArray<IKnnRow>;\n // sqlite-vec returns rows in ascending distance (nearest first); score is\n // `1 - cosineDistance` = cosine similarity, so descending score is preserved.\n return rows.map((row) => ({\n target: SqliteVecVectorIndex._parseKey(row.target_key),\n score: 1 - row.distance\n }));\n }).withErrorFormat((e) => `vector index: query failed: ${e}`)\n );\n }\n\n /** Create the `vec0` virtual table with the established dimension. */\n private _createTable(dimension: number): void {\n this._db.exec(\n `CREATE VIRTUAL TABLE IF NOT EXISTS \"${this._table}\" USING vec0(` +\n `target_key TEXT PRIMARY KEY, embedding float[${dimension}] distance_metric=cosine)`\n );\n }\n\n /** Prepare the statements the index reuses. Requires the table to exist. */\n private _prepare(): ISqliteVecStatements {\n const del: BetterSqlite3.Statement = this._db.prepare(\n `DELETE FROM \"${this._table}\" WHERE target_key = ?`\n );\n const ins: BetterSqlite3.Statement = this._db.prepare(\n `INSERT INTO \"${this._table}\"(target_key, embedding) VALUES (?, ?)`\n );\n // vec0 rejects INSERT OR REPLACE on a TEXT primary key, so replace is a\n // delete-then-insert inside a single transaction.\n const replaceTxn: BetterSqlite3.Transaction<(key: string, blob: Uint8Array) => void> =\n this._db.transaction((key: string, blob: Uint8Array) => {\n del.run(key);\n ins.run(key, blob);\n });\n return {\n delete: del,\n replace: (key: string, blob: Uint8Array): void => {\n replaceTxn(key, blob);\n },\n query: this._db.prepare(\n `SELECT target_key, distance FROM \"${this._table}\" WHERE embedding MATCH ? AND k = ?`\n ),\n count: this._db.prepare(`SELECT count(*) AS c FROM \"${this._table}\"`),\n // `LIMIT 1` rather than a count: membership needs existence, not cardinality,\n // and vec0 can stop at the first row.\n has: this._db.prepare(`SELECT 1 FROM \"${this._table}\" WHERE target_key = ? LIMIT 1`)\n };\n }\n\n /**\n * Recover the established dimension of an existing `vec0` table from its stored\n * `CREATE VIRTUAL TABLE` SQL (`float[<n>]`). Returns `undefined` when the table\n * does not exist yet (a fresh database — dimension is set by the first `add`).\n */\n private static _readExistingDimension(db: BetterSqlite3.Database, table: string): number | undefined {\n const row: { sql: string } | undefined = db\n .prepare(\"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?\")\n .get(table) as { sql: string } | undefined;\n if (row === undefined) {\n return undefined;\n }\n const match: RegExpMatchArray | null = row.sql.match(/float\\[(\\d+)\\]/);\n return match === null ? undefined : Number(match[1]);\n }\n\n /** Pack a `Float32Array` as the little-endian byte blob `vec0` stores. Copies, so the caller may reuse its buffer. */\n private static _toBlob(vector: Float32Array): Uint8Array {\n return new Uint8Array(Float32Array.from(vector).buffer);\n }\n\n /**\n * Reverse `edgeTargetKey` — the canonical key is `scope\\0id` with NUL\n * excluded from both components, so the first NUL splits it unambiguously.\n */\n private static _parseKey(key: string): IEdgeTarget {\n const nul: number = key.indexOf('\\0');\n return {\n scope: key.slice(0, nul) as unknown as MemoryScopeKey,\n id: key.slice(nul + 1) as unknown as MemoryId\n };\n }\n}\n\n/** The prepared statements / helpers the index reuses once its table exists. */\ninterface ISqliteVecStatements {\n readonly delete: BetterSqlite3.Statement;\n readonly replace: (key: string, blob: Uint8Array) => void;\n readonly query: BetterSqlite3.Statement;\n readonly count: BetterSqlite3.Statement;\n readonly has: BetterSqlite3.Statement;\n}\n"]}
1
+ {"version":3,"file":"sqliteVecVectorIndex.js","sourceRoot":"","sources":["../../../src/packlets/sqlite-vec-index/sqliteVecVectorIndex.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { load as loadSqliteVec } from 'sqlite-vec';\nimport {\n DetailedResult,\n Result,\n captureResult,\n fail,\n failWithDetail,\n succeed,\n succeedWithDetail\n} from '@fgv/ts-utils';\nimport {\n IEdgeTarget,\n IMemoryRecordListing,\n IMemoryRecordSource,\n ISkippedVectorRecord,\n IVectorIndex,\n IVectorQueryHit,\n IVectorRebuildOptions,\n IVectorRebuildReport,\n Kind,\n MemoryEmbedder,\n MemoryId,\n MemoryScopeKey,\n edgeTargetKey\n} from '@fgv/ts-agent-memory';\nimport { invokeHook, tally, withRollbackNote } from './rebuildHelpers';\nimport { closeOwnedConnection, openOwnedConnection } from './connection';\nimport {\n ISqliteVecVectorIndexCreateParams,\n ISqliteVecVectorIndexHandle,\n ISqliteVecVectorIndexOpenParams\n} from './model';\n\n/** Default name for the `vec0` virtual table. */\nconst DEFAULT_TABLE_NAME: string = 'memory_vectors';\n\n/** Package-facing prefix for this class's failure messages. */\nconst LABEL: string = 'sqlite-vec index';\n\n/** A simple SQL identifier — the only shape allowed for the table name (it is interpolated into DDL). */\nconst IDENTIFIER_RE: RegExp = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/** One KNN row as returned by the `vec0` MATCH query. */\ninterface IKnnRow {\n readonly target_key: string;\n readonly distance: number;\n}\n\n/**\n * A persistent, `sqlite-vec`-backed `IVectorIndex` for `@fgv/ts-agent-memory`.\n *\n * @remarks\n * This is the **durable** counterpart to the in-memory `InMemoryCosineIndex`:\n * embeddings live in a `sqlite-vec` `vec0` virtual table inside a `better-sqlite3`\n * database, so they survive a process restart. A consumer that wires this index\n * into `FileTreeMemoryStore` (instead of the in-memory index) opens an existing\n * vault **without re-embedding it** — the vectors are already on disk. New writes\n * still flow through the store's incremental embed-on-write path; there is no core\n * store change.\n *\n * The index is keyed by the canonical `edgeTargetKey` of each record's\n * scope-qualified `(scope, id)` address (a `TEXT PRIMARY KEY` on the `vec0` table),\n * so two records that share a filename stem across scopes never collide. The\n * dimension is established by the first `add` (the `vec0` column is fixed-width) and\n * recovered from the table schema when a persistent file is reopened; every later\n * `add`/`query` must match it or fail loudly, exactly as the in-memory index does.\n * Similarity is cosine (`distance_metric=cosine`): the returned `score` is\n * `1 - cosineDistance`, i.e. cosine similarity in `[-1, 1]`, higher = more similar —\n * byte-for-byte the same scoring contract as `InMemoryCosineIndex`.\n *\n * Query is a brute-force `vec0` KNN scan (not an ANN structure): correct and\n * durable, appropriate for the same \"thousands of records\" regime the in-memory\n * index targets. Large-N ANN indexing is explicitly out of scope — see the README.\n *\n * **Connection ownership depends on which factory you use.** With\n * {@link SqliteVecVectorIndex.create} the `Database` is consumer-owned\n * (bring-your-own): this index loads the `sqlite-vec` extension onto it and\n * reads/writes the table, but never opens or closes the connection — and that is\n * the seam for backing a record index and a fragment index with one connection.\n * With {@link SqliteVecVectorIndex.open} this package opens the file itself and\n * hands back a handle carrying the disposer for the connection it created.\n * @public\n */\nexport class SqliteVecVectorIndex implements IVectorIndex {\n private readonly _db: BetterSqlite3.Database;\n private readonly _table: string;\n /** The dimension of every stored vector; `undefined` until the table exists (first `add` or a reopened non-empty file). */\n private _dimension: number | undefined;\n /** Prepared statements; created once the table exists (established or recovered). */\n private _stmts: ISqliteVecStatements | undefined;\n /**\n * Set by {@link SqliteVecVectorIndex.release}. Distinct from `_stmts === undefined`,\n * which means *no dimension established yet* — see the remarks on `release`.\n */\n private _released: boolean;\n\n private constructor(db: BetterSqlite3.Database, table: string, dimension: number | undefined) {\n this._db = db;\n this._table = table;\n this._dimension = dimension;\n this._released = false;\n this._stmts = dimension === undefined ? undefined : this._prepare();\n }\n\n /**\n * The number of vectors currently held. Zero before the first `add`.\n *\n * @remarks\n * **Throws on a released index**, where every other member returns a `Failure` —\n * because `IVectorIndex` declares this a synchronous `number` and there is no\n * `Result` to fail into. Throwing preserves the behaviour a released index had\n * before it had an explicit released state (the underlying statement threw\n * against the closed connection); the alternative, answering `0`, would be a\n * confident lie indistinguishable from an empty index.\n */\n public get size(): number {\n this._assertUsable('read size');\n if (this._stmts === undefined) {\n return 0;\n }\n // `Number(...)` narrows the count in case the consumer enabled better-sqlite3\n // safe-integer mode (`db.defaultSafeIntegers(true)`), which returns `count(*)`\n // as a `bigint`. Without it a `bigint` leaks through a `number`-typed contract\n // member — and now through `IIndexCoverage.indexSize`, which is also declared\n // `number`, so the coverage report would carry a value of the wrong runtime\n // type. `SqliteVecFragmentIndex`'s two counts have always converted; this one\n // was the outlier.\n return Number((this._stmts.count.get() as { c: number | bigint }).c);\n }\n\n /**\n * Family-convention factory. Loads the `sqlite-vec` extension onto the supplied\n * `better-sqlite3` connection and, if the vector table already exists (a reopened\n * persistent file), recovers its established dimension so no re-embedding is\n * needed on open.\n *\n * @param params - See {@link ISqliteVecVectorIndexCreateParams}.\n * @returns `Success` with the index, or `Failure` if the table name is not a\n * simple identifier or the extension fails to load.\n */\n public static create(params: ISqliteVecVectorIndexCreateParams): Promise<Result<SqliteVecVectorIndex>> {\n const table: string = params.tableName ?? DEFAULT_TABLE_NAME;\n if (!IDENTIFIER_RE.test(table)) {\n return Promise.resolve(fail(`sqlite-vec index: table name '${table}' is not a simple SQL identifier`));\n }\n return Promise.resolve(\n captureResult(() => {\n loadSqliteVec(params.database);\n const dimension: number | undefined = SqliteVecVectorIndex._readExistingDimension(\n params.database,\n table\n );\n return new SqliteVecVectorIndex(params.database, table, dimension);\n }).withErrorFormat((e) => `sqlite-vec index: failed to initialize: ${e}`)\n );\n }\n\n /**\n * Path-based factory. Opens the database file itself and returns the index\n * together with a disposer for the connection it created.\n *\n * @remarks\n * The convenience over {@link SqliteVecVectorIndex.create} is that the consumer\n * neither value-imports `better-sqlite3` nor re-establishes `Result` discipline\n * around a constructor that throws — this is the one place the package leaked its\n * own dependency into consumer source.\n *\n * **Use `create` instead when one connection must back more than one index** (a\n * record index and a fragment index in the same file, the intended shared-handle\n * case). Two `open` calls on one path give two independent connections, not a\n * shared one.\n *\n * If initialization fails after the file is opened, the connection is closed\n * before returning, so a failed `open` does not leak the descriptor it created.\n * Should that close *itself* fail — the connection is then genuinely leaked — the\n * returned message says so rather than hiding it.\n *\n * @param params - See {@link ISqliteVecVectorIndexOpenParams}.\n * @returns `Success` with a {@link ISqliteVecVectorIndexHandle}, or `Failure` if\n * the driver could not be loaded, the file could not be opened, the table name is\n * not a simple identifier, or the extension fails to load.\n */\n public static async open(\n params: ISqliteVecVectorIndexOpenParams\n ): Promise<Result<ISqliteVecVectorIndexHandle>> {\n return (await openOwnedConnection(params.path, LABEL)).thenOnSuccess(async (database) =>\n (await SqliteVecVectorIndex.create({ database, tableName: params.tableName }))\n .onFailure((message) =>\n // This call opened the connection, so a failure to initialize on top of it\n // must not leave the file handle behind. A close that ALSO fails is said out\n // loud rather than swallowed — the same reasoning, and the same helper, as\n // `withRollbackNote`: silently discarding it would make the \"a failed open\n // leaks nothing\" guarantee untrue exactly when it stopped holding, with no\n // way for a caller to detect it.\n fail(withRollbackNote(message, closeOwnedConnection(database, LABEL)))\n )\n .onSuccess((index) =>\n succeed({\n index,\n close: () => {\n // Drop the statements BEFORE closing, so there is never a moment where\n // a closed connection has live `Statement` objects pointing at it —\n // see `release`.\n index.release();\n return closeOwnedConnection(database, LABEL);\n }\n })\n )\n );\n }\n\n /**\n * Drops this index's prepared statements and marks it unusable. Does **not**\n * touch the connection.\n *\n * @remarks\n * `better-sqlite3` exposes no public `finalize()`, so releasing the last\n * reference to a `Statement` does not finalize it — it makes it collectable\n * *earlier*, while the environment is alive, rather than surviving to process\n * teardown. That narrows the window in which `Statement::~Statement()` runs\n * against a torn-down environment; it is not a proof against it.\n *\n * **Call this before closing a connection you own.** {@link\n * SqliteVecVectorIndex.open}'s handle does it for you. A `create()`-made index\n * holds a connection it does not own and stays structurally incapable of\n * closing it — this method drops only what the index itself allocated, which is\n * why it is safe to expose there.\n *\n * Idempotent. After it, every member fails (or, for `size`, throws) rather than\n * answering: a released index is deliberately distinguishable from one that has\n * simply never had an `add`, whose `_stmts` are also absent but which answers\n * `size === 0` truthfully.\n */\n public release(): void {\n this._released = true;\n this._stmts = undefined;\n }\n\n /**\n * Throw if this index has been released. The one member that calls it and cannot\n * return a `Result` is `size`; the rest convert the throw via `captureResult`.\n */\n private _assertUsable(what: string): void {\n if (this._released) {\n throw new Error(`vector index: cannot ${what}: the index has been released`);\n }\n }\n\n /** {@inheritDoc IVectorIndex.add} */\n public add(target: IEdgeTarget, vector: Float32Array): Promise<Result<string>> {\n const key: string = edgeTargetKey(target);\n if (this._released) {\n return Promise.resolve(fail(`vector index: cannot add '${key}': the index has been released`));\n }\n if (vector.length === 0) {\n return Promise.resolve(fail(`vector index: cannot add '${key}': empty vector`));\n }\n if (this._dimension !== undefined && vector.length !== this._dimension) {\n return Promise.resolve(\n fail(\n `vector index: cannot add '${key}': dimension ${vector.length} does not match index dimension ${this._dimension}`\n )\n );\n }\n return Promise.resolve(\n captureResult(() => {\n if (this._stmts === undefined) {\n this._createTable(vector.length);\n this._dimension = vector.length;\n this._stmts = this._prepare();\n }\n this._stmts.replace(key, SqliteVecVectorIndex._toBlob(vector));\n return key;\n }).withErrorFormat((e) => `vector index: cannot add '${key}': ${e}`)\n );\n }\n\n /** {@inheritDoc IVectorIndex.has} */\n public has(target: IEdgeTarget): Promise<Result<boolean>> {\n return Promise.resolve(\n captureResult(() => {\n this._assertUsable(`check '${edgeTargetKey(target)}'`);\n // Before any add has created the table there is nothing held, which is a\n // truthful `false` rather than an error — same posture as `remove`'s\n // idempotence and `size`'s zero.\n if (this._stmts === undefined) {\n return false;\n }\n return this._stmts.has.get(edgeTargetKey(target)) !== undefined;\n }).withErrorFormat((e) => `vector index: cannot check '${edgeTargetKey(target)}': ${e}`)\n );\n }\n\n /** {@inheritDoc IVectorIndex.remove} */\n public remove(target: IEdgeTarget): Promise<Result<IEdgeTarget>> {\n return Promise.resolve(\n captureResult(() => {\n this._assertUsable(`remove '${edgeTargetKey(target)}'`);\n // Idempotent: removing a target with no embedding (or before any `add`\n // created the table) still succeeds.\n if (this._stmts !== undefined) {\n this._stmts.delete.run(edgeTargetKey(target));\n }\n return target;\n }).withErrorFormat((e) => `vector index: cannot remove '${edgeTargetKey(target)}': ${e}`)\n );\n }\n\n /**\n * Re-embed every record from `source` and rebuild the persisted index — see\n * `IVectorIndex.rebuild` for the mode semantics, which this implementation\n * matches exactly.\n *\n * @remarks\n * **Not atomic, and cannot be.** `better-sqlite3` transactions are synchronous,\n * so one cannot span the `await embed(...)` calls this loop makes — unlike\n * {@link SqliteVecVectorIndex.add}, which wraps its delete-then-insert. The\n * `'fail'` / `'skip'` modes therefore cover only failures JavaScript can catch:\n * a process kill mid-rebuild leaves the table holding neither the old index nor\n * the complete new one, and the remedy is to run `rebuild` again.\n */\n public async rebuild(\n source: IMemoryRecordSource,\n embed: MemoryEmbedder,\n options?: IVectorRebuildOptions\n ): Promise<DetailedResult<IVectorRebuildReport, IVectorRebuildReport>> {\n const lenient: boolean = (options?.onRecordError ?? 'fail') === 'skip';\n // `source` is consumer-supplied, so a throw or rejection becomes a `Failure`\n // here rather than escaping as an exception.\n const listed: Result<IMemoryRecordListing> = await invokeHook(() => source.list());\n if (listed.isFailure()) {\n // Deliberately BEFORE any clear: a failed list is no evidence about the\n // vectors already held, and no re-embedding has been attempted, so there is\n // no half-rebuilt state to protect against. Clearing here would destroy a\n // healthy persisted index over a transient read error. No report either, for\n // the same reason — there is nothing this call disturbed to describe.\n return failWithDetail(`vector index rebuild: failed to list records: ${listed.message}`);\n }\n const cleared: Result<true> = this._clear();\n if (cleared.isFailure()) {\n // Also nothing established: the table still holds whatever it held.\n return failWithDetail(`vector index rebuild: failed to clear the index: ${cleared.message}`);\n }\n const indexed: Map<Kind, number> = new Map<Kind, number>();\n const declined: Map<Kind, number> = new Map<Kind, number>();\n const skipped: ISkippedVectorRecord[] = [];\n // Absent stays absent — only the source knows whether it filtered anything.\n const report = (): IVectorRebuildReport => ({\n indexed,\n declined,\n excluded: listed.value.excluded,\n skipped\n });\n for (const scoped of listed.value.records) {\n const kind: Kind = scoped.record.envelope.kind;\n // Likewise capture-wrapped: an embedder that throws mid-loop would\n // otherwise escape past the `'fail'` rollback below, leaving this DURABLE\n // table holding a partial index that survives the process.\n const embedded: Result<Float32Array | undefined> = await invokeHook(() => embed(scoped.record));\n if (embedded.isFailure()) {\n const error: string = `vector index rebuild: embedding '${edgeTargetKey(scoped.target)}' failed: ${\n embedded.message\n }`;\n if (!lenient) {\n return failWithDetail(withRollbackNote(error, this._clear()), report());\n }\n skipped.push({ target: scoped.target, error });\n continue;\n }\n if (embedded.value === undefined) {\n tally(declined, kind);\n continue;\n }\n const added: Result<string> = await this.add(scoped.target, embedded.value);\n if (added.isFailure()) {\n const error: string = `vector index rebuild: ${added.message}`;\n if (!lenient) {\n return failWithDetail(withRollbackNote(error, this._clear()), report());\n }\n skipped.push({ target: scoped.target, error });\n continue;\n }\n // Tallied in the loop rather than read back off `size` at the end. That\n // `COUNT` was also the only fallible step in assembling the report, so the\n // per-kind tally removes a failure path as well as a rounding of the answer.\n tally(indexed, kind);\n }\n return succeedWithDetail(report());\n }\n\n /**\n * **Empties the rows; does NOT release the table's declared dimension.** That\n * is a `vec0` constraint rather than a choice — the dimension is schema, and\n * there is no `ALTER TABLE` for it — so a rebuild at a new dimension fails\n * here where it would succeed on the in-memory sibling, which forgets its\n * dimension on reset. Changing dimension needs a drop-and-re-index; see the\n * note on `IVectorIndex.rebuild`.\n */\n private _clear(): Result<true> {\n if (this._released) {\n return fail('vector index: cannot clear: the index has been released');\n }\n if (this._stmts === undefined) {\n return succeed(true);\n }\n // Capture-wrapped like `add` / `remove` / `query`: a closed connection or an\n // I/O error here is a `Failure`, not an exception thrown out of a method\n // whose signature promises a `Result`.\n return captureResult(() => this._db.prepare(`DELETE FROM \"${this._table}\"`).run()).onSuccess(() =>\n succeed(true)\n );\n }\n\n /** {@inheritDoc IVectorIndex.query} */\n public query(vector: Float32Array, topK: number): Promise<Result<ReadonlyArray<IVectorQueryHit>>> {\n if (this._released) {\n return Promise.resolve(fail('vector index: cannot query: the index has been released'));\n }\n if (topK <= 0 || this._stmts === undefined) {\n return Promise.resolve(succeed([]));\n }\n if (vector.length !== this._dimension) {\n return Promise.resolve(\n fail(\n `vector index: query dimension ${vector.length} does not match index dimension ${this._dimension}`\n )\n );\n }\n return Promise.resolve(\n captureResult<ReadonlyArray<IVectorQueryHit>>(() => {\n const rows: ReadonlyArray<IKnnRow> = this._stmts!.query.all(\n SqliteVecVectorIndex._toBlob(vector),\n topK\n ) as ReadonlyArray<IKnnRow>;\n // sqlite-vec returns rows in ascending distance (nearest first); score is\n // `1 - cosineDistance` = cosine similarity, so descending score is preserved.\n return rows.map((row) => ({\n target: SqliteVecVectorIndex._parseKey(row.target_key),\n score: 1 - row.distance\n }));\n }).withErrorFormat((e) => `vector index: query failed: ${e}`)\n );\n }\n\n /** Create the `vec0` virtual table with the established dimension. */\n private _createTable(dimension: number): void {\n this._db.exec(\n `CREATE VIRTUAL TABLE IF NOT EXISTS \"${this._table}\" USING vec0(` +\n `target_key TEXT PRIMARY KEY, embedding float[${dimension}] distance_metric=cosine)`\n );\n }\n\n /** Prepare the statements the index reuses. Requires the table to exist. */\n private _prepare(): ISqliteVecStatements {\n const del: BetterSqlite3.Statement = this._db.prepare(\n `DELETE FROM \"${this._table}\" WHERE target_key = ?`\n );\n const ins: BetterSqlite3.Statement = this._db.prepare(\n `INSERT INTO \"${this._table}\"(target_key, embedding) VALUES (?, ?)`\n );\n // vec0 rejects INSERT OR REPLACE on a TEXT primary key, so replace is a\n // delete-then-insert inside a single transaction.\n const replaceTxn: BetterSqlite3.Transaction<(key: string, blob: Uint8Array) => void> =\n this._db.transaction((key: string, blob: Uint8Array) => {\n del.run(key);\n ins.run(key, blob);\n });\n return {\n delete: del,\n replace: (key: string, blob: Uint8Array): void => {\n replaceTxn(key, blob);\n },\n query: this._db.prepare(\n `SELECT target_key, distance FROM \"${this._table}\" WHERE embedding MATCH ? AND k = ?`\n ),\n count: this._db.prepare(`SELECT count(*) AS c FROM \"${this._table}\"`),\n // `LIMIT 1` rather than a count: membership needs existence, not cardinality,\n // and vec0 can stop at the first row.\n has: this._db.prepare(`SELECT 1 FROM \"${this._table}\" WHERE target_key = ? LIMIT 1`)\n };\n }\n\n /**\n * Recover the established dimension of an existing `vec0` table from its stored\n * `CREATE VIRTUAL TABLE` SQL (`float[<n>]`). Returns `undefined` when the table\n * does not exist yet (a fresh database — dimension is set by the first `add`).\n */\n private static _readExistingDimension(db: BetterSqlite3.Database, table: string): number | undefined {\n const row: { sql: string } | undefined = db\n .prepare(\"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?\")\n .get(table) as { sql: string } | undefined;\n if (row === undefined) {\n return undefined;\n }\n const match: RegExpMatchArray | null = row.sql.match(/float\\[(\\d+)\\]/);\n return match === null ? undefined : Number(match[1]);\n }\n\n /** Pack a `Float32Array` as the little-endian byte blob `vec0` stores. Copies, so the caller may reuse its buffer. */\n private static _toBlob(vector: Float32Array): Uint8Array {\n return new Uint8Array(Float32Array.from(vector).buffer);\n }\n\n /**\n * Reverse `edgeTargetKey` — the canonical key is `scope\\0id` with NUL\n * excluded from both components, so the first NUL splits it unambiguously.\n */\n private static _parseKey(key: string): IEdgeTarget {\n const nul: number = key.indexOf('\\0');\n return {\n scope: key.slice(0, nul) as unknown as MemoryScopeKey,\n id: key.slice(nul + 1) as unknown as MemoryId\n };\n }\n}\n\n/** The prepared statements / helpers the index reuses once its table exists. */\ninterface ISqliteVecStatements {\n readonly delete: BetterSqlite3.Statement;\n readonly replace: (key: string, blob: Uint8Array) => void;\n readonly query: BetterSqlite3.Statement;\n readonly count: BetterSqlite3.Statement;\n readonly has: BetterSqlite3.Statement;\n}\n"]}