sliftutils 1.4.82 → 1.4.84

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/index.d.ts CHANGED
@@ -2372,6 +2372,9 @@ declare module "sliftutils/storage/proxydatabase/ivfEmbeddingDatabase" {
2372
2372
  config: IvfConfig;
2373
2373
  count: number;
2374
2374
  flat: TransactionSetStore<StoredEmbedding>;
2375
+ byRef: {
2376
+ [ref: string]: Uint8Array;
2377
+ };
2375
2378
  steps: {
2376
2379
  [step: string]: boolean;
2377
2380
  };
@@ -2393,8 +2396,9 @@ declare module "sliftutils/storage/proxydatabase/ivfEmbeddingDatabase" {
2393
2396
  probeBudget: number;
2394
2397
  resultCount: number;
2395
2398
  }): SearchHit[] | undefined;
2399
+ export declare function lookupEmbeddings(database: Database<IvfEmbeddingRoot>, refs: string[]): Map<string, StoredEmbedding> | undefined;
2396
2400
  export declare function insertEmbeddings(database: Database<IvfEmbeddingRoot>, items: EmbeddingInput[]): undefined;
2397
- export declare function removeEmbeddings(database: Database<IvfEmbeddingRoot>, items: EmbeddingInput[]): void;
2401
+ export declare function removeEmbeddings(database: Database<IvfEmbeddingRoot>, refs: string[]): void;
2398
2402
 
2399
2403
  }
2400
2404
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sliftutils",
3
- "version": "1.4.82",
3
+ "version": "1.4.84",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -33,7 +33,7 @@ async function buildInputs(count: number): Promise<EmbeddingInput[]> {
33
33
  }
34
34
 
35
35
  function freshDatabase(config: IvfConfig): InMemoryDatabase<IvfEmbeddingRoot> {
36
- const root: IvfEmbeddingRoot = { config, count: 0, flat: {}, steps: {}, centroids: {}, cells: {} };
36
+ const root: IvfEmbeddingRoot = { config, count: 0, flat: {}, byRef: {}, steps: {}, centroids: {}, cells: {} };
37
37
  return new InMemoryDatabase<IvfEmbeddingRoot>(root);
38
38
  }
39
39
 
@@ -10,6 +10,9 @@ export type IvfEmbeddingRoot = {
10
10
  config: IvfConfig;
11
11
  count: number;
12
12
  flat: TransactionSetStore<StoredEmbedding>;
13
+ byRef: {
14
+ [ref: string]: Uint8Array;
15
+ };
13
16
  steps: {
14
17
  [step: string]: boolean;
15
18
  };
@@ -31,5 +34,6 @@ export declare function searchEmbeddings(database: Database<IvfEmbeddingRoot>, q
31
34
  probeBudget: number;
32
35
  resultCount: number;
33
36
  }): SearchHit[] | undefined;
37
+ export declare function lookupEmbeddings(database: Database<IvfEmbeddingRoot>, refs: string[]): Map<string, StoredEmbedding> | undefined;
34
38
  export declare function insertEmbeddings(database: Database<IvfEmbeddingRoot>, items: EmbeddingInput[]): undefined;
35
- export declare function removeEmbeddings(database: Database<IvfEmbeddingRoot>, items: EmbeddingInput[]): void;
39
+ export declare function removeEmbeddings(database: Database<IvfEmbeddingRoot>, refs: string[]): void;
@@ -1,3 +1,4 @@
1
+ import cborx from "cbor-x";
1
2
  import { Database, namespaceDatabase } from "./Database";
2
3
  import { TransactionSetStore, transactionRead, transactionMutate, transactionDelete, replayTransactionStore } from "./transactionSet";
3
4
  import { StoredEmbedding, EmbeddingFormat, getCloseness, embeddingToFloat32, releaseFloat32, encodeEmbedding, hashEmbedding } from "../embeddingFormats";
@@ -17,6 +18,10 @@ export type IvfEmbeddingRoot = {
17
18
  config: IvfConfig;
18
19
  count: number;
19
20
  flat: TransactionSetStore<StoredEmbedding>;
21
+ // Direct ref -> embedding index. Each entry is one CBOR blob at its own key, so a lookup or delete is a
22
+ // single key-value op (no transaction-set replay) and deleteData can drop it (it's a primitive leaf). Refs
23
+ // and their embeddings never move, so a rebuild leaves this untouched.
24
+ byRef: { [ref: string]: Uint8Array };
20
25
  steps: { [step: string]: boolean };
21
26
  centroids: TransactionSetStore<StoredEmbedding>;
22
27
  cells: { [cellId: string]: TransactionSetStore<StoredEmbedding> };
@@ -26,6 +31,14 @@ export type EmbeddingInput = { ref: string; embedding: StoredEmbedding };
26
31
  export type SearchHit = { ref: string; closeness: number };
27
32
  type CellEntry = { ref: string; embedding: StoredEmbedding };
28
33
 
34
+ const embeddingCbor = new cborx.Encoder({ structuredClone: true });
35
+ function encodeRefValue(embedding: StoredEmbedding): Uint8Array {
36
+ return embeddingCbor.encode(embedding) as Uint8Array;
37
+ }
38
+ function decodeRefValue(value: Uint8Array): StoredEmbedding {
39
+ return embeddingCbor.decode(value) as StoredEmbedding;
40
+ }
41
+
29
42
  // Below this many embeddings we skip the IVF entirely (flat store). 1024 = a clean power of two.
30
43
  const FLAT_LIMIT = 1024;
31
44
  // Force a from-scratch rebuild once at each of these (multiples of 4 above FLAT_LIMIT, up to ~16k), gated by a step flag so each runs at most once ever.
@@ -239,6 +252,24 @@ export function searchEmbeddings(
239
252
  return hits.slice(0, options.resultCount);
240
253
  }
241
254
 
255
+ // Direct ref -> stored embedding lookup through the byRef index: one key-value read per ref, no cell scan. A
256
+ // search returns refs; this fetches the data behind them. Skips refs that aren't present; undefined if not synced.
257
+ export function lookupEmbeddings(
258
+ database: Database<IvfEmbeddingRoot>,
259
+ refs: string[],
260
+ ): Map<string, StoredEmbedding> | undefined {
261
+ const values = database.readData(root => refs.map(ref => root.byRef[ref]));
262
+ if (!values) return undefined;
263
+ const result = new Map<string, StoredEmbedding>();
264
+ for (let index = 0; index < refs.length; index++) {
265
+ const value = values[index];
266
+ if (value) {
267
+ result.set(refs[index], decodeRefValue(value));
268
+ }
269
+ }
270
+ return result;
271
+ }
272
+
242
273
  export function insertEmbeddings(
243
274
  database: Database<IvfEmbeddingRoot>,
244
275
  items: EmbeddingInput[],
@@ -255,6 +286,9 @@ export function insertEmbeddings(
255
286
  if (!steps[STEP_IVF]) {
256
287
  const flatWrites = items.map(item => ({ key: item.ref, value: item.embedding }));
257
288
  transactionMutate(flatStore(database), flatWrites);
289
+ for (const item of items) {
290
+ database.writeData(root => root.byRef[item.ref], encodeRefValue(item.embedding));
291
+ }
258
292
  database.writeData(root => root.count, newCount);
259
293
  if (newCount > FLAT_LIMIT) {
260
294
  rebuildStructure(database);
@@ -279,6 +313,9 @@ export function insertEmbeddings(
279
313
  const memberWrites = group.map(item => ({ key: item.ref, value: item.embedding }));
280
314
  transactionMutate(cellStore(database, cellId), memberWrites);
281
315
  }
316
+ for (const item of items) {
317
+ database.writeData(root => root.byRef[item.ref], encodeRefValue(item.embedding));
318
+ }
282
319
  database.writeData(root => root.count, newCount);
283
320
 
284
321
 
@@ -305,18 +342,21 @@ export function insertEmbeddings(
305
342
 
306
343
  export function removeEmbeddings(
307
344
  database: Database<IvfEmbeddingRoot>,
308
- items: EmbeddingInput[],
345
+ refs: string[],
309
346
  ) {
310
- if (!items.length) return;
347
+ if (!refs.length) return;
311
348
  const steps = database.readData(root => root.steps);
312
349
  const count = database.readData(root => root.count);
313
350
  if (!steps) return;
314
351
  if (count === undefined) return;
315
352
 
316
353
  if (!steps[STEP_IVF]) {
317
- const flatDeletes = items.map(item => ({ key: item.ref, value: undefined }));
354
+ const flatDeletes = refs.map(ref => ({ key: ref, value: undefined }));
318
355
  transactionMutate(flatStore(database), flatDeletes);
319
- database.writeData(root => root.count, Math.max(0, count - items.length));
356
+ for (const ref of refs) {
357
+ database.deleteData(root => root.byRef[ref]);
358
+ }
359
+ database.writeData(root => root.count, Math.max(0, count - refs.length));
320
360
  return;
321
361
  }
322
362
 
@@ -324,11 +364,20 @@ export function removeEmbeddings(
324
364
  if (!centroids) return;
325
365
  if (!centroids.size) return;
326
366
 
367
+ // Look up each ref's embedding through byRef, then rank its cells the way an insert would, so we delete from
368
+ // the cell it actually landed in (plus a few neighbours, in case a rebuild nudged it across a boundary).
369
+ const refValues = database.readData(root => refs.map(ref => root.byRef[ref]));
370
+ if (!refValues) return;
327
371
  const candidatesByItem: { ref: string; cellIds: string[] }[] = [];
328
372
  const candidateSet = new Set<string>();
329
- for (const item of items) {
330
- const cellIds = rankCellsByCloseness(item.embedding, centroids).slice(0, 1 + DELETE_FALLBACK_CELLS);
331
- candidatesByItem.push({ ref: item.ref, cellIds });
373
+ for (let index = 0; index < refs.length; index++) {
374
+ const value = refValues[index];
375
+ if (!value) {
376
+ continue;
377
+ }
378
+ const embedding = decodeRefValue(value);
379
+ const cellIds = rankCellsByCloseness(embedding, centroids).slice(0, 1 + DELETE_FALLBACK_CELLS);
380
+ candidatesByItem.push({ ref: refs[index], cellIds });
332
381
  for (const cellId of cellIds) {
333
382
  candidateSet.add(cellId);
334
383
  }
@@ -343,30 +392,33 @@ export function removeEmbeddings(
343
392
  }
344
393
 
345
394
  const deletesByCell = new Map<string, string[]>();
395
+ const deletedRefs: string[] = [];
346
396
  for (const candidate of candidatesByItem) {
347
397
  for (const cellId of candidate.cellIds) {
348
398
  const members = membersByCell.get(cellId);
349
399
  if (!members || !members.has(candidate.ref)) {
350
400
  continue;
351
401
  }
352
- let refs = deletesByCell.get(cellId);
353
- if (!refs) {
354
- refs = [];
355
- deletesByCell.set(cellId, refs);
402
+ let cellRefs = deletesByCell.get(cellId);
403
+ if (!cellRefs) {
404
+ cellRefs = [];
405
+ deletesByCell.set(cellId, cellRefs);
356
406
  }
357
- refs.push(candidate.ref);
407
+ cellRefs.push(candidate.ref);
408
+ deletedRefs.push(candidate.ref);
358
409
  break;
359
410
  }
360
411
  }
361
412
 
362
- let deletedCount = 0;
363
413
  for (const cellId of deletesByCell.keys()) {
364
- const refs = deletesByCell.get(cellId)!;
365
- deletedCount += refs.length;
366
- const memberDeletes = refs.map(ref => ({ key: ref, value: undefined }));
414
+ const cellRefs = deletesByCell.get(cellId)!;
415
+ const memberDeletes = cellRefs.map(ref => ({ key: ref, value: undefined }));
367
416
  transactionMutate(cellStore(database, cellId), memberDeletes);
368
417
  }
369
- if (deletedCount) {
370
- database.writeData(root => root.count, Math.max(0, count - deletedCount));
418
+ for (const ref of deletedRefs) {
419
+ database.deleteData(root => root.byRef[ref]);
420
+ }
421
+ if (deletedRefs.length) {
422
+ database.writeData(root => root.count, Math.max(0, count - deletedRefs.length));
371
423
  }
372
424
  }