@fgv/ts-agent-memory-sqlite-vec 5.1.0-42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.rush/temp/689b960e7e1a3b63bd68fbb91474d5c28d567861.tar.log +60 -0
- package/.rush/temp/chunked-rush-logs/ts-agent-memory-sqlite-vec.build.chunks.jsonl +9 -0
- package/.rush/temp/operation/build/all.log +9 -0
- package/.rush/temp/operation/build/log-chunks.jsonl +9 -0
- package/.rush/temp/operation/build/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +720 -0
- package/README.md +109 -0
- package/config/api-extractor.json +38 -0
- package/config/jest.config.json +13 -0
- package/config/rig.json +6 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/packlets/sqlite-vec-index/index.js +8 -0
- package/dist/packlets/sqlite-vec-index/index.js.map +1 -0
- package/dist/packlets/sqlite-vec-index/model.js +6 -0
- package/dist/packlets/sqlite-vec-index/model.js.map +1 -0
- package/dist/packlets/sqlite-vec-index/sqliteVecFragmentIndex.js +277 -0
- package/dist/packlets/sqlite-vec-index/sqliteVecFragmentIndex.js.map +1 -0
- package/dist/packlets/sqlite-vec-index/sqliteVecVectorIndex.js +182 -0
- package/dist/packlets/sqlite-vec-index/sqliteVecVectorIndex.js.map +1 -0
- package/dist/test/unit/sqliteVecFragmentIndex.test.js +352 -0
- package/dist/test/unit/sqliteVecFragmentIndex.test.js.map +1 -0
- package/dist/test/unit/sqliteVecVectorIndex.test.js +199 -0
- package/dist/test/unit/sqliteVecVectorIndex.test.js.map +1 -0
- package/dist/ts-agent-memory-sqlite-vec.d.ts +225 -0
- package/dist/tsdoc-metadata.json +11 -0
- package/eslint.config.js +15 -0
- package/etc/ts-agent-memory-sqlite-vec.api.md +66 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +22 -0
- package/lib/index.js.map +1 -0
- package/lib/packlets/sqlite-vec-index/index.d.ts +4 -0
- package/lib/packlets/sqlite-vec-index/index.d.ts.map +1 -0
- package/lib/packlets/sqlite-vec-index/index.js +24 -0
- package/lib/packlets/sqlite-vec-index/index.js.map +1 -0
- package/lib/packlets/sqlite-vec-index/model.d.ts +48 -0
- package/lib/packlets/sqlite-vec-index/model.d.ts.map +1 -0
- package/lib/packlets/sqlite-vec-index/model.js +7 -0
- package/lib/packlets/sqlite-vec-index/model.js.map +1 -0
- package/lib/packlets/sqlite-vec-index/sqliteVecFragmentIndex.d.ts +94 -0
- package/lib/packlets/sqlite-vec-index/sqliteVecFragmentIndex.d.ts.map +1 -0
- package/lib/packlets/sqlite-vec-index/sqliteVecFragmentIndex.js +281 -0
- package/lib/packlets/sqlite-vec-index/sqliteVecFragmentIndex.js.map +1 -0
- package/lib/packlets/sqlite-vec-index/sqliteVecVectorIndex.d.ts +80 -0
- package/lib/packlets/sqlite-vec-index/sqliteVecVectorIndex.d.ts.map +1 -0
- package/lib/packlets/sqlite-vec-index/sqliteVecVectorIndex.js +186 -0
- package/lib/packlets/sqlite-vec-index/sqliteVecVectorIndex.js.map +1 -0
- package/lib/test/unit/sqliteVecFragmentIndex.test.d.ts +2 -0
- package/lib/test/unit/sqliteVecFragmentIndex.test.d.ts.map +1 -0
- package/lib/test/unit/sqliteVecFragmentIndex.test.js +390 -0
- package/lib/test/unit/sqliteVecFragmentIndex.test.js.map +1 -0
- package/lib/test/unit/sqliteVecVectorIndex.test.d.ts +2 -0
- package/lib/test/unit/sqliteVecVectorIndex.test.d.ts.map +1 -0
- package/lib/test/unit/sqliteVecVectorIndex.test.js +237 -0
- package/lib/test/unit/sqliteVecVectorIndex.test.js.map +1 -0
- package/package.json +87 -0
- package/rush-logs/ts-agent-memory-sqlite-vec.build.cache.log +3 -0
- package/rush-logs/ts-agent-memory-sqlite-vec.build.log +9 -0
- package/src/index.ts +6 -0
- package/src/packlets/sqlite-vec-index/index.ts +8 -0
- package/src/packlets/sqlite-vec-index/model.ts +56 -0
- package/src/packlets/sqlite-vec-index/sqliteVecFragmentIndex.ts +381 -0
- package/src/packlets/sqlite-vec-index/sqliteVecVectorIndex.ts +255 -0
- package/src/test/unit/sqliteVecFragmentIndex.test.ts +466 -0
- package/src/test/unit/sqliteVecVectorIndex.test.ts +253 -0
- package/temp/build/lint/_eslint-5eVG3S6w.json +34 -0
- package/temp/build/typescript/ts_8nwakTlr.json +1 -0
- package/temp/ts-agent-memory-sqlite-vec.api.json +1167 -0
- package/temp/ts-agent-memory-sqlite-vec.api.md +66 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2026 Erik Fortune
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
|
+
};
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
require("@fgv/ts-utils-jest");
|
|
44
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
const os = __importStar(require("os"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const index_1 = require("../../index");
|
|
49
|
+
function target(scope, id) {
|
|
50
|
+
return { scope: scope, id: id };
|
|
51
|
+
}
|
|
52
|
+
function vec(...values) {
|
|
53
|
+
return Float32Array.from(values);
|
|
54
|
+
}
|
|
55
|
+
describe('SqliteVecVectorIndex', () => {
|
|
56
|
+
let db;
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
db = new better_sqlite3_1.default(':memory:');
|
|
59
|
+
});
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
db.close();
|
|
62
|
+
});
|
|
63
|
+
async function makeIndex() {
|
|
64
|
+
return (await index_1.SqliteVecVectorIndex.create({ database: db })).orThrow();
|
|
65
|
+
}
|
|
66
|
+
describe('create', () => {
|
|
67
|
+
test('succeeds over a fresh database with an empty index', async () => {
|
|
68
|
+
expect(await index_1.SqliteVecVectorIndex.create({ database: db })).toSucceedAndSatisfy((index) => {
|
|
69
|
+
expect(index.size).toBe(0);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
test('rejects a table name that is not a simple identifier', async () => {
|
|
73
|
+
expect(await index_1.SqliteVecVectorIndex.create({ database: db, tableName: 'bad name; DROP TABLE x' })).toFailWith(/not a simple SQL identifier/i);
|
|
74
|
+
});
|
|
75
|
+
test('fails loudly when the sqlite-vec extension cannot load (closed database)', async () => {
|
|
76
|
+
const closed = new better_sqlite3_1.default(':memory:');
|
|
77
|
+
closed.close();
|
|
78
|
+
expect(await index_1.SqliteVecVectorIndex.create({ database: closed })).toFailWith(/failed to initialize/i);
|
|
79
|
+
});
|
|
80
|
+
test('recovers no dimension from a pre-existing non-vec0 table of the same name', async () => {
|
|
81
|
+
// A plain table that happens to share the index's name has no `float[<n>]`
|
|
82
|
+
// in its DDL, so no dimension is recovered — the index opens as if empty.
|
|
83
|
+
db.exec('CREATE TABLE memory_vectors (foo TEXT)');
|
|
84
|
+
expect(await index_1.SqliteVecVectorIndex.create({ database: db })).toSucceedAndSatisfy((index) => {
|
|
85
|
+
expect(index.size).toBe(0);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
describe('add', () => {
|
|
90
|
+
test('adds a vector and returns the canonical scoped key, growing size', async () => {
|
|
91
|
+
const index = await makeIndex();
|
|
92
|
+
expect(await index.add(target('knowledge', 'doc-a'), vec(1, 0, 0))).toSucceedWith('knowledge\0doc-a');
|
|
93
|
+
expect(index.size).toBe(1);
|
|
94
|
+
});
|
|
95
|
+
test('rejects an empty vector', async () => {
|
|
96
|
+
const index = await makeIndex();
|
|
97
|
+
expect(await index.add(target('knowledge', 'doc-a'), vec())).toFailWith(/cannot add 'knowledge\0doc-a': empty vector/i);
|
|
98
|
+
});
|
|
99
|
+
test('rejects a vector whose dimension does not match the established dimension', async () => {
|
|
100
|
+
const index = await makeIndex();
|
|
101
|
+
(await index.add(target('knowledge', 'doc-a'), vec(1, 0, 0))).orThrow();
|
|
102
|
+
expect(await index.add(target('knowledge', 'doc-b'), vec(1, 0))).toFailWith(/dimension 2 does not match index dimension 3/i);
|
|
103
|
+
});
|
|
104
|
+
test('replacing an existing target keeps size stable and the new vector wins the query', async () => {
|
|
105
|
+
const index = await makeIndex();
|
|
106
|
+
(await index.add(target('knowledge', 'doc-a'), vec(1, 0, 0))).orThrow();
|
|
107
|
+
(await index.add(target('knowledge', 'doc-a'), vec(0, 0, 1))).orThrow();
|
|
108
|
+
expect(index.size).toBe(1);
|
|
109
|
+
expect(await index.query(vec(0, 0, 1), 1)).toSucceedAndSatisfy((hits) => {
|
|
110
|
+
expect(hits[0].target).toEqual(target('knowledge', 'doc-a'));
|
|
111
|
+
expect(hits[0].score).toBeCloseTo(1, 5);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
test('two records that share a stem across scopes are distinct entries', async () => {
|
|
115
|
+
const index = await makeIndex();
|
|
116
|
+
(await index.add(target('conversations/a', 'turn-3'), vec(1, 0, 0))).orThrow();
|
|
117
|
+
(await index.add(target('conversations/b', 'turn-3'), vec(0, 1, 0))).orThrow();
|
|
118
|
+
expect(index.size).toBe(2);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe('query', () => {
|
|
122
|
+
async function seeded() {
|
|
123
|
+
const index = await makeIndex();
|
|
124
|
+
(await index.add(target('knowledge', 'x'), vec(1, 0, 0))).orThrow();
|
|
125
|
+
(await index.add(target('knowledge', 'y'), vec(0, 1, 0))).orThrow();
|
|
126
|
+
(await index.add(target('knowledge', 'z'), vec(0.9, 0.1, 0))).orThrow();
|
|
127
|
+
return index;
|
|
128
|
+
}
|
|
129
|
+
test('returns hits in descending cosine-similarity score with round-tripped scoped targets', async () => {
|
|
130
|
+
const index = await seeded();
|
|
131
|
+
expect(await index.query(vec(1, 0, 0), 3)).toSucceedAndSatisfy((hits) => {
|
|
132
|
+
expect(hits.map((h) => h.target.id)).toEqual(['x', 'z', 'y']);
|
|
133
|
+
expect(hits[0].target.scope).toBe('knowledge');
|
|
134
|
+
expect(hits[0].score).toBeCloseTo(1, 5); // identical
|
|
135
|
+
expect(hits[2].score).toBeCloseTo(0, 5); // orthogonal
|
|
136
|
+
// scores are sorted descending
|
|
137
|
+
expect(hits[0].score).toBeGreaterThan(hits[1].score);
|
|
138
|
+
expect(hits[1].score).toBeGreaterThan(hits[2].score);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
test('honors topK', async () => {
|
|
142
|
+
const index = await seeded();
|
|
143
|
+
expect(await index.query(vec(1, 0, 0), 2)).toSucceedAndSatisfy((hits) => {
|
|
144
|
+
expect(hits).toHaveLength(2);
|
|
145
|
+
expect(hits.map((h) => h.target.id)).toEqual(['x', 'z']);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
test('returns empty for topK <= 0', async () => {
|
|
149
|
+
const index = await seeded();
|
|
150
|
+
expect(await index.query(vec(1, 0, 0), 0)).toSucceedWith([]);
|
|
151
|
+
expect(await index.query(vec(1, 0, 0), -5)).toSucceedWith([]);
|
|
152
|
+
});
|
|
153
|
+
test('returns empty before any add (no established dimension)', async () => {
|
|
154
|
+
const index = await makeIndex();
|
|
155
|
+
expect(await index.query(vec(1, 0, 0), 5)).toSucceedWith([]);
|
|
156
|
+
});
|
|
157
|
+
test('rejects a query vector of the wrong dimension', async () => {
|
|
158
|
+
const index = await seeded();
|
|
159
|
+
expect(await index.query(vec(1, 0), 3)).toFailWith(/query dimension 2 does not match index dimension 3/i);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
describe('remove', () => {
|
|
163
|
+
test('removes an entry and shrinks size, returning the target', async () => {
|
|
164
|
+
const index = await makeIndex();
|
|
165
|
+
(await index.add(target('knowledge', 'doc-a'), vec(1, 0, 0))).orThrow();
|
|
166
|
+
(await index.add(target('knowledge', 'doc-b'), vec(0, 1, 0))).orThrow();
|
|
167
|
+
expect(await index.remove(target('knowledge', 'doc-a'))).toSucceedWith(target('knowledge', 'doc-a'));
|
|
168
|
+
expect(index.size).toBe(1);
|
|
169
|
+
expect(await index.query(vec(1, 0, 0), 5)).toSucceedAndSatisfy((hits) => {
|
|
170
|
+
expect(hits.map((h) => h.target.id)).toEqual(['doc-b']);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
test('is idempotent — removing a missing target succeeds', async () => {
|
|
174
|
+
const index = await makeIndex();
|
|
175
|
+
(await index.add(target('knowledge', 'doc-a'), vec(1, 0, 0))).orThrow();
|
|
176
|
+
expect(await index.remove(target('knowledge', 'missing'))).toSucceedWith(target('knowledge', 'missing'));
|
|
177
|
+
expect(index.size).toBe(1);
|
|
178
|
+
});
|
|
179
|
+
test('succeeds before any add (no table yet)', async () => {
|
|
180
|
+
const index = await makeIndex();
|
|
181
|
+
expect(await index.remove(target('knowledge', 'doc-a'))).toSucceedWith(target('knowledge', 'doc-a'));
|
|
182
|
+
expect(index.size).toBe(0);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
describe('custom table name', () => {
|
|
186
|
+
test('two indexes on distinct tables in one database are independent', async () => {
|
|
187
|
+
const a = (await index_1.SqliteVecVectorIndex.create({ database: db, tableName: 'idx_a' })).orThrow();
|
|
188
|
+
const b = (await index_1.SqliteVecVectorIndex.create({ database: db, tableName: 'idx_b' })).orThrow();
|
|
189
|
+
(await a.add(target('s', 'one'), vec(1, 0))).orThrow();
|
|
190
|
+
expect(a.size).toBe(1);
|
|
191
|
+
expect(b.size).toBe(0);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
describe('persistence across reopen (the durability guarantee)', () => {
|
|
195
|
+
let dir;
|
|
196
|
+
let dbPath;
|
|
197
|
+
beforeEach(() => {
|
|
198
|
+
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'svtest-'));
|
|
199
|
+
dbPath = path.join(dir, 'vectors.db');
|
|
200
|
+
});
|
|
201
|
+
afterEach(() => {
|
|
202
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
203
|
+
});
|
|
204
|
+
test('vectors written to a file survive a close + reopen with no re-embed, and the dimension is recovered', async () => {
|
|
205
|
+
// First session: write three embeddings, then close the connection entirely.
|
|
206
|
+
const first = new better_sqlite3_1.default(dbPath);
|
|
207
|
+
const writeIndex = (await index_1.SqliteVecVectorIndex.create({ database: first })).orThrow();
|
|
208
|
+
(await writeIndex.add(target('knowledge', 'x'), vec(1, 0, 0))).orThrow();
|
|
209
|
+
(await writeIndex.add(target('knowledge', 'y'), vec(0, 1, 0))).orThrow();
|
|
210
|
+
(await writeIndex.add(target('conversations/c', 'turn-1'), vec(0.9, 0.1, 0))).orThrow();
|
|
211
|
+
first.close();
|
|
212
|
+
// Second session: a brand-new connection + index over the same file. No add
|
|
213
|
+
// calls (no re-embedding) — the vectors and the established dimension come
|
|
214
|
+
// straight off disk.
|
|
215
|
+
const second = new better_sqlite3_1.default(dbPath);
|
|
216
|
+
const reopened = (await index_1.SqliteVecVectorIndex.create({ database: second })).orThrow();
|
|
217
|
+
try {
|
|
218
|
+
expect(reopened.size).toBe(3);
|
|
219
|
+
expect(await reopened.query(vec(1, 0, 0), 3)).toSucceedAndSatisfy((hits) => {
|
|
220
|
+
expect(hits.map((h) => h.target.id)).toEqual(['x', 'turn-1', 'y']);
|
|
221
|
+
expect(hits[0].score).toBeCloseTo(1, 5);
|
|
222
|
+
// the cross-scope target round-trips its full (scope, id)
|
|
223
|
+
expect(hits[1].target).toEqual(target('conversations/c', 'turn-1'));
|
|
224
|
+
});
|
|
225
|
+
// The recovered dimension is enforced on a post-reopen add.
|
|
226
|
+
expect(await reopened.add(target('knowledge', 'w'), vec(1, 0))).toFailWith(/dimension 2 does not match index dimension 3/i);
|
|
227
|
+
// A matching-dimension add still works and persists incrementally.
|
|
228
|
+
(await reopened.add(target('knowledge', 'w'), vec(0, 0, 1))).orThrow();
|
|
229
|
+
expect(reopened.size).toBe(4);
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
second.close();
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
//# sourceMappingURL=sqliteVecVectorIndex.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqliteVecVectorIndex.test.js","sourceRoot":"","sources":["../../../src/test/unit/sqliteVecVectorIndex.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport '@fgv/ts-utils-jest';\n\nimport BetterSqlite3 from 'better-sqlite3';\nimport * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\nimport { IEdgeTarget, IVectorQueryHit, MemoryId, MemoryScopeKey } from '@fgv/ts-agent-memory';\nimport { SqliteVecVectorIndex } from '../../index';\n\nfunction target(scope: string, id: string): IEdgeTarget {\n return { scope: scope as unknown as MemoryScopeKey, id: id as unknown as MemoryId };\n}\nfunction vec(...values: number[]): Float32Array {\n return Float32Array.from(values);\n}\n\ndescribe('SqliteVecVectorIndex', () => {\n let db: BetterSqlite3.Database;\n\n beforeEach(() => {\n db = new BetterSqlite3(':memory:');\n });\n afterEach(() => {\n db.close();\n });\n\n async function makeIndex(): Promise<SqliteVecVectorIndex> {\n return (await SqliteVecVectorIndex.create({ database: db })).orThrow();\n }\n\n describe('create', () => {\n test('succeeds over a fresh database with an empty index', async () => {\n expect(await SqliteVecVectorIndex.create({ database: db })).toSucceedAndSatisfy(\n (index: SqliteVecVectorIndex) => {\n expect(index.size).toBe(0);\n }\n );\n });\n\n test('rejects a table name that is not a simple identifier', async () => {\n expect(\n await SqliteVecVectorIndex.create({ database: db, tableName: 'bad name; DROP TABLE x' })\n ).toFailWith(/not a simple SQL identifier/i);\n });\n\n test('fails loudly when the sqlite-vec extension cannot load (closed database)', async () => {\n const closed = new BetterSqlite3(':memory:');\n closed.close();\n expect(await SqliteVecVectorIndex.create({ database: closed })).toFailWith(/failed to initialize/i);\n });\n\n test('recovers no dimension from a pre-existing non-vec0 table of the same name', async () => {\n // A plain table that happens to share the index's name has no `float[<n>]`\n // in its DDL, so no dimension is recovered — the index opens as if empty.\n db.exec('CREATE TABLE memory_vectors (foo TEXT)');\n expect(await SqliteVecVectorIndex.create({ database: db })).toSucceedAndSatisfy(\n (index: SqliteVecVectorIndex) => {\n expect(index.size).toBe(0);\n }\n );\n });\n });\n\n describe('add', () => {\n test('adds a vector and returns the canonical scoped key, growing size', async () => {\n const index = await makeIndex();\n expect(await index.add(target('knowledge', 'doc-a'), vec(1, 0, 0))).toSucceedWith('knowledge\\0doc-a');\n expect(index.size).toBe(1);\n });\n\n test('rejects an empty vector', async () => {\n const index = await makeIndex();\n expect(await index.add(target('knowledge', 'doc-a'), vec())).toFailWith(\n /cannot add 'knowledge\\0doc-a': empty vector/i\n );\n });\n\n test('rejects a vector whose dimension does not match the established dimension', async () => {\n const index = await makeIndex();\n (await index.add(target('knowledge', 'doc-a'), vec(1, 0, 0))).orThrow();\n expect(await index.add(target('knowledge', 'doc-b'), vec(1, 0))).toFailWith(\n /dimension 2 does not match index dimension 3/i\n );\n });\n\n test('replacing an existing target keeps size stable and the new vector wins the query', async () => {\n const index = await makeIndex();\n (await index.add(target('knowledge', 'doc-a'), vec(1, 0, 0))).orThrow();\n (await index.add(target('knowledge', 'doc-a'), vec(0, 0, 1))).orThrow();\n expect(index.size).toBe(1);\n expect(await index.query(vec(0, 0, 1), 1)).toSucceedAndSatisfy(\n (hits: ReadonlyArray<IVectorQueryHit>) => {\n expect(hits[0].target).toEqual(target('knowledge', 'doc-a'));\n expect(hits[0].score).toBeCloseTo(1, 5);\n }\n );\n });\n\n test('two records that share a stem across scopes are distinct entries', async () => {\n const index = await makeIndex();\n (await index.add(target('conversations/a', 'turn-3'), vec(1, 0, 0))).orThrow();\n (await index.add(target('conversations/b', 'turn-3'), vec(0, 1, 0))).orThrow();\n expect(index.size).toBe(2);\n });\n });\n\n describe('query', () => {\n async function seeded(): Promise<SqliteVecVectorIndex> {\n const index = await makeIndex();\n (await index.add(target('knowledge', 'x'), vec(1, 0, 0))).orThrow();\n (await index.add(target('knowledge', 'y'), vec(0, 1, 0))).orThrow();\n (await index.add(target('knowledge', 'z'), vec(0.9, 0.1, 0))).orThrow();\n return index;\n }\n\n test('returns hits in descending cosine-similarity score with round-tripped scoped targets', async () => {\n const index = await seeded();\n expect(await index.query(vec(1, 0, 0), 3)).toSucceedAndSatisfy(\n (hits: ReadonlyArray<IVectorQueryHit>) => {\n expect(hits.map((h) => h.target.id)).toEqual(['x', 'z', 'y']);\n expect(hits[0].target.scope).toBe('knowledge');\n expect(hits[0].score).toBeCloseTo(1, 5); // identical\n expect(hits[2].score).toBeCloseTo(0, 5); // orthogonal\n // scores are sorted descending\n expect(hits[0].score).toBeGreaterThan(hits[1].score);\n expect(hits[1].score).toBeGreaterThan(hits[2].score);\n }\n );\n });\n\n test('honors topK', async () => {\n const index = await seeded();\n expect(await index.query(vec(1, 0, 0), 2)).toSucceedAndSatisfy(\n (hits: ReadonlyArray<IVectorQueryHit>) => {\n expect(hits).toHaveLength(2);\n expect(hits.map((h) => h.target.id)).toEqual(['x', 'z']);\n }\n );\n });\n\n test('returns empty for topK <= 0', async () => {\n const index = await seeded();\n expect(await index.query(vec(1, 0, 0), 0)).toSucceedWith([]);\n expect(await index.query(vec(1, 0, 0), -5)).toSucceedWith([]);\n });\n\n test('returns empty before any add (no established dimension)', async () => {\n const index = await makeIndex();\n expect(await index.query(vec(1, 0, 0), 5)).toSucceedWith([]);\n });\n\n test('rejects a query vector of the wrong dimension', async () => {\n const index = await seeded();\n expect(await index.query(vec(1, 0), 3)).toFailWith(\n /query dimension 2 does not match index dimension 3/i\n );\n });\n });\n\n describe('remove', () => {\n test('removes an entry and shrinks size, returning the target', async () => {\n const index = await makeIndex();\n (await index.add(target('knowledge', 'doc-a'), vec(1, 0, 0))).orThrow();\n (await index.add(target('knowledge', 'doc-b'), vec(0, 1, 0))).orThrow();\n expect(await index.remove(target('knowledge', 'doc-a'))).toSucceedWith(target('knowledge', 'doc-a'));\n expect(index.size).toBe(1);\n expect(await index.query(vec(1, 0, 0), 5)).toSucceedAndSatisfy(\n (hits: ReadonlyArray<IVectorQueryHit>) => {\n expect(hits.map((h) => h.target.id)).toEqual(['doc-b']);\n }\n );\n });\n\n test('is idempotent — removing a missing target succeeds', async () => {\n const index = await makeIndex();\n (await index.add(target('knowledge', 'doc-a'), vec(1, 0, 0))).orThrow();\n expect(await index.remove(target('knowledge', 'missing'))).toSucceedWith(\n target('knowledge', 'missing')\n );\n expect(index.size).toBe(1);\n });\n\n test('succeeds before any add (no table yet)', async () => {\n const index = await makeIndex();\n expect(await index.remove(target('knowledge', 'doc-a'))).toSucceedWith(target('knowledge', 'doc-a'));\n expect(index.size).toBe(0);\n });\n });\n\n describe('custom table name', () => {\n test('two indexes on distinct tables in one database are independent', async () => {\n const a = (await SqliteVecVectorIndex.create({ database: db, tableName: 'idx_a' })).orThrow();\n const b = (await SqliteVecVectorIndex.create({ database: db, tableName: 'idx_b' })).orThrow();\n (await a.add(target('s', 'one'), vec(1, 0))).orThrow();\n expect(a.size).toBe(1);\n expect(b.size).toBe(0);\n });\n });\n\n describe('persistence across reopen (the durability guarantee)', () => {\n let dir: string;\n let dbPath: string;\n\n beforeEach(() => {\n dir = fs.mkdtempSync(path.join(os.tmpdir(), 'svtest-'));\n dbPath = path.join(dir, 'vectors.db');\n });\n afterEach(() => {\n fs.rmSync(dir, { recursive: true, force: true });\n });\n\n test('vectors written to a file survive a close + reopen with no re-embed, and the dimension is recovered', async () => {\n // First session: write three embeddings, then close the connection entirely.\n const first = new BetterSqlite3(dbPath);\n const writeIndex = (await SqliteVecVectorIndex.create({ database: first })).orThrow();\n (await writeIndex.add(target('knowledge', 'x'), vec(1, 0, 0))).orThrow();\n (await writeIndex.add(target('knowledge', 'y'), vec(0, 1, 0))).orThrow();\n (await writeIndex.add(target('conversations/c', 'turn-1'), vec(0.9, 0.1, 0))).orThrow();\n first.close();\n\n // Second session: a brand-new connection + index over the same file. No add\n // calls (no re-embedding) — the vectors and the established dimension come\n // straight off disk.\n const second = new BetterSqlite3(dbPath);\n const reopened = (await SqliteVecVectorIndex.create({ database: second })).orThrow();\n try {\n expect(reopened.size).toBe(3);\n expect(await reopened.query(vec(1, 0, 0), 3)).toSucceedAndSatisfy(\n (hits: ReadonlyArray<IVectorQueryHit>) => {\n expect(hits.map((h) => h.target.id)).toEqual(['x', 'turn-1', 'y']);\n expect(hits[0].score).toBeCloseTo(1, 5);\n // the cross-scope target round-trips its full (scope, id)\n expect(hits[1].target).toEqual(target('conversations/c', 'turn-1'));\n }\n );\n // The recovered dimension is enforced on a post-reopen add.\n expect(await reopened.add(target('knowledge', 'w'), vec(1, 0))).toFailWith(\n /dimension 2 does not match index dimension 3/i\n );\n // A matching-dimension add still works and persists incrementally.\n (await reopened.add(target('knowledge', 'w'), vec(0, 0, 1))).orThrow();\n expect(reopened.size).toBe(4);\n } finally {\n second.close();\n }\n });\n });\n});\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fgv/ts-agent-memory-sqlite-vec",
|
|
3
|
+
"version": "5.1.0-42",
|
|
4
|
+
"description": "Result-integration boundary providing a persistent, sqlite-vec-backed IVectorIndex for @fgv/ts-agent-memory (survives restarts — no re-embed on open)",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "dist/ts-agent-memory-sqlite-vec.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"node": {
|
|
10
|
+
"import": "./lib/index.js",
|
|
11
|
+
"require": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"default": {
|
|
14
|
+
"import": "./lib/index.js",
|
|
15
|
+
"require": "./lib/index.js"
|
|
16
|
+
},
|
|
17
|
+
"types": "./dist/ts-agent-memory-sqlite-vec.d.ts"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "heft build --clean",
|
|
22
|
+
"clean": "heft clean",
|
|
23
|
+
"test": "heft test --clean",
|
|
24
|
+
"test-handles": "jest --runInBand --detectOpenHandles",
|
|
25
|
+
"clean-jest": "jest --clear-cache",
|
|
26
|
+
"update-snapshot": "jest --updateSnapshot",
|
|
27
|
+
"coverage": "jest --coverage",
|
|
28
|
+
"lint": "eslint src --ext .ts",
|
|
29
|
+
"fixlint": "eslint src --ext .ts --fix"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"typescript",
|
|
33
|
+
"sqlite-vec",
|
|
34
|
+
"vector-index",
|
|
35
|
+
"embeddings",
|
|
36
|
+
"agent-memory",
|
|
37
|
+
"result"
|
|
38
|
+
],
|
|
39
|
+
"author": "Erik Fortune",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/ErikFortune/fgv/issues"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/ErikFortune/fgv/tree/main/libraries/ts-agent-memory-sqlite-vec#readme",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/ErikFortune/fgv.git"
|
|
48
|
+
},
|
|
49
|
+
"sideEffects": false,
|
|
50
|
+
"dependencies": {},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@fgv/heft-dual-rig": "workspace:*",
|
|
53
|
+
"@fgv/ts-utils": "workspace:*",
|
|
54
|
+
"@fgv/ts-agent-memory": "workspace:*",
|
|
55
|
+
"@fgv/ts-utils-jest": "workspace:*",
|
|
56
|
+
"better-sqlite3": "^12.0.0",
|
|
57
|
+
"sqlite-vec": "^0.1.9",
|
|
58
|
+
"@types/better-sqlite3": "^7.6.11",
|
|
59
|
+
"@microsoft/api-extractor": "^7.55.2",
|
|
60
|
+
"@rushstack/eslint-config": "4.6.4",
|
|
61
|
+
"@rushstack/heft": "1.2.7",
|
|
62
|
+
"@rushstack/heft-jest-plugin": "1.2.6",
|
|
63
|
+
"@rushstack/heft-node-rig": "2.11.27",
|
|
64
|
+
"@types/heft-jest": "1.0.6",
|
|
65
|
+
"@types/jest": "^29.5.14",
|
|
66
|
+
"@types/node": "^20.14.9",
|
|
67
|
+
"@typescript-eslint/eslint-plugin": "^8.52.0",
|
|
68
|
+
"@typescript-eslint/parser": "^8.52.0",
|
|
69
|
+
"eslint": "^9.39.2",
|
|
70
|
+
"eslint-plugin-import": "^2.32.0",
|
|
71
|
+
"eslint-plugin-n": "^17.23.1",
|
|
72
|
+
"eslint-plugin-node": "^11.1.0",
|
|
73
|
+
"eslint-plugin-promise": "^7.2.1",
|
|
74
|
+
"eslint-plugin-tsdoc": "~0.5.2",
|
|
75
|
+
"jest": "^29.7.0",
|
|
76
|
+
"rimraf": "^6.1.2",
|
|
77
|
+
"ts-jest": "^29.4.6",
|
|
78
|
+
"ts-node": "^10.9.2",
|
|
79
|
+
"typescript": "5.9.3"
|
|
80
|
+
},
|
|
81
|
+
"peerDependencies": {
|
|
82
|
+
"@fgv/ts-utils": "workspace:*",
|
|
83
|
+
"@fgv/ts-agent-memory": "workspace:*",
|
|
84
|
+
"better-sqlite3": "^12.0.0",
|
|
85
|
+
"sqlite-vec": "^0.1.9"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Invoking: heft build --clean
|
|
2
|
+
---- build started ----
|
|
3
|
+
[build:typescript] The TypeScript compiler version 5.9.3 is newer than the latest version that was tested with Heft (5.8); it may not work correctly.
|
|
4
|
+
[build:typescript] Using TypeScript version 5.9.3
|
|
5
|
+
[build:lint] Using ESLint version 9.39.5
|
|
6
|
+
[build:api-extractor] Using API Extractor version 7.58.9
|
|
7
|
+
[build:api-extractor] Analysis will use the bundled TypeScript version 5.9.3
|
|
8
|
+
---- build finished (1.357s) ----
|
|
9
|
+
-------------------- Finished (1.36s) --------------------
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Erik Fortune
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type BetterSqlite3 from 'better-sqlite3';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Parameters for {@link SqliteVecVectorIndex.create}.
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
export interface ISqliteVecVectorIndexCreateParams {
|
|
13
|
+
/**
|
|
14
|
+
* A `better-sqlite3` `Database` the consumer owns (bring-your-own, mirroring
|
|
15
|
+
* the boundary-package convention). The consumer opens it (`new Database(path)`
|
|
16
|
+
* for a persistent file, or `new Database(':memory:')` for an ephemeral index)
|
|
17
|
+
* and owns its lifecycle — this index never closes it. `create` loads the
|
|
18
|
+
* `sqlite-vec` extension onto the connection and, if the vector table already
|
|
19
|
+
* exists (a reopened persistent file), recovers its established dimension so no
|
|
20
|
+
* re-embedding is required on open.
|
|
21
|
+
*/
|
|
22
|
+
readonly database: BetterSqlite3.Database;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Name of the `vec0` virtual table that holds the embeddings. Must be a simple
|
|
26
|
+
* SQL identifier (`[A-Za-z_][A-Za-z0-9_]*`). Defaults to `'memory_vectors'`.
|
|
27
|
+
* Supply a distinct name to hold more than one independent index in a single
|
|
28
|
+
* database file.
|
|
29
|
+
*/
|
|
30
|
+
readonly tableName?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parameters for {@link SqliteVecFragmentIndex.create}.
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
export interface ISqliteVecFragmentIndexCreateParams {
|
|
38
|
+
/**
|
|
39
|
+
* A `better-sqlite3` `Database` the consumer owns (bring-your-own, mirroring
|
|
40
|
+
* {@link ISqliteVecVectorIndexCreateParams.database}). The consumer opens it and
|
|
41
|
+
* owns its lifecycle — this index never closes it. `create` loads the
|
|
42
|
+
* `sqlite-vec` extension onto the connection and, if the fragment table already
|
|
43
|
+
* exists (a reopened persistent file), recovers its established dimension so no
|
|
44
|
+
* re-embedding is required on open.
|
|
45
|
+
*/
|
|
46
|
+
readonly database: BetterSqlite3.Database;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Name of the `vec0` virtual table that holds the fragment embeddings. Must be a
|
|
50
|
+
* simple SQL identifier (`[A-Za-z_][A-Za-z0-9_]*`). Defaults to
|
|
51
|
+
* `'memory_fragments'`. Supply a distinct name (distinct from any record-level
|
|
52
|
+
* {@link SqliteVecVectorIndex} table) to hold more than one independent index in
|
|
53
|
+
* a single database file.
|
|
54
|
+
*/
|
|
55
|
+
readonly tableName?: string;
|
|
56
|
+
}
|