@statewalker/indexer-pglite 0.2.1 → 0.2.2

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 (2) hide show
  1. package/README.md +68 -29
  2. package/package.json +11 -7
package/README.md CHANGED
@@ -1,53 +1,92 @@
1
1
  # @statewalker/indexer-pglite
2
2
 
3
- PGlite-backed implementation of `@statewalker/indexer-api`: full-text search via PostgreSQL's native `tsvector` + GIN index, vector search via [pgvector](https://github.com/pgvector/pgvector) with an HNSW cosine index. Runs entirely in-process (in the browser, Node, or anywhere [PGlite](https://github.com/electric-sql/pglite) does).
3
+ ## What it is
4
4
 
5
- ## Installation
5
+ An `Indexer` stored in [PGlite](https://github.com/electric-sql/pglite) (PostgreSQL compiled to WASM). Full-text sub-indexes use PostgreSQL `tsvector` columns with a GIN index and `ts_rank_cd` scoring; vector sub-indexes use pgvector with an HNSW cosine index. It runs in the browser, in Node and in workers.
6
+
7
+ ## Why it exists
8
+
9
+ It gives real PostgreSQL text search (stemming per language) and an approximate nearest-neighbour vector index without a server. With an IndexedDB or file `dataDir`, data persists without a separate save step.
10
+
11
+ ## How to use
6
12
 
7
13
  ```sh
8
- pnpm add @statewalker/indexer-pglite @electric-sql/pglite
14
+ pnpm add @statewalker/indexer-pglite
9
15
  ```
10
16
 
11
- ## Usage
17
+ `@electric-sql/pglite` and `@electric-sql/pglite-pgvector` are dependencies of this package. Add them to your own `package.json` only if you create the `PGlite` instance yourself.
18
+
19
+ One entry point, `@statewalker/indexer-pglite`:
20
+
21
+ - `createPGLiteIndexer(options?): Promise<Indexer>` — `options.db?: PGlite`.
22
+ - `PGLiteIndexerOptions` — the options type.
23
+
24
+ ## Examples
25
+
26
+ In-memory database owned by the indexer:
12
27
 
13
28
  ```ts
14
29
  import { createPGLiteIndexer } from "@statewalker/indexer-pglite";
30
+ import { newFullTextAccess, setFullTextConfig } from "@statewalker/indexer-fulltext";
15
31
 
16
- // Uses an in-memory PGlite instance by default.
17
32
  const indexer = await createPGLiteIndexer();
18
33
 
19
- // Or pass an existing PGlite (with the `vector` extension already loaded):
20
- // import { PGlite } from "@electric-sql/pglite";
21
- // import { vector } from "@electric-sql/pglite/vector";
22
- // const db = await PGlite.create({ dataDir: "idb://myapp", extensions: { vector } });
23
- // const indexer = await createPGLiteIndexer({ db });
24
-
25
- const index = await indexer.createIndex({
26
- name: "docs",
27
- fulltext: { language: "en" },
28
- vector: { dimensionality: 384, model: "all-MiniLM-L6-v2" },
29
- });
34
+ const params = { name: "docs" };
35
+ setFullTextConfig(params, "q", { language: "en" });
36
+ const index = await indexer.createIndex(params);
37
+
38
+ const fts = newFullTextAccess("q");
39
+ await fts.get(index).addDocument([{ path: "/docs/a", blockId: "1", content: "running dogs" }]);
40
+
41
+ const request = { topK: 10 };
42
+ fts.setQuery(request, { queries: ["run"] }); // stemmed: matches "running"
43
+ for await (const r of index.search(request)) {
44
+ console.log(r.path, r.blockId, fts.getResult(r)?.score); // ts_rank_cd
45
+ }
46
+
47
+ await indexer.close(); // also closes the PGlite instance it created
30
48
  ```
31
49
 
32
- ## Runtime extension
50
+ Your own persistent database:
33
51
 
34
- `createPGLiteIndexer` runs `CREATE EXTENSION IF NOT EXISTS vector` at init. When the factory creates its own PGlite (no `db` passed), it auto-loads the `vector` extension via PGlite's `extensions: { vector }` option; when a caller-supplied `db` is passed, the caller is responsible for having the extension available (pass `extensions: { vector }` to `PGlite.create`).
52
+ ```ts
53
+ import { PGlite } from "@electric-sql/pglite";
54
+ import { vector } from "@electric-sql/pglite-pgvector";
55
+ import { createPGLiteIndexer } from "@statewalker/indexer-pglite";
56
+
57
+ const db = await PGlite.create({ dataDir: "idb://my-app", extensions: { vector } });
58
+ const indexer = await createPGLiteIndexer({ db });
59
+ // indexer.close() does not close db; call db.close() yourself
60
+ ```
61
+
62
+ ## Internals
35
63
 
36
- ## SQL shape
64
+ ### The vector extension must be loaded
65
+
66
+ At startup the indexer runs `CREATE EXTENSION IF NOT EXISTS vector`. When it creates the database itself it passes `extensions: { vector }`. A `PGlite` you pass in must be created with the same extension, otherwise this statement fails and the indexer is not created.
67
+
68
+ ### Tables
69
+
70
+ ```
71
+ __indexer_manifest(name, config)
72
+ idx_<index>_docs(doc_id SERIAL, path UNIQUE) path -> doc_id
73
+ idx_<index>_<sub>_fts(doc_id, block_id, content, content_tsv TSVECTOR GENERATED, metadata) + GIN
74
+ idx_<index>_<sub>_vec(doc_id, block_id, embedding vector(dim)) + HNSW vector_cosine_ops
75
+ ```
37
76
 
38
- Per named index:
77
+ `content_tsv` is a generated column, so the full-text index is always current; there is no rebuild step.
39
78
 
40
- - `idx_<prefix>_docs(doc_id SERIAL PRIMARY KEY, path TEXT UNIQUE)` — path ↔ doc_id mapping.
41
- - `idx_<prefix>_fts(doc_id, block_id, content, content_tsv TSVECTOR GENERATED ..., metadata)` + `USING GIN (content_tsv)`.
42
- - `idx_<prefix>_vec(doc_id, block_id, embedding vector(dim))` + `USING hnsw (embedding vector_cosine_ops)`.
79
+ ### Queries, scores and languages
43
80
 
44
- Search uses `ts_rank_cd` + `to_tsquery(<lang>, ...)` for FTS, and the `<=>` cosine-distance operator for vector search.
81
+ - A full-text query is lowercased, split on whitespace, stripped of tsquery operators (`& | ! ( ) : ' " \`) and joined with `|` (OR). Score is `ts_rank_cd`. With several `queries` in one sub-query, each block keeps its best score across them (scores are not summed).
82
+ - Vector `score` is `1 - (embedding <=> query)` (cosine). Embeddings are sent as text literals cast to `vector(dim)`.
83
+ - `language` accepts ISO-639-1 codes (`en`, `fr`, `de`, `es`, `it`, `pt`, `nl`, `ru`, `sv`, `no`, `da`, `fi`, `hu`, `ro`, `tr`), `simple`, or the matching Postgres config names (`english`, ...). Anything else throws `pglite FTS: unsupported language "<x>" (must be an ISO-639-1 code or a Postgres text-search config name)`. The value is put into SQL, which is why it is checked against this list.
84
+ - Default retrieval depth is `topK = 100` per sub-query.
45
85
 
46
- ## API
86
+ ### Dependencies
47
87
 
48
- - `createPGLiteIndexer(options?)` — returns `Promise<Indexer>`. Options: `db?: PGlite` (bring your own; otherwise a fresh in-memory instance is created, and the returned indexer owns and will close it on `indexer.close()`).
49
- - `PGLiteIndexerOptions` — option type.
88
+ `@electric-sql/pglite` and `@electric-sql/pglite-pgvector` (database and vector extension), `@statewalker/indexer-core` (`createSqlIndexer` and the SQL retrievers), `@statewalker/indexer-api`, `@statewalker/indexer-fulltext`, `@statewalker/indexer-vector`.
50
89
 
51
- ## Related
90
+ ## License
52
91
 
53
- - `@statewalker/indexer-api`, `@electric-sql/pglite`, `pgvector`.
92
+ MIT
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@statewalker/indexer-pglite",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "PGlite-backed indexer implementation: full-text + vector search using in-browser Postgres.",
7
- "homepage": "https://github.com/statewalker/statewalker-indexer",
7
+ "bugs": {
8
+ "url": "https://github.com/statewalker/statewalker-search/issues"
9
+ },
10
+ "homepage": "https://github.com/statewalker/statewalker-search/tree/main/packages/indexer-pglite#readme",
8
11
  "author": {
9
12
  "name": "Mikhail Kotelnikov",
10
13
  "email": "mikhail.kotelnikov@gmail.com"
@@ -12,7 +15,8 @@
12
15
  "license": "MIT",
13
16
  "repository": {
14
17
  "type": "git",
15
- "url": "git+ssh://git@github.com/statewalker/statewalker-indexer.git"
18
+ "url": "git+https://github.com/statewalker/statewalker-search.git",
19
+ "directory": "packages/indexer-pglite"
16
20
  },
17
21
  "exports": {
18
22
  ".": {
@@ -29,10 +33,10 @@
29
33
  "dependencies": {
30
34
  "@electric-sql/pglite": "^0.5.8",
31
35
  "@electric-sql/pglite-pgvector": "^0.0.9",
32
- "@statewalker/indexer-api": "^0.1.1",
33
- "@statewalker/indexer-core": "^0.1.1",
34
- "@statewalker/indexer-fulltext": "^0.1.1",
35
- "@statewalker/indexer-vector": "^0.1.1"
36
+ "@statewalker/indexer-api": "^0.1.2",
37
+ "@statewalker/indexer-core": "^0.1.2",
38
+ "@statewalker/indexer-fulltext": "^0.1.2",
39
+ "@statewalker/indexer-vector": "^0.1.2"
36
40
  },
37
41
  "devDependencies": {
38
42
  "rimraf": "^6.1.3",