@fgv/ts-agent-memory-sqlite-vec 5.1.0-47 → 5.1.0-49

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 (46) hide show
  1. package/LICENSE +21 -0
  2. package/dist/packlets/sqlite-vec-index/sqliteVecVectorIndex.js +107 -1
  3. package/dist/packlets/sqlite-vec-index/sqliteVecVectorIndex.js.map +1 -1
  4. package/dist/ts-agent-memory-sqlite-vec.d.ts +26 -0
  5. package/lib/packlets/sqlite-vec-index/sqliteVecVectorIndex.d.ts +23 -1
  6. package/lib/packlets/sqlite-vec-index/sqliteVecVectorIndex.d.ts.map +1 -1
  7. package/lib/packlets/sqlite-vec-index/sqliteVecVectorIndex.js +106 -0
  8. package/lib/packlets/sqlite-vec-index/sqliteVecVectorIndex.js.map +1 -1
  9. package/package.json +17 -7
  10. package/.rush/temp/2b1ffec4a34b11a2a7317510ba4142ae814069d2.tar.log +0 -60
  11. package/.rush/temp/chunked-rush-logs/ts-agent-memory-sqlite-vec.build.chunks.jsonl +0 -9
  12. package/.rush/temp/operation/build/all.log +0 -9
  13. package/.rush/temp/operation/build/log-chunks.jsonl +0 -9
  14. package/.rush/temp/operation/build/state.json +0 -3
  15. package/.rush/temp/shrinkwrap-deps.json +0 -720
  16. package/config/api-extractor.json +0 -38
  17. package/config/jest.config.json +0 -13
  18. package/config/rig.json +0 -6
  19. package/dist/test/unit/sqliteVecFragmentIndex.test.js +0 -513
  20. package/dist/test/unit/sqliteVecFragmentIndex.test.js.map +0 -1
  21. package/dist/test/unit/sqliteVecVectorIndex.test.js +0 -199
  22. package/dist/test/unit/sqliteVecVectorIndex.test.js.map +0 -1
  23. package/eslint.config.js +0 -15
  24. package/etc/ts-agent-memory-sqlite-vec.api.md +0 -66
  25. package/lib/test/unit/sqliteVecFragmentIndex.test.d.ts +0 -2
  26. package/lib/test/unit/sqliteVecFragmentIndex.test.d.ts.map +0 -1
  27. package/lib/test/unit/sqliteVecFragmentIndex.test.js +0 -551
  28. package/lib/test/unit/sqliteVecFragmentIndex.test.js.map +0 -1
  29. package/lib/test/unit/sqliteVecVectorIndex.test.d.ts +0 -2
  30. package/lib/test/unit/sqliteVecVectorIndex.test.d.ts.map +0 -1
  31. package/lib/test/unit/sqliteVecVectorIndex.test.js +0 -237
  32. package/lib/test/unit/sqliteVecVectorIndex.test.js.map +0 -1
  33. package/rush-logs/ts-agent-memory-sqlite-vec.build.cache.log +0 -3
  34. package/rush-logs/ts-agent-memory-sqlite-vec.build.log +0 -9
  35. package/src/index.ts +0 -6
  36. package/src/packlets/sqlite-vec-index/index.ts +0 -8
  37. package/src/packlets/sqlite-vec-index/model.ts +0 -56
  38. package/src/packlets/sqlite-vec-index/sqliteVecFragmentIndex.ts +0 -540
  39. package/src/packlets/sqlite-vec-index/sqliteVecVectorIndex.ts +0 -255
  40. package/src/test/unit/sqliteVecFragmentIndex.test.ts +0 -691
  41. package/src/test/unit/sqliteVecVectorIndex.test.ts +0 -253
  42. package/temp/build/lint/_eslint-5eVG3S6w.json +0 -34
  43. package/temp/build/typescript/ts_8nwakTlr.json +0 -1
  44. package/temp/ts-agent-memory-sqlite-vec.api.json +0 -1167
  45. package/temp/ts-agent-memory-sqlite-vec.api.md +0 -66
  46. package/tsconfig.json +0 -8
@@ -1,255 +0,0 @@
1
- /*
2
- * Copyright (c) 2026 Erik Fortune
3
- * SPDX-License-Identifier: MIT
4
- */
5
-
6
- import type BetterSqlite3 from 'better-sqlite3';
7
- import { load as loadSqliteVec } from 'sqlite-vec';
8
- import { Result, captureResult, fail, succeed } from '@fgv/ts-utils';
9
- import {
10
- IEdgeTarget,
11
- IVectorIndex,
12
- IVectorQueryHit,
13
- MemoryId,
14
- MemoryScopeKey,
15
- edgeTargetKey
16
- } from '@fgv/ts-agent-memory';
17
- import { ISqliteVecVectorIndexCreateParams } from './model';
18
-
19
- /** Default name for the `vec0` virtual table. */
20
- const DEFAULT_TABLE_NAME: string = 'memory_vectors';
21
-
22
- /** A simple SQL identifier — the only shape allowed for the table name (it is interpolated into DDL). */
23
- const IDENTIFIER_RE: RegExp = /^[A-Za-z_][A-Za-z0-9_]*$/;
24
-
25
- /** One KNN row as returned by the `vec0` MATCH query. */
26
- interface IKnnRow {
27
- readonly target_key: string;
28
- readonly distance: number;
29
- }
30
-
31
- /**
32
- * A persistent, `sqlite-vec`-backed `IVectorIndex` for `@fgv/ts-agent-memory`.
33
- *
34
- * @remarks
35
- * This is the **durable** counterpart to the in-memory `InMemoryCosineIndex`:
36
- * embeddings live in a `sqlite-vec` `vec0` virtual table inside a `better-sqlite3`
37
- * database, so they survive a process restart. A consumer that wires this index
38
- * into `FileTreeMemoryStore` (instead of the in-memory index) opens an existing
39
- * vault **without re-embedding it** — the vectors are already on disk. New writes
40
- * still flow through the store's incremental embed-on-write path; there is no core
41
- * store change.
42
- *
43
- * The index is keyed by the canonical `edgeTargetKey` of each record's
44
- * scope-qualified `(scope, id)` address (a `TEXT PRIMARY KEY` on the `vec0` table),
45
- * so two records that share a filename stem across scopes never collide. The
46
- * dimension is established by the first `add` (the `vec0` column is fixed-width) and
47
- * recovered from the table schema when a persistent file is reopened; every later
48
- * `add`/`query` must match it or fail loudly, exactly as the in-memory index does.
49
- * Similarity is cosine (`distance_metric=cosine`): the returned `score` is
50
- * `1 - cosineDistance`, i.e. cosine similarity in `[-1, 1]`, higher = more similar —
51
- * byte-for-byte the same scoring contract as `InMemoryCosineIndex`.
52
- *
53
- * Query is a brute-force `vec0` KNN scan (not an ANN structure): correct and
54
- * durable, appropriate for the same "thousands of records" regime the in-memory
55
- * index targets. Large-N ANN indexing is explicitly out of scope — see the README.
56
- *
57
- * The `better-sqlite3` `Database` is consumer-owned (bring-your-own): this index
58
- * loads the `sqlite-vec` extension onto it and reads/writes the table, but never
59
- * opens or closes the connection.
60
- * @public
61
- */
62
- export class SqliteVecVectorIndex implements IVectorIndex {
63
- private readonly _db: BetterSqlite3.Database;
64
- private readonly _table: string;
65
- /** The dimension of every stored vector; `undefined` until the table exists (first `add` or a reopened non-empty file). */
66
- private _dimension: number | undefined;
67
- /** Prepared statements; created once the table exists (established or recovered). */
68
- private _stmts: ISqliteVecStatements | undefined;
69
-
70
- private constructor(db: BetterSqlite3.Database, table: string, dimension: number | undefined) {
71
- this._db = db;
72
- this._table = table;
73
- this._dimension = dimension;
74
- this._stmts = dimension === undefined ? undefined : this._prepare();
75
- }
76
-
77
- /** The number of vectors currently held. Zero before the first `add`. */
78
- public get size(): number {
79
- if (this._stmts === undefined) {
80
- return 0;
81
- }
82
- return (this._stmts.count.get() as { c: number }).c;
83
- }
84
-
85
- /**
86
- * Family-convention factory. Loads the `sqlite-vec` extension onto the supplied
87
- * `better-sqlite3` connection and, if the vector table already exists (a reopened
88
- * persistent file), recovers its established dimension so no re-embedding is
89
- * needed on open.
90
- *
91
- * @param params - See {@link ISqliteVecVectorIndexCreateParams}.
92
- * @returns `Success` with the index, or `Failure` if the table name is not a
93
- * simple identifier or the extension fails to load.
94
- */
95
- public static create(params: ISqliteVecVectorIndexCreateParams): Promise<Result<SqliteVecVectorIndex>> {
96
- const table: string = params.tableName ?? DEFAULT_TABLE_NAME;
97
- if (!IDENTIFIER_RE.test(table)) {
98
- return Promise.resolve(fail(`sqlite-vec index: table name '${table}' is not a simple SQL identifier`));
99
- }
100
- return Promise.resolve(
101
- captureResult(() => {
102
- loadSqliteVec(params.database);
103
- const dimension: number | undefined = SqliteVecVectorIndex._readExistingDimension(
104
- params.database,
105
- table
106
- );
107
- return new SqliteVecVectorIndex(params.database, table, dimension);
108
- }).withErrorFormat((e) => `sqlite-vec index: failed to initialize: ${e}`)
109
- );
110
- }
111
-
112
- /** {@inheritDoc IVectorIndex.add} */
113
- public add(target: IEdgeTarget, vector: Float32Array): Promise<Result<string>> {
114
- const key: string = edgeTargetKey(target);
115
- if (vector.length === 0) {
116
- return Promise.resolve(fail(`vector index: cannot add '${key}': empty vector`));
117
- }
118
- if (this._dimension !== undefined && vector.length !== this._dimension) {
119
- return Promise.resolve(
120
- fail(
121
- `vector index: cannot add '${key}': dimension ${vector.length} does not match index dimension ${this._dimension}`
122
- )
123
- );
124
- }
125
- return Promise.resolve(
126
- captureResult(() => {
127
- if (this._stmts === undefined) {
128
- this._createTable(vector.length);
129
- this._dimension = vector.length;
130
- this._stmts = this._prepare();
131
- }
132
- this._stmts.replace(key, SqliteVecVectorIndex._toBlob(vector));
133
- return key;
134
- }).withErrorFormat((e) => `vector index: cannot add '${key}': ${e}`)
135
- );
136
- }
137
-
138
- /** {@inheritDoc IVectorIndex.remove} */
139
- public remove(target: IEdgeTarget): Promise<Result<IEdgeTarget>> {
140
- return Promise.resolve(
141
- captureResult(() => {
142
- // Idempotent: removing a target with no embedding (or before any `add`
143
- // created the table) still succeeds.
144
- if (this._stmts !== undefined) {
145
- this._stmts.delete.run(edgeTargetKey(target));
146
- }
147
- return target;
148
- }).withErrorFormat((e) => `vector index: cannot remove '${edgeTargetKey(target)}': ${e}`)
149
- );
150
- }
151
-
152
- /** {@inheritDoc IVectorIndex.query} */
153
- public query(vector: Float32Array, topK: number): Promise<Result<ReadonlyArray<IVectorQueryHit>>> {
154
- if (topK <= 0 || this._stmts === undefined) {
155
- return Promise.resolve(succeed([]));
156
- }
157
- if (vector.length !== this._dimension) {
158
- return Promise.resolve(
159
- fail(
160
- `vector index: query dimension ${vector.length} does not match index dimension ${this._dimension}`
161
- )
162
- );
163
- }
164
- return Promise.resolve(
165
- captureResult<ReadonlyArray<IVectorQueryHit>>(() => {
166
- const rows: ReadonlyArray<IKnnRow> = this._stmts!.query.all(
167
- SqliteVecVectorIndex._toBlob(vector),
168
- topK
169
- ) as ReadonlyArray<IKnnRow>;
170
- // sqlite-vec returns rows in ascending distance (nearest first); score is
171
- // `1 - cosineDistance` = cosine similarity, so descending score is preserved.
172
- return rows.map((row) => ({
173
- target: SqliteVecVectorIndex._parseKey(row.target_key),
174
- score: 1 - row.distance
175
- }));
176
- }).withErrorFormat((e) => `vector index: query failed: ${e}`)
177
- );
178
- }
179
-
180
- /** Create the `vec0` virtual table with the established dimension. */
181
- private _createTable(dimension: number): void {
182
- this._db.exec(
183
- `CREATE VIRTUAL TABLE IF NOT EXISTS "${this._table}" USING vec0(` +
184
- `target_key TEXT PRIMARY KEY, embedding float[${dimension}] distance_metric=cosine)`
185
- );
186
- }
187
-
188
- /** Prepare the statements the index reuses. Requires the table to exist. */
189
- private _prepare(): ISqliteVecStatements {
190
- const del: BetterSqlite3.Statement = this._db.prepare(
191
- `DELETE FROM "${this._table}" WHERE target_key = ?`
192
- );
193
- const ins: BetterSqlite3.Statement = this._db.prepare(
194
- `INSERT INTO "${this._table}"(target_key, embedding) VALUES (?, ?)`
195
- );
196
- // vec0 rejects INSERT OR REPLACE on a TEXT primary key, so replace is a
197
- // delete-then-insert inside a single transaction.
198
- const replaceTxn: BetterSqlite3.Transaction<(key: string, blob: Uint8Array) => void> =
199
- this._db.transaction((key: string, blob: Uint8Array) => {
200
- del.run(key);
201
- ins.run(key, blob);
202
- });
203
- return {
204
- delete: del,
205
- replace: (key: string, blob: Uint8Array): void => {
206
- replaceTxn(key, blob);
207
- },
208
- query: this._db.prepare(
209
- `SELECT target_key, distance FROM "${this._table}" WHERE embedding MATCH ? AND k = ?`
210
- ),
211
- count: this._db.prepare(`SELECT count(*) AS c FROM "${this._table}"`)
212
- };
213
- }
214
-
215
- /**
216
- * Recover the established dimension of an existing `vec0` table from its stored
217
- * `CREATE VIRTUAL TABLE` SQL (`float[<n>]`). Returns `undefined` when the table
218
- * does not exist yet (a fresh database — dimension is set by the first `add`).
219
- */
220
- private static _readExistingDimension(db: BetterSqlite3.Database, table: string): number | undefined {
221
- const row: { sql: string } | undefined = db
222
- .prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?")
223
- .get(table) as { sql: string } | undefined;
224
- if (row === undefined) {
225
- return undefined;
226
- }
227
- const match: RegExpMatchArray | null = row.sql.match(/float\[(\d+)\]/);
228
- return match === null ? undefined : Number(match[1]);
229
- }
230
-
231
- /** Pack a `Float32Array` as the little-endian byte blob `vec0` stores. Copies, so the caller may reuse its buffer. */
232
- private static _toBlob(vector: Float32Array): Uint8Array {
233
- return new Uint8Array(Float32Array.from(vector).buffer);
234
- }
235
-
236
- /**
237
- * Reverse `edgeTargetKey` — the canonical key is `scope\0id` with NUL
238
- * excluded from both components, so the first NUL splits it unambiguously.
239
- */
240
- private static _parseKey(key: string): IEdgeTarget {
241
- const nul: number = key.indexOf('\0');
242
- return {
243
- scope: key.slice(0, nul) as unknown as MemoryScopeKey,
244
- id: key.slice(nul + 1) as unknown as MemoryId
245
- };
246
- }
247
- }
248
-
249
- /** The prepared statements / helpers the index reuses once its table exists. */
250
- interface ISqliteVecStatements {
251
- readonly delete: BetterSqlite3.Statement;
252
- readonly replace: (key: string, blob: Uint8Array) => void;
253
- readonly query: BetterSqlite3.Statement;
254
- readonly count: BetterSqlite3.Statement;
255
- }