@remit/search-service 0.0.9 → 0.0.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/search-service",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -142,3 +142,58 @@ describe("sqlite-vec store (integration)", { skip: !RUN }, () => {
142
142
  assert.equal(hashes.size, 0);
143
143
  });
144
144
  });
145
+
146
+ /**
147
+ * The SQLITE_VEC_EXTENSION_PATH override branch — how the Alpine/musl backend
148
+ * image loads its own vec0.so instead of the npm package's glibc prebuilt. This
149
+ * suite runs on the glibc dev host, so it points the override at the npm
150
+ * package's own resolved loadable path: it proves the loader short-circuits
151
+ * getLoadablePath() and loads the extension via db.loadExtension(path), then
152
+ * drives the same vec0 read path (create table, upsert, cosine KNN) through it.
153
+ * The musl build of that .so is asserted to dlopen at image-build time in the
154
+ * Dockerfile's sqlite-vec-musl stage.
155
+ */
156
+ describe("sqlite-vec store — SQLITE_VEC_EXTENSION_PATH override (integration)", {
157
+ skip: !RUN,
158
+ }, () => {
159
+ let store: VectorStoreService;
160
+ let previous: string | undefined;
161
+
162
+ before(async () => {
163
+ previous = process.env.SQLITE_VEC_EXTENSION_PATH;
164
+ const { getLoadablePath } = await import("sqlite-vec");
165
+ process.env.SQLITE_VEC_EXTENSION_PATH = getLoadablePath();
166
+ store = createSqliteVectorStore({
167
+ path: ":memory:",
168
+ dimensions: DIMENSIONS,
169
+ });
170
+ });
171
+
172
+ after(async () => {
173
+ await store.close?.();
174
+ if (previous === undefined) {
175
+ delete process.env.SQLITE_VEC_EXTENSION_PATH;
176
+ } else {
177
+ process.env.SQLITE_VEC_EXTENSION_PATH = previous;
178
+ }
179
+ });
180
+
181
+ test("loads the extension from the override path and ranks by cosine", async () => {
182
+ await store.upsert([
183
+ record("o-x", [1, 0, 0, 0], { messageId: "m-ox", contentHash: "hx" }),
184
+ record("o-z", [0.9, 0.1, 0, 0], {
185
+ messageId: "m-oz",
186
+ contentHash: "hz",
187
+ }),
188
+ record("o-y", [0, 1, 0, 0], { messageId: "m-oy", contentHash: "hy" }),
189
+ ]);
190
+
191
+ const matches = await store.query({ vector: [1, 0, 0, 0], topK: 3 });
192
+
193
+ assert.deepEqual(
194
+ matches.map((m) => m.chunkId),
195
+ ["o-x", "o-z", "o-y"],
196
+ );
197
+ assert.ok(matches[0].score > 0.99);
198
+ });
199
+ });
@@ -21,6 +21,24 @@ type SqliteVecModule = {
21
21
  load: (db: Database) => void;
22
22
  };
23
23
 
24
+ // `SQLITE_VEC_EXTENSION_PATH` overrides the npm `sqlite-vec` package's
25
+ // getLoadablePath() with a pre-built loadable extension. That package resolves a
26
+ // glibc-only prebuilt (`sqlite-vec-<platform>/vec0.so`) which cannot dlopen on
27
+ // the Alpine/musl backend image, so that image bakes a musl-compiled `vec0.so`
28
+ // and points this at it. better-sqlite3 derives the entry point from the
29
+ // filename, and the `vec0` basename resolves to `sqlite3_vec_init` (SQLite drops
30
+ // the digit). Unset keeps the npm resolution — the glibc search-index-worker
31
+ // image sets nothing and loads the package unchanged.
32
+ const loadSqliteVec = async (db: Database): Promise<void> => {
33
+ const overridePath = process.env.SQLITE_VEC_EXTENSION_PATH;
34
+ if (overridePath) {
35
+ db.loadExtension(overridePath);
36
+ return;
37
+ }
38
+ const sqliteVec = await runtimeImport<SqliteVecModule>("sqlite-vec");
39
+ sqliteVec.load(db);
40
+ };
41
+
24
42
  /**
25
43
  * vec0 stores each chunk's vector alongside the scalar fields the query path
26
44
  * filters on, so equality / range filters are pushed into the KNN instead of
@@ -127,13 +145,12 @@ export const createSqliteVectorStore = (
127
145
  dbPromise = (async () => {
128
146
  const { default: Database } =
129
147
  await runtimeImport<BetterSqlite3Module>("better-sqlite3");
130
- const sqliteVec = await runtimeImport<SqliteVecModule>("sqlite-vec");
131
148
  if (config.path !== ":memory:") {
132
149
  mkdirSync(dirname(config.path), { recursive: true });
133
150
  }
134
151
  const db = new Database(config.path);
135
152
  db.pragma("journal_mode = WAL");
136
- sqliteVec.load(db);
153
+ await loadSqliteVec(db);
137
154
  db.exec(CREATE_TABLE(dimensions));
138
155
  return db;
139
156
  })();
package/src/types.ts CHANGED
@@ -40,6 +40,8 @@ export interface ChunkMetadata {
40
40
  subject?: string;
41
41
  /** Header-derived category. Stored at index time; absent for pre-enrichment vectors. */
42
42
  category?: MessageCategory;
43
+ /** Normalized `List-Id` header value. Stored at index time so the semantic-arm literal refine can match a `ListId` clause; absent for pre-enrichment vectors or non-list mail. */
44
+ listId?: string;
43
45
  /**
44
46
  * sha256 over the embedding model/version id and the chunk's embeddable text.
45
47
  * Lets a re-index skip an unchanged chunk and re-embed only when content or the