@statewalker/indexer-mem-flexsearch 0.1.2 → 0.1.4
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/README.md +76 -27
- package/package.json +12 -8
package/README.md
CHANGED
|
@@ -1,55 +1,104 @@
|
|
|
1
1
|
# @statewalker/indexer-mem-flexsearch
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## What it is
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
An in-memory `Indexer` that uses [FlexSearch](https://github.com/nextapps-de/flexsearch) for full-text sub-indexes and `MemVectorIndex` from `@statewalker/indexer-mem` for vector sub-indexes. State can be saved and restored through an `IndexerPersistence` port you provide (files, IndexedDB, a blob store, ...).
|
|
6
|
+
|
|
7
|
+
## Why it exists
|
|
8
|
+
|
|
9
|
+
It needs no database and no native code, so it runs in the browser, in Node and in workers, and starts instantly. Use it for small and medium collections, tests, and offline apps. `@statewalker/indexer-mem-minisearch` is the same indexer with a different full-text engine; see "How the two in-memory engines differ" below.
|
|
10
|
+
|
|
11
|
+
## How to use
|
|
6
12
|
|
|
7
13
|
```sh
|
|
8
14
|
pnpm add @statewalker/indexer-mem-flexsearch
|
|
9
15
|
```
|
|
10
16
|
|
|
11
|
-
|
|
17
|
+
One entry point, `@statewalker/indexer-mem-flexsearch`:
|
|
18
|
+
|
|
19
|
+
- `createFlexSearchIndexer(options?)` returns an `Indexer`. `options.persistence?: IndexerPersistence`.
|
|
20
|
+
- `flexSearchFullTextProvider` — the `FullTextProvider`, for building your own indexer with `createPersistenceBackedIndexer`.
|
|
21
|
+
- `FlexSearchFullTextIndex` — the full-text sub-index class.
|
|
22
|
+
- `FlexSearchIndexerOptions` — the options type.
|
|
23
|
+
|
|
24
|
+
Add `@statewalker/indexer-fulltext` and `@statewalker/indexer-vector` for the access handles used below.
|
|
25
|
+
|
|
26
|
+
## Examples
|
|
12
27
|
|
|
13
28
|
```ts
|
|
14
29
|
import { createFlexSearchIndexer } from "@statewalker/indexer-mem-flexsearch";
|
|
30
|
+
import { newFullTextAccess, setFullTextConfig } from "@statewalker/indexer-fulltext";
|
|
31
|
+
import { newVectorAccess, setVectorConfig } from "@statewalker/indexer-vector";
|
|
15
32
|
|
|
16
33
|
const indexer = createFlexSearchIndexer();
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
34
|
+
|
|
35
|
+
const params = { name: "docs" };
|
|
36
|
+
setFullTextConfig(params, "q", { language: "en" });
|
|
37
|
+
setVectorConfig(params, "semantic", { dimensionality: 384, model: "all-MiniLM-L6-v2" });
|
|
38
|
+
const index = await indexer.createIndex(params);
|
|
39
|
+
|
|
40
|
+
const fts = newFullTextAccess("q");
|
|
41
|
+
await fts.get(index).addDocument([{ path: "/docs/a", blockId: "1", content: "hello world" }]);
|
|
42
|
+
|
|
43
|
+
const request = { topK: 10 };
|
|
44
|
+
fts.setQuery(request, { queries: ["hello"] });
|
|
45
|
+
for await (const r of index.search(request)) {
|
|
46
|
+
console.log(r.path, r.blockId, r.score, fts.getResult(r)?.score);
|
|
29
47
|
}
|
|
30
48
|
```
|
|
31
49
|
|
|
32
|
-
|
|
50
|
+
With persistence:
|
|
33
51
|
|
|
34
52
|
```ts
|
|
35
|
-
import { createFlexSearchIndexer } from "@statewalker/indexer-mem-flexsearch";
|
|
36
53
|
import type { IndexerPersistence } from "@statewalker/indexer-api";
|
|
54
|
+
import { createFlexSearchIndexer } from "@statewalker/indexer-mem-flexsearch";
|
|
55
|
+
|
|
56
|
+
const persistence: IndexerPersistence = {
|
|
57
|
+
async save(entries) {
|
|
58
|
+
for await (const entry of entries) {
|
|
59
|
+
// write entry.name; read bytes from entry.content (AsyncIterable<Uint8Array>)
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
async *load() {
|
|
63
|
+
// yield { name, content } for every saved entry
|
|
64
|
+
},
|
|
65
|
+
};
|
|
37
66
|
|
|
38
|
-
const persistence: IndexerPersistence = /* … your save/load adapter … */;
|
|
39
67
|
const indexer = createFlexSearchIndexer({ persistence });
|
|
68
|
+
// ... createIndex / getIndex, add documents ...
|
|
69
|
+
await indexer.flush(); // writes everything; close() also saves
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Internals
|
|
73
|
+
|
|
74
|
+
### When state is saved
|
|
75
|
+
|
|
76
|
+
Saved state is read on the first indexer call and written on `indexer.flush()` and `indexer.close()`, never in between. A process that exits without one of them loses its changes. `save` receives the whole state each time:
|
|
40
77
|
|
|
41
|
-
// First use loads existing state; `indexer.flush()` writes current state back.
|
|
42
78
|
```
|
|
79
|
+
__manifest__ JSON array of index names
|
|
80
|
+
<index>/__manifest__ JSON { name, subIndexes }
|
|
81
|
+
<index>/<sub>/json full-text sub-index (FlexSearch state, format version 3)
|
|
82
|
+
<index>/<sub>/arrow vector sub-index (Arrow IPC from MemVectorIndex)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Loading a saved full-text entry with another format version throws `FlexSearchFullTextIndex: unsupported serialised version <n> (expected 3)`.
|
|
86
|
+
|
|
87
|
+
### How the two in-memory engines differ
|
|
88
|
+
|
|
89
|
+
| | FlexSearch | MiniSearch |
|
|
90
|
+
| --- | --- | --- |
|
|
91
|
+
| Matching | `tokenize: "forward"` (prefix matching), multi-word queries with `suggest: true` so partial matches are returned | `prefix: true`, `fuzzy: 0.2`; if nothing matches, retries without fuzzy, then with `combineWith: "OR"` |
|
|
92
|
+
| `score` in `subResults` | rank-based: `1 - rank / hits` per query | MiniSearch relevance score |
|
|
93
|
+
|
|
94
|
+
In both, a sub-query with several `queries` adds up per-query scores, so blocks matching more queries rank higher. `snippet` is the whole block content. `language` is stored but not used: there is no stemming or stop-word list. Default retrieval depth is `topK = 100` per sub-query.
|
|
43
95
|
|
|
44
|
-
|
|
96
|
+
FlexSearch-specific: each query fetches `topK * 3` candidates from FlexSearch and then applies the `paths` filter. With a narrow path prefix in a large index you can get fewer than `topK` hits even though more blocks under that prefix match. Raise the sub-query `topK` if that matters.
|
|
45
97
|
|
|
46
|
-
|
|
98
|
+
### Dependencies
|
|
47
99
|
|
|
48
|
-
- `
|
|
49
|
-
- `FlexSearchIndexerOptions` — option type.
|
|
100
|
+
`flexsearch` (full-text engine), `@statewalker/indexer-mem` (vectors), `@statewalker/indexer-core` (indexer builder), `@statewalker/indexer-api`, `@statewalker/indexer-fulltext`.
|
|
50
101
|
|
|
51
|
-
##
|
|
102
|
+
## License
|
|
52
103
|
|
|
53
|
-
|
|
54
|
-
- `@statewalker/indexer-mem-minisearch` — drop-in alternative using MiniSearch.
|
|
55
|
-
- `@statewalker/indexer-mem` — provides the vector sub-index (`MemVectorIndex`).
|
|
104
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statewalker/indexer-mem-flexsearch",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "In-memory indexer implementation backed by FlexSearch for full-text search.",
|
|
7
|
-
"
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://github.com/statewalker/statewalker-search/issues"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/statewalker/statewalker-search/tree/main/packages/indexer-mem-flexsearch#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+
|
|
18
|
+
"url": "git+https://github.com/statewalker/statewalker-search.git",
|
|
19
|
+
"directory": "packages/indexer-mem-flexsearch"
|
|
16
20
|
},
|
|
17
21
|
"exports": {
|
|
18
22
|
".": {
|
|
@@ -28,10 +32,10 @@
|
|
|
28
32
|
],
|
|
29
33
|
"dependencies": {
|
|
30
34
|
"flexsearch": "^0.8.212",
|
|
31
|
-
"@statewalker/indexer-api": "^0.1.
|
|
32
|
-
"@statewalker/indexer-core": "^0.1.
|
|
33
|
-
"@statewalker/indexer-
|
|
34
|
-
"@statewalker/indexer-
|
|
35
|
+
"@statewalker/indexer-api": "^0.1.3",
|
|
36
|
+
"@statewalker/indexer-core": "^0.1.3",
|
|
37
|
+
"@statewalker/indexer-fulltext": "^0.1.4",
|
|
38
|
+
"@statewalker/indexer-mem": "^0.1.4"
|
|
35
39
|
},
|
|
36
40
|
"devDependencies": {
|
|
37
41
|
"rimraf": "^6.1.3",
|
|
@@ -40,7 +44,7 @@
|
|
|
40
44
|
"vitest": "^5.0.3",
|
|
41
45
|
"@statewalker/indexer-search": "^0.1.0",
|
|
42
46
|
"@statewalker/indexer-tests": "^0.0.0",
|
|
43
|
-
"@statewalker/indexer-vector": "^0.1.
|
|
47
|
+
"@statewalker/indexer-vector": "^0.1.4"
|
|
44
48
|
},
|
|
45
49
|
"sideEffects": false,
|
|
46
50
|
"publishConfig": {
|