@pattern-js/mod-vectors 0.4.0-main.c8eb671

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 (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -0
  3. package/dist/admin.d.ts +20 -0
  4. package/dist/admin.d.ts.map +1 -0
  5. package/dist/admin.js +249 -0
  6. package/dist/admin.js.map +1 -0
  7. package/dist/chunk.d.ts +28 -0
  8. package/dist/chunk.d.ts.map +1 -0
  9. package/dist/chunk.js +74 -0
  10. package/dist/chunk.js.map +1 -0
  11. package/dist/engine-local.d.ts +62 -0
  12. package/dist/engine-local.d.ts.map +1 -0
  13. package/dist/engine-local.js +418 -0
  14. package/dist/engine-local.js.map +1 -0
  15. package/dist/index.d.ts +9 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +9 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/mod.d.ts +21 -0
  20. package/dist/mod.d.ts.map +1 -0
  21. package/dist/mod.js +49 -0
  22. package/dist/mod.js.map +1 -0
  23. package/dist/ops.d.ts +12 -0
  24. package/dist/ops.d.ts.map +1 -0
  25. package/dist/ops.js +138 -0
  26. package/dist/ops.js.map +1 -0
  27. package/dist/service.d.ts +72 -0
  28. package/dist/service.d.ts.map +1 -0
  29. package/dist/service.js +166 -0
  30. package/dist/service.js.map +1 -0
  31. package/dist/types.d.ts +104 -0
  32. package/dist/types.d.ts.map +1 -0
  33. package/dist/types.js +33 -0
  34. package/dist/types.js.map +1 -0
  35. package/docs/index.md +94 -0
  36. package/docs/ops/vectors.chunk.md +8 -0
  37. package/docs/ops/vectors.collection.ensure.md +7 -0
  38. package/docs/ops/vectors.collections.ingest.md +10 -0
  39. package/docs/ops/vectors.collections.list.md +3 -0
  40. package/docs/ops/vectors.collections.search.md +7 -0
  41. package/docs/ops/vectors.delete.md +5 -0
  42. package/docs/ops/vectors.index.md +7 -0
  43. package/docs/ops/vectors.query.md +13 -0
  44. package/docs/ops/vectors.upsert.md +8 -0
  45. package/package.json +53 -0
package/docs/index.md ADDED
@@ -0,0 +1,94 @@
1
+ # Vectors
2
+
3
+ `@pattern-js/mod-vectors` gives your app **embedding collections**: named
4
+ indexes of text (or raw vectors) you can search semantically, by keyword, or
5
+ both at once — the retrieval half of RAG, as ordinary ops on the canvas.
6
+
7
+ ```jsonc
8
+ { "mods": ["@pattern-js/mod-ai", "@pattern-js/mod-vectors"] }
9
+ ```
10
+
11
+ ## Collections declare their model
12
+
13
+ The classic silent RAG bug is indexing with one embedding model and querying
14
+ with another — every score turns to noise and nothing errors. Pattern designs
15
+ it out: a collection **declares its embedding alias** at creation
16
+ (`vectors.collection.ensure`), every embed — index side and query side — goes
17
+ through that alias, and the vector dimensionality **locks on the first
18
+ write**. Re-point the alias at a model with different dims and the next write
19
+ fails with an error that names the alias and the fix (re-index into a fresh
20
+ collection), instead of silently degrading your search.
21
+
22
+ ```
23
+ vectors.collection.ensure { name: "kb", alias: "embeddings", filterables: ["product", "lang"] }
24
+ ```
25
+
26
+ Aliases come from mod-ai's Settings page — `@pattern-js/mod-ai` must be
27
+ installed for text embedding (raw-vector upsert/query works without it).
28
+
29
+ ## Ingesting: chunk → embed → upsert
30
+
31
+ `vectors.index` is the one-node ingestion path: give it
32
+ `docs: [{ id, text, meta? }]`, it splits each doc (recursive character
33
+ splitter — `maxChars` 1200, `overlap` 150, paragraph/sentence separators),
34
+ embeds through the collection's alias, and upserts chunks with ids
35
+ `${docId}#${i}`, each carrying its doc's meta. `vectors.chunk` and
36
+ `vectors.upsert` are the same steps as separate nodes when you want the
37
+ pipeline visible.
38
+
39
+ Writes are **content-hashed**: re-running ingestion over unchanged documents
40
+ embeds nothing and writes nothing — only the diff pays. That's what makes
41
+ "re-index everything on boot" a cheap, idempotent habit.
42
+
43
+ ## Querying: vector, keyword, hybrid
44
+
45
+ `vectors.query` takes `text` (embedded via the collection's alias) or a raw
46
+ `vector`, and ranks in one of three modes:
47
+
48
+ - **`vector`** (default) — cosine similarity.
49
+ - **`keyword`** — exact-word ranking: FTS5 when your Node's sqlite build has
50
+ it (probed at startup), a zero-dependency token-overlap scorer otherwise.
51
+ - **`hybrid`** — both rankings fused with reciprocal-rank fusion (RRF). No
52
+ score calibration, no tuning knob; exact identifiers and paraphrased
53
+ questions both surface.
54
+
55
+ ## Filterable metadata
56
+
57
+ Real corpora carry structure — products, languages, tenants, doc types.
58
+ Declare those meta fields as `filterables` on the collection and they are
59
+ extracted into an indexed side table at write time; `vectors.query`'s
60
+ `filter: { field: value | values[] }` (AND of equality/any-of) then **prunes
61
+ before any scoring touches a vector**. Filtering on an undeclared field is a
62
+ located error naming the field and the fix — never a silently empty result.
63
+
64
+ The pattern for complex structures: keep the canonical record in
65
+ **mod-store** (the truth), index its text here with `docId` + taxonomy fields
66
+ in meta (the index), and compose `vectors.query → store.doc.get` on the
67
+ canvas.
68
+
69
+ ## The local engine, durability, and offload
70
+
71
+ The default engine is honest brute force over Float32 vectors in
72
+ `.pattern-data/vectors.db` (WAL sqlite) — no native extensions, no services,
73
+ fine to roughly a hundred thousand vectors. Because the index is a file, it
74
+ survives restarts AND stays visible to **offloaded** workflows: a worker's
75
+ own service instance opens the same database, where an in-memory index would
76
+ be invisible.
77
+
78
+ Past that scale, the **driver SPI** is the seam: an engine registers
79
+ `{ id, ensureCollection, upsert, query, delete }` in its mod's `ready` (the
80
+ mod-email driver pattern) and receives `{ filter, mode }` to push down.
81
+ `sqlite-vec` is the natural first driver — note it is a NATIVE sqlite
82
+ extension (needs `allowExtension` + platform binaries), which is exactly why
83
+ it ships as a separate driver package rather than in this zero-dependency
84
+ default. pgvector follows the same shape.
85
+
86
+ ## Admin
87
+
88
+ **Data → Vectors** is the whole RAG loop on one page: the collections table
89
+ (alias, dims, filterables, row count, active engine), **Ingest text** (paste
90
+ anything — it's chunked, embedded through the collection's alias and indexed;
91
+ the collection is created on first use; re-pasting the same text is a no-op
92
+ thanks to content-hash dedupe), and **Search** (hybrid by default; scored
93
+ matches with their text and meta). Paste a document, search for a phrase from
94
+ it, read the scores — no curl required.
@@ -0,0 +1,8 @@
1
+ Split text for indexing without a tokenizer dependency: a recursive character
2
+ splitter that prefers paragraph breaks, then line breaks, then sentence ends,
3
+ merges neighbors up to `maxChars` (default 1200) and carries `overlap`
4
+ characters (default 150) across chunk boundaries so answers spanning a cut
5
+ survive retrieval. Wire `text` (one string) or `docs`
6
+ (`[{ id?, text, meta? }]`); chunks come back as `[{ id, text, meta? }]` with
7
+ ids `${docId}#${i}` and the doc's meta on every chunk. Usually you want
8
+ `vectors.index`, which chunks and writes in one node.
@@ -0,0 +1,7 @@
1
+ Declare an embedding collection — idempotent, so it belongs at the head of
2
+ ingestion workflows. The declaration IS the safety: `alias` names the one
3
+ embedding model this collection indexes and queries with, `dims` locks on the
4
+ first write (a later mismatch errors naming the alias), and `filterables`
5
+ lists the meta fields extracted into the indexed side table — the only fields
6
+ `vectors.query` may filter on. Re-declaring updates alias/filterables; dims
7
+ never silently change.
@@ -0,0 +1,10 @@
1
+ The admin's paste-to-RAG op (behind Data → Vectors → **Ingest text**): chunk a
2
+ pasted text, embed it through the collection's declared alias, and upsert —
3
+ creating the collection (with the given `alias`, default `embeddings`) when it
4
+ doesn't exist yet. An EXISTING collection keeps its declared alias; the input
5
+ is ignored on purpose (re-pointing a live collection's embedding model would
6
+ corrupt its space). Without a `docId` the id derives from the text content, so
7
+ re-pasting the same document is a no-op (content-hash dedupe) and pasting an
8
+ edited version re-indexes only the changed chunks. `meta` (a JSON object) is
9
+ stamped on every chunk — filterable when the collection declares the field.
10
+ Privileged: gate the trigger with the `admin` scope.
@@ -0,0 +1,3 @@
1
+ List every vector collection with its embedding alias, locked dims, declared
2
+ filterables, row count and the active engine — the backing op for the admin's
3
+ Data → Vectors page. Privileged: gate the trigger with the `admin` scope.
@@ -0,0 +1,7 @@
1
+ The admin's try-a-query op (behind Data → Vectors → **Search**): top-k scored
2
+ matches from a collection, hybrid by default (vector + keyword, RRF-fused),
3
+ with each match's text (truncated) and meta. This is the smoke test for what
4
+ an ingest actually indexed — paste in one section, search in the next, read
5
+ the scores. For canvas/workflow retrieval use `vectors.query` (config-driven,
6
+ filterable); this op exists so the admin form can pass the collection as an
7
+ input. Privileged: gate the trigger with the `admin` scope.
@@ -0,0 +1,5 @@
1
+ Delete rows by id from a collection (vectors, meta index and keyword index
2
+ together). Returns `{ count }`. Chunked docs delete by their chunk ids
3
+ (`${docId}#${i}`) — list them from a `vectors.query` filtered on the doc's
4
+ meta, or re-index the doc instead: unchanged chunks are skipped, changed ones
5
+ overwrite in place.
@@ -0,0 +1,7 @@
1
+ The one-node RAG ingestion path: chunk → embed → upsert. Wire
2
+ `docs: [{ id, text, meta? }]`; each doc is split (same knobs as
3
+ `vectors.chunk`), embedded through the collection's declared alias, and
4
+ written as chunks `${docId}#${i}` carrying the doc's meta (so declared
5
+ filterables apply to every chunk). Content-hashed like `vectors.upsert`:
6
+ unchanged chunks cost nothing on re-runs. Returns `{ count, chunks }` (rows
7
+ actually written / chunks produced).
@@ -0,0 +1,13 @@
1
+ Search a collection: wire `text` (embedded via the collection's declared
2
+ alias) or a raw `vector`, get `{ matches: [{ id, score, text, meta }] }`.
3
+
4
+ `mode` picks the ranking: `vector` (cosine, the default), `keyword`
5
+ (FTS5 when available, token-overlap otherwise — best for exact identifiers),
6
+ or `hybrid` (both, fused with reciprocal-rank fusion — the safe default when
7
+ queries mix names and natural language).
8
+
9
+ `filter: { field: value | values[] }` is an AND of equality/any-of over the
10
+ collection's DECLARED filterables, pruned through the indexed side table
11
+ BEFORE any scoring — filtering on an undeclared field is a located error, not
12
+ an empty result. Compose `vectors.query → store.doc.get` when the canonical
13
+ record lives in mod-store.
@@ -0,0 +1,8 @@
1
+ Write items into a collection. Each item is `{ id?, text, meta? }` (the text
2
+ embeds through the collection's declared alias — mod-ai must be installed) or
3
+ `{ id, vector, meta? }` (raw vectors skip embedding entirely). Declared
4
+ filterable meta fields are extracted into the indexed side table at write
5
+ time. Writes are content-hashed: items whose id + content match what's stored
6
+ are skipped — no embedding call, no write — so re-running ingestion is cheap
7
+ and idempotent. Returns `{ count, embedded }` (rows written / texts actually
8
+ embedded).
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@pattern-js/mod-vectors",
3
+ "version": "0.4.0-main.c8eb671",
4
+ "description": "Vector search for Pattern — embedding collections that DECLARE their alias and dimensions (query-vs-index model mismatch becomes unrepresentable), filterable metadata, hybrid vector+keyword retrieval with RRF fusion, text chunking, and a zero-dependency sqlite-backed local engine with a driver SPI for sqlite-vec/pgvector later.",
5
+ "keywords": [
6
+ "pattern",
7
+ "mod",
8
+ "vectors",
9
+ "vector-search",
10
+ "embeddings",
11
+ "rag",
12
+ "hybrid-search"
13
+ ],
14
+ "type": "module",
15
+ "license": "MIT",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ }
21
+ },
22
+ "main": "./dist/index.js",
23
+ "types": "./dist/index.d.ts",
24
+ "files": [
25
+ "dist",
26
+ "docs",
27
+ "README.md"
28
+ ],
29
+ "dependencies": {
30
+ "zod": "^4.0.0",
31
+ "@pattern-js/core": "0.4.0-main.c8eb671",
32
+ "@pattern-js/runtime-node": "0.4.0-main.c8eb671"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^22.0.0",
36
+ "typescript": "^5.6.0"
37
+ },
38
+ "engines": {
39
+ "node": ">=22.5"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/makkr-studio/pattern.git",
44
+ "directory": "packages/mod-vectors"
45
+ },
46
+ "homepage": "https://github.com/makkr-studio/pattern#readme",
47
+ "bugs": "https://github.com/makkr-studio/pattern/issues",
48
+ "author": "Makkr Studio (https://github.com/makkr-studio)",
49
+ "scripts": {
50
+ "build": "tsc -p tsconfig.json",
51
+ "typecheck": "tsc -p tsconfig.json --noEmit"
52
+ }
53
+ }