@zukhruf/embeddings 0.0.0-stage → 0.3.48

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ezzabuzaid
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,98 @@
1
- # Temporary Holding Version
1
+ # @zukhruf/embeddings
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A program that compares texts by meaning needs a vector for each text, and the model call that makes the vector is slow. This package keeps the vectors of documents in a SQLite database. An upsert calls the model only for new or changed text. A search gives the nearest documents of a document, by exact cosine similarity, from the documents published near it. This kind of package is also known as a vector store.
4
+
5
+ The words in these documents have one meaning each. See the glossary in [CONTEXT.md](./CONTEXT.md).
6
+
7
+ ## Use it
8
+
9
+ This project is an experiment. It uses `node:sqlite` and an embedding model of the [AI SDK](https://ai-sdk.dev). The package does not start a model or a server. Give it a model that is ready.
10
+
11
+ ```sh
12
+ npm install @zukhruf/embeddings ai
13
+ ```
14
+
15
+ ```ts
16
+ import { DatabaseSync } from 'node:sqlite';
17
+
18
+ import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
19
+
20
+ import { Collection } from '@zukhruf/embeddings';
21
+
22
+ const local = createOpenAICompatible({
23
+ name: 'llama.cpp',
24
+ baseURL: 'http://127.0.0.1:8080/v1',
25
+ });
26
+ using database = new DatabaseSync('embeddings.sqlite');
27
+ const collection = new Collection({
28
+ database,
29
+ model: local.embeddingModel('embeddinggemma'),
30
+ });
31
+
32
+ await collection.upsert([
33
+ {
34
+ id: 'a',
35
+ text: 'Storm closes the port',
36
+ publishedAt: new Date('2026-10-20'),
37
+ },
38
+ {
39
+ id: 'b',
40
+ text: 'Port shut as storm hits',
41
+ publishedAt: new Date('2026-10-21'),
42
+ },
43
+ { id: 'c', text: 'Port opens again', publishedAt: new Date('2026-11-30') },
44
+ ]);
45
+
46
+ collection.nearest('a', { k: 10, withinDays: 7 }); // [{ id: 'b', similarity: … }]
47
+ collection.similarity('a', 'c');
48
+ ```
49
+
50
+ ### Search a vector table of your own
51
+
52
+ Another program can write the vectors into its own table. A `VectorSearch` searches that table with the same rules as a collection. The table or view must have the columns `id` (text), `vector` and `published_at` (milliseconds since the Unix epoch). Each `vector` must be a unit vector, as a BLOB of float32 values in the byte order of the platform ([ADR 0005](./docs/adr/0005-a-vector-search-reads-a-vector-table-of-unit-vectors.md)).
53
+
54
+ ```ts
55
+ import { DatabaseSync } from 'node:sqlite';
56
+
57
+ import { VectorSearch } from '@zukhruf/embeddings';
58
+
59
+ using database = new DatabaseSync('listings.sqlite');
60
+ const search = new VectorSearch({ database, table: 'listing_vectors' });
61
+
62
+ search.nearest('listing-42', { k: 10, withinDays: 30 });
63
+ ```
64
+
65
+ To search a table with other column names, make a view that renames the columns, and give the name of the view.
66
+
67
+ ## API
68
+
69
+ - `new Collection({ database, model })`. The caller opens and closes `database`. The collection makes its table `embedding_document` in it. `model` is an AI SDK embedding model object.
70
+ - `upsert(documents)` stores each document. It calls the model, through `embedMany`, only for a document whose text or model changed. It stores the new publish date of each document.
71
+ - `nearest(id, { k, withinDays })` gives at most `k` neighbours of the document, with the highest similarity first. A neighbour has a publish date at most `withinDays` days before or after the publish date of the document. Equal similarities sort by id.
72
+ - `neighbourGraph({ from, to, k, withinDays })` gives an iterator of `[id, neighbours]` entries, one for each document published from `from` to `to`, in the order of the publish dates and then of the ids. The neighbours of each entry are equal to the result of `nearest(id, { k, withinDays })`. A document outside the range has no entry, but it can be a neighbour of a document in the range. The call reads and decodes each vector one time ([ADR 0004](./docs/adr/0004-a-neighbour-graph-decodes-each-vector-once.md)). Use it when you need the neighbours of many documents.
73
+ - `similarity(a, b)` gives the cosine similarity of two stored documents.
74
+ - `new VectorSearch({ database, table })`. The caller opens and closes `database`. `table` is the name of a table or a view in it, with the columns `id`, `vector` and `published_at`. The vector search only reads it. Each call reads the table again, so the table can change between two calls.
75
+ - `VectorSearch` gives `nearest`, `neighbourGraph` and `similarity`, with the same rules and results as these calls of a collection. A collection gives these calls to a `VectorSearch` over its table `embedding_document`.
76
+
77
+ ## Errors
78
+
79
+ - `UnknownDocumentError`: `nearest` or `similarity` got an id that the vector table does not contain.
80
+ - `DimensionMismatchError`: the model gave a vector with a dimension that is not the dimension of the stored vectors. The upsert stores none of its documents. To change to a model with another dimension, upsert all documents in one call.
81
+
82
+ ## Limits
83
+
84
+ - One database holds one collection.
85
+ - The collection stores the vector of the text, not the text. The caller chooses the text, for example a title and the first sentences.
86
+ - A document has one vector. A long text gives one mean vector, and its details become a small part of it.
87
+ - A vector search requires unit vectors. It does not examine the length or the dimension of a vector.
88
+ - The search reads each document in the window ([ADR 0001](./docs/adr/0001-search-compares-each-document-in-the-window.md)). A window of many thousand documents is slow. A neighbour graph decodes each vector once, but it still calculates a similarity for each pair of documents in a window.
89
+ - After a model change, upsert all documents again ([ADR 0002](./docs/adr/0002-the-hash-includes-the-model.md)).
90
+
91
+ ## Decisions
92
+
93
+ - [ADR 0001: A search compares each document in the window, with no index](./docs/adr/0001-search-compares-each-document-in-the-window.md)
94
+ - [ADR 0002: The hash includes the model](./docs/adr/0002-the-hash-includes-the-model.md)
95
+ - [ADR 0003: All vectors of a collection have one dimension](./docs/adr/0003-all-vectors-of-a-collection-have-one-dimension.md)
96
+ - [ADR 0004: A neighbour graph decodes each vector once](./docs/adr/0004-a-neighbour-graph-decodes-each-vector-once.md)
97
+ - [ADR 0005: A vector search reads a vector table of unit vectors](./docs/adr/0005-a-vector-search-reads-a-vector-table-of-unit-vectors.md)
98
+ - [Code copied from @deepagents/retrieval](./docs/copied-from-deepagents-retrieval.md)
@@ -0,0 +1,29 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import { type EmbeddingModel } from 'ai';
3
+ import { type Neighbour } from './vector-search.ts';
4
+ export type Document = {
5
+ readonly id: string;
6
+ readonly text: string;
7
+ readonly publishedAt: Date;
8
+ };
9
+ export type CollectionOptions = {
10
+ readonly database: DatabaseSync;
11
+ readonly model: Exclude<EmbeddingModel, string>;
12
+ };
13
+ export declare class Collection {
14
+ #private;
15
+ constructor(options: CollectionOptions);
16
+ upsert(documents: readonly Document[]): Promise<void>;
17
+ nearest(id: string, options: {
18
+ readonly k: number;
19
+ readonly withinDays: number;
20
+ }): Neighbour[];
21
+ neighbourGraph(options: {
22
+ readonly from: Date;
23
+ readonly to: Date;
24
+ readonly k: number;
25
+ readonly withinDays: number;
26
+ }): IteratorObject<[string, Neighbour[]]>;
27
+ similarity(a: string, b: string): number;
28
+ }
29
+ //# sourceMappingURL=collection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAE,KAAK,cAAc,EAAa,MAAM,IAAI,CAAC;AAMpD,OAAO,EAAE,KAAK,SAAS,EAAgB,MAAM,oBAAoB,CAAC;AAGlE,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAEpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAE9B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAGhC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;CACjD,CAAC;AAIF,qBAAa,UAAU;;gBAKT,OAAO,EAAE,iBAAiB;IAYhC,MAAM,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAoD3D,OAAO,CACL,EAAE,EAAE,MAAM,EACV,OAAO,EAAE;QAAE,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAC3D,SAAS,EAAE;IAKd,cAAc,CAAC,OAAO,EAAE;QACtB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;QACpB,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;QAClB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;KAC7B,GAAG,cAAc,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IAKzC,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM;CAwCzC"}
@@ -0,0 +1,94 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { embedMany } from 'ai';
3
+ import { transaction } from '@zukhruf/sqlite-transaction';
4
+ import { DimensionMismatchError } from "./dimension-mismatch-error.js";
5
+ import { collectionSchema } from "./schema.js";
6
+ import { VectorSearch } from "./vector-search.js";
7
+ import { UnitVector } from "./vector.js";
8
+ // The embeddings of documents, one vector for each document, in a SQLite
9
+ // database. The collection searches its table through a VectorSearch.
10
+ export class Collection {
11
+ #database;
12
+ #model;
13
+ #search;
14
+ constructor(options) {
15
+ this.#database = options.database;
16
+ this.#model = options.model;
17
+ collectionSchema.apply(this.#database);
18
+ this.#search = new VectorSearch({
19
+ database: this.#database,
20
+ table: 'embedding_document',
21
+ });
22
+ }
23
+ // Embeds the documents whose text or model changed since their last upsert,
24
+ // and stores the publish date of each document.
25
+ async upsert(documents) {
26
+ const stored = this.#database.prepare('SELECT "hash" FROM "embedding_document" WHERE "id" = ?');
27
+ const hashed = documents.map((document) => ({
28
+ document,
29
+ hash: this.#hash(document.text),
30
+ }));
31
+ const { changed = [], unchanged = [] } = Object.groupBy(hashed, ({ document, hash }) => stored.get(document.id)?.hash === hash ? 'unchanged' : 'changed');
32
+ const vectors = await this.#embed(changed.map(({ document }) => document.text));
33
+ transaction(this.#database, () => {
34
+ const write = this.#database.prepare(`INSERT INTO "embedding_document" ("id", "hash", "published_at", "vector") VALUES (?, ?, ?, ?)
35
+ ON CONFLICT ("id") DO UPDATE SET "hash" = excluded."hash", "published_at" = excluded."published_at", "vector" = excluded."vector"`);
36
+ for (const [index, { document, hash }] of changed.entries()) {
37
+ const vector = vectors[index];
38
+ if (vector === undefined)
39
+ throw new Error(`The model gave ${vectors.length} embeddings for ${changed.length} texts.`);
40
+ write.run(document.id, hash, document.publishedAt.getTime(), vector.toBlob());
41
+ }
42
+ const move = this.#database.prepare('UPDATE "embedding_document" SET "published_at" = ? WHERE "id" = ?');
43
+ for (const { document } of unchanged)
44
+ move.run(document.publishedAt.getTime(), document.id);
45
+ this.#checkDimension(changed.map(({ document }) => document.id), vectors);
46
+ }, { mode: 'immediate' });
47
+ }
48
+ // The neighbours of a document (docs/adr/0005).
49
+ nearest(id, options) {
50
+ return this.#search.nearest(id, options);
51
+ }
52
+ // The neighbours of each document published from `from` to `to`.
53
+ neighbourGraph(options) {
54
+ return this.#search.neighbourGraph(options);
55
+ }
56
+ // The cosine similarity of two stored documents.
57
+ similarity(a, b) {
58
+ return this.#search.similarity(a, b);
59
+ }
60
+ // No texts make no call to the model.
61
+ async #embed(texts) {
62
+ if (texts.length === 0)
63
+ return [];
64
+ const { embeddings } = await embedMany({
65
+ model: this.#model,
66
+ values: texts,
67
+ });
68
+ return embeddings.map((embedding) => UnitVector.normalize(embedding));
69
+ }
70
+ // The model is part of the hash, so a new model embeds the text again.
71
+ #hash(text) {
72
+ return createHash('sha256')
73
+ .update(JSON.stringify([this.#model.provider, this.#model.modelId, text]))
74
+ .digest('hex');
75
+ }
76
+ // Before the upsert, each stored vector has one dimension. The new vectors
77
+ // must have the dimension of a vector that the upsert did not replace, and
78
+ // of each other.
79
+ #checkDimension(replaced, vectors) {
80
+ const [first] = vectors;
81
+ if (first === undefined)
82
+ return;
83
+ const kept = this.#database
84
+ .prepare('SELECT length("vector") AS "bytes" FROM "embedding_document" WHERE "id" NOT IN (SELECT "value" FROM json_each(?)) LIMIT 1')
85
+ .get(JSON.stringify(replaced));
86
+ const expected = kept === undefined
87
+ ? first.dimension
88
+ : Number(kept.bytes) / Float32Array.BYTES_PER_ELEMENT;
89
+ for (const vector of vectors)
90
+ if (vector.dimension !== expected)
91
+ throw new DimensionMismatchError(expected, vector.dimension);
92
+ }
93
+ }
94
+ //# sourceMappingURL=collection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collection.js","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export declare class DimensionMismatchError extends Error {
2
+ readonly expected: number;
3
+ readonly actual: number;
4
+ constructor(expected: number, actual: number);
5
+ }
6
+ //# sourceMappingURL=dimension-mismatch-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dimension-mismatch-error.d.ts","sourceRoot":"","sources":["../src/dimension-mismatch-error.ts"],"names":[],"mappings":"AAEA,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAQ7C"}
@@ -0,0 +1,13 @@
1
+ // The model gave a vector whose dimension is not the dimension of the vectors
2
+ // that the collection stores.
3
+ export class DimensionMismatchError extends Error {
4
+ expected;
5
+ actual;
6
+ constructor(expected, actual) {
7
+ super(`The collection stores vectors of dimension ${expected}, and the model gave a vector of dimension ${actual}. Upsert every document again with the new model, in one call.`);
8
+ this.name = 'DimensionMismatchError';
9
+ this.expected = expected;
10
+ this.actual = actual;
11
+ }
12
+ }
13
+ //# sourceMappingURL=dimension-mismatch-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dimension-mismatch-error.js","sourceRoot":"","sources":["../src/dimension-mismatch-error.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,8BAA8B;AAC9B,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IACtC,QAAQ,CAAS;IACjB,MAAM,CAAS;IAExB,YAAY,QAAgB,EAAE,MAAc;QAC1C,KAAK,CACH,8CAA8C,QAAQ,8CAA8C,MAAM,gEAAgE,CAC3K,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF"}
@@ -0,0 +1,5 @@
1
+ export { Collection, type CollectionOptions, type Document, } from './collection.ts';
2
+ export { DimensionMismatchError } from './dimension-mismatch-error.ts';
3
+ export { UnknownDocumentError } from './unknown-document-error.ts';
4
+ export { type Neighbour, VectorSearch, type VectorSearchOptions, } from './vector-search.ts';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,QAAQ,GACd,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EACL,KAAK,SAAS,EACd,YAAY,EACZ,KAAK,mBAAmB,GACzB,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { Collection, } from "./collection.js";
2
+ export { DimensionMismatchError } from "./dimension-mismatch-error.js";
3
+ export { UnknownDocumentError } from "./unknown-document-error.js";
4
+ export { VectorSearch, } from "./vector-search.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,GAGX,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAEL,YAAY,GAEb,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Declaration } from '@zukhruf/sqlite-drift';
2
+ export declare const collectionSchema: Declaration;
3
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAKpD,eAAO,MAAM,gBAAgB,aAc5B,CAAC"}
package/dist/schema.js ADDED
@@ -0,0 +1,17 @@
1
+ import { Declaration } from '@zukhruf/sqlite-drift';
2
+ // The documents of a collection. Every row derives from a caller's document
3
+ // and the model, so a stale table is replaced with its rows: the next upsert
4
+ // of the caller embeds the documents again (docs/adr/0003).
5
+ export const collectionSchema = new Declaration([
6
+ `CREATE TABLE embedding_document (
7
+ id TEXT PRIMARY KEY,
8
+ -- sha256 of the model's provider and id and the text (docs/adr/0002).
9
+ hash TEXT NOT NULL,
10
+ -- Milliseconds since the Unix epoch.
11
+ published_at INTEGER NOT NULL,
12
+ -- A unit vector of float32 values, in the byte order of the platform.
13
+ vector BLOB NOT NULL
14
+ ) STRICT`,
15
+ `CREATE INDEX embedding_document_published_at ON embedding_document (published_at)`,
16
+ ], { rows: 'replace' });
17
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,4EAA4E;AAC5E,6EAA6E;AAC7E,4DAA4D;AAC5D,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,WAAW,CAC7C;IACE;;;;;;;;SAQK;IACL,mFAAmF;CACpF,EACD,EAAE,IAAI,EAAE,SAAS,EAAE,CACpB,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare class UnknownDocumentError extends Error {
2
+ readonly id: string;
3
+ constructor(id: string);
4
+ }
5
+ //# sourceMappingURL=unknown-document-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unknown-document-error.d.ts","sourceRoot":"","sources":["../src/unknown-document-error.ts"],"names":[],"mappings":"AACA,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;gBAER,EAAE,EAAE,MAAM;CAKvB"}
@@ -0,0 +1,10 @@
1
+ // The vector table has no document with this id.
2
+ export class UnknownDocumentError extends Error {
3
+ id;
4
+ constructor(id) {
5
+ super(`The vector table has no document with the id ${id}.`);
6
+ this.name = 'UnknownDocumentError';
7
+ this.id = id;
8
+ }
9
+ }
10
+ //# sourceMappingURL=unknown-document-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unknown-document-error.js","sourceRoot":"","sources":["../src/unknown-document-error.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IACpC,EAAE,CAAS;IAEpB,YAAY,EAAU;QACpB,KAAK,CAAC,gDAAgD,EAAE,GAAG,CAAC,CAAC;QAC7D,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QACnC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACf,CAAC;CACF"}
@@ -0,0 +1,25 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ export type Neighbour = {
3
+ readonly id: string;
4
+ readonly similarity: number;
5
+ };
6
+ export type VectorSearchOptions = {
7
+ readonly database: DatabaseSync;
8
+ readonly table: string;
9
+ };
10
+ export declare class VectorSearch {
11
+ #private;
12
+ constructor(options: VectorSearchOptions);
13
+ nearest(id: string, options: {
14
+ readonly k: number;
15
+ readonly withinDays: number;
16
+ }): Neighbour[];
17
+ neighbourGraph(options: {
18
+ readonly from: Date;
19
+ readonly to: Date;
20
+ readonly k: number;
21
+ readonly withinDays: number;
22
+ }): IteratorObject<[string, Neighbour[]]>;
23
+ similarity(a: string, b: string): number;
24
+ }
25
+ //# sourceMappingURL=vector-search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vector-search.d.ts","sourceRoot":"","sources":["../src/vector-search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAOhD,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAEhC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAIhC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,CAAC;AAMF,qBAAa,YAAY;;gBAIX,OAAO,EAAE,mBAAmB;IAQxC,OAAO,CACL,EAAE,EAAE,MAAM,EACV,OAAO,EAAE;QAAE,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAC3D,SAAS,EAAE;IAoBd,cAAc,CAAC,OAAO,EAAE;QACtB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;QACpB,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;QAClB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;KAC7B,GAAG,cAAc,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IAgCzC,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM;CAgBzC"}
@@ -0,0 +1,96 @@
1
+ import { UnknownDocumentError } from "./unknown-document-error.js";
2
+ import { UnitVector } from "./vector.js";
3
+ const DAY_MS = 86_400_000;
4
+ // The neighbours of the documents in a vector table. A search compares the
5
+ // document with each document published inside the window, with no index
6
+ // (docs/adr/0001). Each call prepares its statements, so the table can come
7
+ // after the search.
8
+ export class VectorSearch {
9
+ #database;
10
+ #table;
11
+ constructor(options) {
12
+ this.#database = options.database;
13
+ this.#table = `"${options.table.replaceAll('"', '""')}"`;
14
+ }
15
+ // The k documents most similar to the document, by cosine similarity, from
16
+ // those published at most withinDays before or after it. The document
17
+ // itself is not one of them. Equal similarities sort by id.
18
+ nearest(id, options) {
19
+ const { vector, publishedAt } = this.#read(id);
20
+ const window = options.withinDays * DAY_MS;
21
+ const candidates = this.#database
22
+ .prepare(`SELECT "id", "vector" FROM ${this.#table} WHERE "published_at" BETWEEN ? AND ? AND "id" != ?`)
23
+ .all(publishedAt - window, publishedAt + window, id)
24
+ .map((row) => ({
25
+ id: String(row.id),
26
+ vector: UnitVector.fromBlob(blob(row.vector)),
27
+ }));
28
+ return ranked(vector, candidates, options.k);
29
+ }
30
+ // The k nearest neighbours of each document published from `from` to `to`.
31
+ // Each entry is what nearest gives for the document with the same k and
32
+ // withinDays. The call reads and decodes each vector once
33
+ // (docs/adr/0004). The entries come in the order of the publish dates, then
34
+ // of the ids.
35
+ neighbourGraph(options) {
36
+ const from = options.from.getTime();
37
+ const to = options.to.getTime();
38
+ const window = options.withinDays * DAY_MS;
39
+ const documents = this.#database
40
+ .prepare(`SELECT "id", "published_at", "vector" FROM ${this.#table} WHERE "published_at" BETWEEN ? AND ? ORDER BY "published_at", "id"`)
41
+ .all(from - window, to + window)
42
+ .map((row) => ({
43
+ id: String(row.id),
44
+ publishedAt: Number(row.published_at),
45
+ vector: UnitVector.fromBlob(blob(row.vector)),
46
+ }));
47
+ return documents
48
+ .values()
49
+ .filter(({ publishedAt }) => publishedAt >= from && publishedAt <= to)
50
+ .map((document) => [
51
+ document.id,
52
+ ranked(document.vector, documents.filter((other) => other.id !== document.id &&
53
+ Math.abs(other.publishedAt - document.publishedAt) <= window), options.k),
54
+ ]);
55
+ }
56
+ // The cosine similarity of two documents.
57
+ similarity(a, b) {
58
+ return this.#read(a).vector.cosine(this.#read(b).vector);
59
+ }
60
+ #read(id) {
61
+ const row = this.#database
62
+ .prepare(`SELECT "vector", "published_at" FROM ${this.#table} WHERE "id" = ?`)
63
+ .get(id);
64
+ if (row === undefined)
65
+ throw new UnknownDocumentError(id);
66
+ return {
67
+ vector: UnitVector.fromBlob(blob(row.vector)),
68
+ publishedAt: Number(row.published_at),
69
+ };
70
+ }
71
+ }
72
+ // The k candidates most similar to the vector. Equal similarities sort by id.
73
+ function ranked(vector, candidates, k) {
74
+ return candidates
75
+ .map((candidate) => ({
76
+ id: candidate.id,
77
+ similarity: vector.cosine(candidate.vector),
78
+ }))
79
+ .toSorted((left, right) => right.similarity - left.similarity || compareIds(left.id, right.id))
80
+ .slice(0, k);
81
+ }
82
+ // Compares by code unit, not by locale, so the order is the same on every
83
+ // machine.
84
+ function compareIds(left, right) {
85
+ if (left < right)
86
+ return -1;
87
+ if (left > right)
88
+ return 1;
89
+ return 0;
90
+ }
91
+ function blob(value) {
92
+ if (!(value instanceof Uint8Array))
93
+ throw new TypeError('A stored vector must be a BLOB.');
94
+ return value;
95
+ }
96
+ //# sourceMappingURL=vector-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vector-search.js","sourceRoot":"","sources":["../src/vector-search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export declare class UnitVector {
2
+ #private;
3
+ private constructor();
4
+ static normalize(values: readonly number[]): UnitVector;
5
+ static fromBlob(blob: Uint8Array): UnitVector;
6
+ get dimension(): number;
7
+ toBlob(): Uint8Array;
8
+ cosine(other: UnitVector): number;
9
+ }
10
+ //# sourceMappingURL=vector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vector.d.ts","sourceRoot":"","sources":["../src/vector.ts"],"names":[],"mappings":"AAEA,qBAAa,UAAU;;IAGrB,OAAO;IAKP,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU;IAavD,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU;IAI7C,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,MAAM,IAAI,UAAU;IAWpB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM;CAQlC"}
package/dist/vector.js ADDED
@@ -0,0 +1,38 @@
1
+ // A unit vector of float32 values. A dot product of two unit vectors is their
2
+ // cosine similarity.
3
+ export class UnitVector {
4
+ #values;
5
+ constructor(values) {
6
+ this.#values = values;
7
+ }
8
+ // Divides each value by the length of the vector.
9
+ static normalize(values) {
10
+ const length = Math.sqrt(values.reduce((sum, value) => sum + value * value, 0));
11
+ if (length === 0 || !Number.isFinite(length))
12
+ throw new RangeError(`An embedding must have a finite length that is not zero. Its length is ${length}.`);
13
+ return new UnitVector(Float32Array.from(values, (value) => value / length));
14
+ }
15
+ // The bytes that SQLite gives are copied, because a Float32Array needs an
16
+ // offset that is a multiple of 4.
17
+ static fromBlob(blob) {
18
+ return new UnitVector(new Float32Array(Uint8Array.from(blob).buffer));
19
+ }
20
+ get dimension() {
21
+ return this.#values.length;
22
+ }
23
+ toBlob() {
24
+ return new Uint8Array(this.#values.buffer, this.#values.byteOffset, this.#values.byteLength);
25
+ }
26
+ // An indexed loop: an entries() iterator with destructuring made the
27
+ // nearest search of each document in a window of 1,001 documents about two
28
+ // times slower.
29
+ cosine(other) {
30
+ const values = this.#values;
31
+ const others = other.#values;
32
+ let sum = 0;
33
+ for (let index = 0; index < values.length; index++)
34
+ sum += (values[index] ?? 0) * (others[index] ?? 0);
35
+ return sum;
36
+ }
37
+ }
38
+ //# sourceMappingURL=vector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vector.js","sourceRoot":"","sources":["../src/vector.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,qBAAqB;AACrB,MAAM,OAAO,UAAU;IACZ,OAAO,CAAe;IAE/B,YAAoB,MAAoB;QACtC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;IACxB,CAAC;IAED,kDAAkD;IAClD,MAAM,CAAC,SAAS,CAAC,MAAyB;QACxC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CACtB,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,GAAG,KAAK,EAAE,CAAC,CAAC,CACtD,CAAC;QACF,IAAI,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC1C,MAAM,IAAI,UAAU,CAClB,0EAA0E,MAAM,GAAG,CACpF,CAAC;QACJ,OAAO,IAAI,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC;IAC9E,CAAC;IAED,0EAA0E;IAC1E,kCAAkC;IAClC,MAAM,CAAC,QAAQ,CAAC,IAAgB;QAC9B,OAAO,IAAI,UAAU,CAAC,IAAI,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACxE,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;IAC7B,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,UAAU,CACnB,IAAI,CAAC,OAAO,CAAC,MAAM,EACnB,IAAI,CAAC,OAAO,CAAC,UAAU,EACvB,IAAI,CAAC,OAAO,CAAC,UAAU,CACxB,CAAC;IACJ,CAAC;IAED,qEAAqE;IACrE,2EAA2E;IAC3E,gBAAgB;IAChB,MAAM,CAAC,KAAiB;QACtB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5B,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC;QAC7B,IAAI,GAAG,GAAG,CAAC,CAAC;QACZ,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE;YAChD,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACrD,OAAO,GAAG,CAAC;IACb,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,51 @@
1
1
  {
2
2
  "name": "@zukhruf/embeddings",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.48",
4
+ "description": "A collection of document embeddings in SQLite. It embeds only new or changed text through an AI SDK embedding model, and finds the nearest documents of a document by exact cosine similarity inside a publish-date window.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/JanuaryLabs/zukhruf",
9
+ "directory": "packages/embeddings"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/JanuaryLabs/zukhruf/issues"
13
+ },
14
+ "homepage": "https://github.com/JanuaryLabs/zukhruf/tree/main/packages/embeddings#readme",
15
+ "keywords": [
16
+ "embeddings",
17
+ "vector-search",
18
+ "nearest-neighbour",
19
+ "cosine-similarity",
20
+ "sqlite",
21
+ "node-sqlite",
22
+ "ai-sdk"
23
+ ],
24
+ "type": "module",
25
+ "exports": {
26
+ "./package.json": "./package.json",
27
+ ".": {
28
+ "@zukhruf/source": "./src/index.ts",
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js",
31
+ "default": "./dist/index.js"
32
+ }
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "!dist/spec/**",
37
+ "!**/*.tsbuildinfo",
38
+ "!**/*.test.*"
39
+ ],
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "dependencies": {
44
+ "@zukhruf/sqlite-drift": "^0.3.48",
45
+ "@zukhruf/sqlite-transaction": "^0.3.48",
46
+ "ai": "^7.0.154"
47
+ },
48
+ "devDependencies": {
49
+ "@zukhruf/testing": "^0.3.48"
50
+ }
51
+ }