@kedem/okdb 1.6.1 → 1.6.3
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/bin/okdb.js +1 -1
- package/docs/OKDB-ARCHITECTURE.md +20 -10
- package/docs/data-model.md +17 -4
- package/docs/fts.md +122 -28
- package/docs/http-api.md +26 -11
- package/docs/transactions.md +1 -1
- package/okdb.js +1 -1
- package/package.json +2 -1
- package/public/sections/db/parts/db-overview.ok.js +1 -1
|
@@ -63,7 +63,9 @@ independent compaction, encryption, and sync control.
|
|
|
63
63
|
queue/ ← Queue jobs/payloads/stats (sync: on)
|
|
64
64
|
embeddings/ ← Embedding doc_status + models (sync: on)
|
|
65
65
|
vectors/ ← HNSW graph files + LMDB snapshots (sync: off)
|
|
66
|
-
|
|
66
|
+
~fts/
|
|
67
|
+
<env>/ ← Shared FTS env: posting lists + dicts (sync: off, rebuildable)
|
|
68
|
+
<env>_docs/ ← Shared FTS env: LZ4-compressed forward index
|
|
67
69
|
blobs/ ← File attachment BLOBs
|
|
68
70
|
```
|
|
69
71
|
|
|
@@ -309,19 +311,27 @@ GET /api/env/:env/ref-violations/:type → violations for a specific type
|
|
|
309
311
|
|
|
310
312
|
### 3.4 FTS Environments
|
|
311
313
|
|
|
312
|
-
|
|
314
|
+
All FTS data for one OKDB env lives in a pair of shared LMDB environments. Individual indexes are distinguished by an auto-increment `ftsId` used as a key prefix — no new files per index.
|
|
313
315
|
|
|
314
316
|
```
|
|
315
|
-
|
|
316
|
-
├── post
|
|
317
|
-
|
|
317
|
+
~fts/<env>/ ← uncompressed env
|
|
318
|
+
├── post dupSort [ftsId, token] → docId (live "add" tier)
|
|
319
|
+
├── postDel dupSort [ftsId, token] → docId (live "delete" tombstones)
|
|
320
|
+
├── frozen [ftsId, token] → Roaring blob (compacted tier)
|
|
321
|
+
├── dict [type, 'k', docKey] → docId (docKey ↔ docId interning)
|
|
322
|
+
└── tokenDict ['k', token] → tokenId (token ↔ tokenId interning)
|
|
323
|
+
|
|
324
|
+
~fts/<env>_docs/ ← LZ4-compressed env
|
|
325
|
+
└── docs [ftsId, docId] → tokenId[] (forward index)
|
|
318
326
|
```
|
|
319
327
|
|
|
320
|
-
-
|
|
321
|
-
-
|
|
322
|
-
|
|
323
|
-
-
|
|
324
|
-
|
|
328
|
+
- **Two-tier writes.** Hot path lands in the live tier (`post` / `postDel`) — O(1) dupSort puts. The compactor periodically merges live entries into per-token Roaring bitmaps in `frozen` and clears the live tier.
|
|
329
|
+
- **Reads** merge all three tiers: `frozen ∪ liveAdds − liveDeletes`. Roaring's set ops keep this essentially free.
|
|
330
|
+
- **ID interning.** `dict` maps (type, docKey) → uint32 docIds so posting-list values and forward-index keys are 4 bytes instead of full strings. `tokenDict` maps tokens → uint32 tokenIds so forward-index values are integer arrays instead of token strings.
|
|
331
|
+
- Always `sync: false` — fully rebuildable from the env's type data.
|
|
332
|
+
- FTS metadata (field config, status, created/updated timestamps, storage `format` version) is stored in `<env>/types` under `ftsIndexes[name]` — this IS synced. The storage format auto-migrates on open by triggering a rebuild for any index whose format is older than the current code.
|
|
333
|
+
- Writes are async via a background processor (one per type). `fts.flush(type)` waits for the processor and runs the compactor.
|
|
334
|
+
- `fts.compactStorage(env)` reclaims slack inside the FTS data.mdb files (LMDB never shrinks files in place). `env.compact()` runs both the main-data and FTS compactions in one operation.
|
|
325
335
|
|
|
326
336
|
### 3.5 Vectors Environment
|
|
327
337
|
|
package/docs/data-model.md
CHANGED
|
@@ -177,14 +177,27 @@ const keys = okdb.fts.search('articles', 'main', 'hello');
|
|
|
177
177
|
|
|
178
178
|
### `flush(type)`
|
|
179
179
|
|
|
180
|
-
`fts.flush(type)` awaits the background processor catching up to the current write position
|
|
180
|
+
`fts.flush(type)` awaits the background processor catching up to the current write position, then runs the FTS compactor to roll live-tier entries into the compacted Roaring bitmap tier. Returns immediately if there's nothing to do.
|
|
181
181
|
|
|
182
182
|
### list() response
|
|
183
183
|
|
|
184
|
-
`fts.list(type)` returns an entry per registered index. Each entry includes
|
|
184
|
+
`fts.list(type)` returns an entry per registered index. Each entry includes:
|
|
185
185
|
|
|
186
|
-
- `processorState`: `'building' | 'online' | 'error' | 'waiting' | null` — `null` if no processor is running (no writes have occurred yet).
|
|
187
|
-
- `lag`: number of write operations the processor has not yet indexed. `0` means fully caught up. `null` if no processor
|
|
186
|
+
- `processorState`: `'building' | 'online' | 'error' | 'waiting' | null` — `null` if no processor is running (no writes have occurred yet). Same value for every entry of the same type.
|
|
187
|
+
- `lag`: number of write operations the processor has not yet indexed. `0` means fully caught up. `null` if no processor.
|
|
188
|
+
- `sizeBytes`: approximate per-index payload (frozen blobs + live entries + forward index scoped by this index's `ftsId`). Excludes env-shared dictionaries.
|
|
189
|
+
|
|
190
|
+
### Storage architecture
|
|
191
|
+
|
|
192
|
+
FTS uses a two-tier inverted index. New writes land in a small **live** tier (LMDB dupSort) on the hot write path. A background **compactor** periodically merges live entries into the **frozen** tier — one Roaring bitmap per token. Reads merge all tiers transparently.
|
|
193
|
+
|
|
194
|
+
Documents and tokens are interned to uint32 IDs (`dict` and `tokenDict` sub-DBs) so posting lists and the forward index store compact integer arrays rather than strings.
|
|
195
|
+
|
|
196
|
+
Each registered index carries a storage `format` integer; older formats auto-rebuild on open. See [FTS](./fts.md) for the full schema and the storage-size APIs (`fts.list(type)[].sizeBytes`, `fts.envSize()`).
|
|
197
|
+
|
|
198
|
+
### Reclaiming FTS slack
|
|
199
|
+
|
|
200
|
+
LMDB never shrinks `data.mdb` in place — freed pages are reused by future writes but the file stays at its peak high-water mark. `env.compact()` reclaims that slack for both the primary env and the FTS envs in one pass. `fts.compactStorage(env)` is the FTS-only equivalent if you want to reclaim just FTS without disturbing primary storage.
|
|
188
201
|
|
|
189
202
|
---
|
|
190
203
|
|
package/docs/fts.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Full-Text Search
|
|
2
2
|
|
|
3
|
-
OKDB includes a built-in full-text search engine
|
|
3
|
+
OKDB includes a built-in full-text search engine. All FTS data for an environment lives in two shared LMDB environments — one inverted/uncompressed (posting lists + dictionaries), one compressed (forward index). Indexes are differentiated by an auto-increment `ftsId` used as a key prefix inside the shared envs, so adding indexes adds no new files and no per-index LMDB overhead.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -21,16 +21,19 @@ await okdb.fts.register('articles', 'content', {
|
|
|
21
21
|
The index builds from existing data immediately after registration. Wait for it:
|
|
22
22
|
|
|
23
23
|
```javascript
|
|
24
|
-
await okdb.fts.ready('articles'
|
|
24
|
+
await okdb.fts.ready('articles');
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
### Multiple FTS indexes on one type
|
|
28
28
|
|
|
29
|
-
You can have several FTS indexes on the same type, each covering different fields or using different tokenizer settings
|
|
29
|
+
You can have several FTS indexes on the same type, each covering different fields or using different tokenizer settings. All of them share the env-level storage — adding a second index does not double your disk usage.
|
|
30
30
|
|
|
31
31
|
```javascript
|
|
32
32
|
await okdb.fts.register('products', 'name', { fields: ['name'] });
|
|
33
|
-
await okdb.fts.register('products', 'description', {
|
|
33
|
+
await okdb.fts.register('products', 'description', {
|
|
34
|
+
fields: ['description'],
|
|
35
|
+
tokenizer: { minTokenLength: 3 },
|
|
36
|
+
});
|
|
34
37
|
```
|
|
35
38
|
|
|
36
39
|
---
|
|
@@ -44,14 +47,13 @@ for (const { key, value } of results) {
|
|
|
44
47
|
}
|
|
45
48
|
```
|
|
46
49
|
|
|
47
|
-
`ftsQuery` returns records that contain
|
|
50
|
+
`ftsQuery` returns records that contain all of the query tokens by default (AND). Use `options.mode: 'or'` for OR semantics, and `options.prefix: true` for prefix matching.
|
|
48
51
|
|
|
49
52
|
### Combining FTS with field filters
|
|
50
53
|
|
|
51
54
|
Pass a sift-compatible filter as the fourth argument to narrow results:
|
|
52
55
|
|
|
53
56
|
```javascript
|
|
54
|
-
// Articles matching the query that are also published
|
|
55
57
|
const results = okdb.ftsQuery('articles', 'content', 'database performance', {
|
|
56
58
|
status: 'published',
|
|
57
59
|
});
|
|
@@ -62,7 +64,8 @@ const results = okdb.ftsQuery('articles', 'content', 'database performance', {
|
|
|
62
64
|
```javascript
|
|
63
65
|
const results = okdb.ftsQuery('articles', 'content', 'hello world', null, {
|
|
64
66
|
limit: 20,
|
|
65
|
-
|
|
67
|
+
mode: 'and', // 'and' (default) | 'or'
|
|
68
|
+
prefix: false, // enable prefix matching on tokens
|
|
66
69
|
});
|
|
67
70
|
```
|
|
68
71
|
|
|
@@ -77,9 +80,28 @@ okdb.fts.has('articles', 'content'); // → boolean
|
|
|
77
80
|
// Get status
|
|
78
81
|
okdb.fts.status('articles', 'content'); // → 'creating' | 'ready' | null
|
|
79
82
|
|
|
80
|
-
// List all FTS indexes on a type
|
|
83
|
+
// List all FTS indexes on a type — includes runtime state
|
|
81
84
|
okdb.fts.list('articles');
|
|
82
|
-
// → [{
|
|
85
|
+
// → [{
|
|
86
|
+
// name, status, config, created, updated, error,
|
|
87
|
+
// processorState, // per-type async processor state
|
|
88
|
+
// lag, // writes not yet indexed (0 = caught up)
|
|
89
|
+
// sizeBytes, // approximate bytes for this index (excludes shared dicts)
|
|
90
|
+
// }]
|
|
91
|
+
|
|
92
|
+
// Env-level storage summary across all FTS indexes
|
|
93
|
+
okdb.fts.envSize();
|
|
94
|
+
// → {
|
|
95
|
+
// diskBytes, // ~fts/<env>/ + ~fts/<env>_docs/ data.mdb file sizes
|
|
96
|
+
// payloadBytes, // actual stored bytes (indexes + shared dicts)
|
|
97
|
+
// indexPayloadBytes, // payload attributable to indexes only
|
|
98
|
+
// sharedDictBytes, // shared docKey↔docId and token↔tokenId mappings
|
|
99
|
+
// slackBytes, // LMDB free pages (reclaimable via env.compact)
|
|
100
|
+
// indexes: [{ type, name, sizeBytes }],
|
|
101
|
+
// }
|
|
102
|
+
|
|
103
|
+
// Reclaim FTS slack — usually called via env.compact() which does both data and FTS
|
|
104
|
+
await okdb.fts.compactStorage();
|
|
83
105
|
|
|
84
106
|
// Drop an index
|
|
85
107
|
await okdb.fts.drop('articles', 'content');
|
|
@@ -87,31 +109,102 @@ await okdb.fts.drop('articles', 'content');
|
|
|
87
109
|
|
|
88
110
|
---
|
|
89
111
|
|
|
90
|
-
##
|
|
112
|
+
## Storage architecture
|
|
91
113
|
|
|
92
|
-
OKDB's FTS
|
|
114
|
+
OKDB's FTS uses a two-tier inverted index with Roaring bitmaps and ID interning. The names below are the actual LMDB sub-DB names in `~fts/<envName>/data.mdb`:
|
|
93
115
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
116
|
+
```
|
|
117
|
+
~fts/<envName>/data.mdb (uncompressed env)
|
|
118
|
+
post dupSort [ftsId, token] → docId (live "add" tier)
|
|
119
|
+
postDel dupSort [ftsId, token] → docId (live "delete" tombstones)
|
|
120
|
+
frozen [ftsId, token] → Roaring blob (compacted tier)
|
|
121
|
+
dict [type, 'k', docKey] → docId (docKey ↔ docId mapping)
|
|
122
|
+
[type, 'i', docId] → docKey
|
|
123
|
+
[type, 'n'] → counter
|
|
124
|
+
tokenDict ['k', token] → tokenId (token ↔ tokenId mapping)
|
|
125
|
+
['i', tokenId] → token
|
|
126
|
+
['n'] → counter
|
|
127
|
+
|
|
128
|
+
~fts/<envName>_docs/data.mdb (LZ4-compressed env)
|
|
129
|
+
docs [ftsId, docId] → tokenId[] (forward index)
|
|
130
|
+
```
|
|
98
131
|
|
|
99
|
-
|
|
132
|
+
**Why these pieces:**
|
|
100
133
|
|
|
101
|
-
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
134
|
+
| Sub-DB | Purpose | Notes |
|
|
135
|
+
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
|
136
|
+
| `post` / `postDel` | Live tier — every write goes here first. dupSort makes inserts O(1). | Compactor periodically merges these into `frozen`. |
|
|
137
|
+
| `frozen` | Compacted tier — one Roaring bitmap per (ftsId, token) holds all matching docIds. | 10–50× smaller than the equivalent dupSort entries for dense postings. |
|
|
138
|
+
| `dict` | Per-type docKey ↔ docId interning. uint32 docIds replace string keys in posting lists. | One mapping per type, shared across all that type's indexes. |
|
|
139
|
+
| `tokenDict` | Env-wide token ↔ tokenId interning. uint32 tokenIds replace token strings in `docs`. | "the" is the same word in every collection — one map for the whole env. |
|
|
140
|
+
| `docs` | Forward index — used by the indexer to diff old vs new tokens on every write. LZ4-compressed because token arrays compress well. | Read on every doc update, not on search. |
|
|
105
141
|
|
|
106
|
-
|
|
142
|
+
**Reads** merge all three tiers: `frozen ∪ liveAdds − liveDeletes`. Roaring's set operations make this essentially free.
|
|
107
143
|
|
|
144
|
+
**Writes** only touch the live tier — never the frozen tier. The hot path is one dupSort `putSync` per added token.
|
|
145
|
+
|
|
146
|
+
### Compaction
|
|
147
|
+
|
|
148
|
+
The compactor periodically rolls live entries into the frozen tier. It runs in two situations:
|
|
149
|
+
|
|
150
|
+
1. **Automatically after `fts.flush(type)`** — keeps the live tier small after a known burst of writes.
|
|
151
|
+
2. **Explicitly via `fts.compactStorage(env)` or `env.compact()`** — also reclaims `data.mdb` slack by rewriting the file (LMDB never shrinks files in place; freed pages are reused but the high-water mark stays).
|
|
152
|
+
|
|
153
|
+
After a stress run of 13k docs across 5 indexes, `env.compact()` typically reclaims ~75% of the FTS file size in a few seconds, with no impact on search.
|
|
154
|
+
|
|
155
|
+
### Storage format versioning
|
|
156
|
+
|
|
157
|
+
Each registered index carries a `format` integer in its metadata. The schema has evolved (legacy → docId interning → two-tier Roaring → token interning); on every open, OKDB checks each index's format and auto-rebuilds any whose format is older than the current code. This is transparent — no manual migration needed. The rebuild reads from the type's primary data, which is the source of truth.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Async indexing
|
|
162
|
+
|
|
163
|
+
FTS writes are **always async** — the FTS index is updated by a background processor after each write commits. Secondary indexes (field indexes, geo indexes) update synchronously inside the same LMDB transaction as the write, so they are immediately consistent. FTS isn't.
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
await okdb.put('articles', 'a1', { title: 'Hello world' });
|
|
167
|
+
// At this point a1 is in the primary data but NOT YET in the FTS index.
|
|
168
|
+
|
|
169
|
+
await okdb.fts.flush('articles');
|
|
170
|
+
// Now it is — and the live tier has been compacted into the frozen tier.
|
|
171
|
+
|
|
172
|
+
const results = okdb.fts.search('articles', 'main', 'hello'); // includes a1
|
|
108
173
|
```
|
|
109
|
-
~fts/<envName>/<type>/<indexName>/
|
|
110
|
-
post dupSort=true token → docKey (inverted list)
|
|
111
|
-
docs docKey → tokens (forward index)
|
|
112
|
-
```
|
|
113
174
|
|
|
114
|
-
|
|
175
|
+
### `ready(type)`
|
|
176
|
+
|
|
177
|
+
`fts.ready(type)` resolves when all FTS indexes registered on `type` have completed their initial build. The optional second argument is accepted for backward compatibility but ignored — readiness is per-type, not per-index.
|
|
178
|
+
|
|
179
|
+
### `flush(type)`
|
|
180
|
+
|
|
181
|
+
`fts.flush(type)` awaits the background processor catching up to the current write position, then runs the compactor. Returns immediately if there's nothing to do.
|
|
182
|
+
|
|
183
|
+
### Processor state in `list()`
|
|
184
|
+
|
|
185
|
+
`processorState` and `lag` are per-type fields reflecting the single background processor that drives all of a type's indexes. Both have the same value on every entry of the same type.
|
|
186
|
+
|
|
187
|
+
- `processorState`: `'building' | 'online' | 'error' | 'waiting' | null` — `null` if no writes have happened yet.
|
|
188
|
+
- `lag`: count of writes the processor hasn't indexed yet. `0` means caught up.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Tokenizer
|
|
193
|
+
|
|
194
|
+
Default behaviour:
|
|
195
|
+
|
|
196
|
+
- Splits on whitespace and punctuation
|
|
197
|
+
- Lowercases (when `toLower: true`, default)
|
|
198
|
+
- Discards tokens shorter than `minTokenLength` (default 2) or longer than `maxTokenLength` (default 64)
|
|
199
|
+
- Keeps numbers by default (`keepNumbers: true`)
|
|
200
|
+
|
|
201
|
+
You can override per-index in the `register()` config.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Sync behaviour
|
|
206
|
+
|
|
207
|
+
FTS environments are not replicated. On a new node they rebuild from synced type data automatically — the source of truth is the primary data plus the change log, both of which are sync'd.
|
|
115
208
|
|
|
116
209
|
---
|
|
117
210
|
|
|
@@ -122,7 +215,7 @@ await okdb.ensureType('articles');
|
|
|
122
215
|
await okdb.fts.register('articles', 'search', {
|
|
123
216
|
fields: ['title', 'body', 'tags'],
|
|
124
217
|
});
|
|
125
|
-
await okdb.fts.ready('articles'
|
|
218
|
+
await okdb.fts.ready('articles');
|
|
126
219
|
|
|
127
220
|
await okdb.put('articles', 'a1', {
|
|
128
221
|
title: 'Getting started with LMDB',
|
|
@@ -130,7 +223,8 @@ await okdb.put('articles', 'a1', {
|
|
|
130
223
|
tags: ['database', 'lmdb', 'storage'],
|
|
131
224
|
});
|
|
132
225
|
|
|
133
|
-
|
|
226
|
+
await okdb.fts.flush('articles');
|
|
227
|
+
const results = okdb.fts.search('articles', 'search', 'LMDB key value');
|
|
134
228
|
// → includes 'a1'
|
|
135
229
|
```
|
|
136
230
|
|
package/docs/http-api.md
CHANGED
|
@@ -461,6 +461,7 @@ GET /api/:env/type/:type/index/:idx/count?start=<json>&end=<json> → count in
|
|
|
461
461
|
## Full-text search endpoints
|
|
462
462
|
|
|
463
463
|
```http
|
|
464
|
+
GET /api/:env/fts/size → FTS storage summary for the env
|
|
464
465
|
GET /api/:env/type/:type/fts → list FTS indexes
|
|
465
466
|
POST /api/:env/type/:type/fts/:name → register a new FTS index
|
|
466
467
|
DELETE /api/:env/type/:type/fts/:name → drop an FTS index
|
|
@@ -469,26 +470,40 @@ POST /api/:env/type/:type/fts/:name/search → search an FTS index
|
|
|
469
470
|
POST /api/:env/type/:type/fts/flush → flush FTS processor (await catch-up)
|
|
470
471
|
```
|
|
471
472
|
|
|
473
|
+
### GET /api/:env/fts/size — env-level storage summary
|
|
474
|
+
|
|
475
|
+
Returns a single object summarizing FTS storage for the environment. Useful for "how much disk is FTS costing me" overviews and for spotting reclaimable slack.
|
|
476
|
+
|
|
477
|
+
| Field | Type | Description |
|
|
478
|
+
| ------------------- | -------- | -------------------------------------------------------------------------------------------- |
|
|
479
|
+
| `diskBytes` | `number` | Sum of `~fts/<env>/` + `~fts/<env>_docs/` data.mdb file sizes. LMDB high-water mark. |
|
|
480
|
+
| `payloadBytes` | `number` | Actual stored bytes (sum of all index payloads + shared dictionaries). |
|
|
481
|
+
| `indexPayloadBytes` | `number` | Payload attributable to indexes only (excludes shared dictionaries). |
|
|
482
|
+
| `sharedDictBytes` | `number` | Bytes used by the env-shared docKey↔docId and token↔tokenId dictionaries. |
|
|
483
|
+
| `slackBytes` | `number` | `diskBytes - payloadBytes`. Free LMDB pages — reclaimable by future writes or `env.compact`. |
|
|
484
|
+
| `indexes` | `array` | Per-index breakdown: `[{ type, name, sizeBytes }, ...]`. |
|
|
485
|
+
|
|
472
486
|
### GET /api/:env/type/:type/fts — list response
|
|
473
487
|
|
|
474
488
|
Each entry in the returned array includes:
|
|
475
489
|
|
|
476
|
-
| Field | Type | Description
|
|
477
|
-
| ---------------- | ----------------------------------------------------------- |
|
|
478
|
-
| `name` | `string` | Index name
|
|
479
|
-
| `status` | `'creating' \| 'resetting' \| 'ready' \| 'waiting' \| null` | Per-index lifecycle status
|
|
480
|
-
| `config` | `object \| null` | Index configuration as registered
|
|
481
|
-
| `created` | `number \| null` | HLC clock at registration time
|
|
482
|
-
| `updated` | `number \| null` | HLC clock at last config update
|
|
483
|
-
| `error` | `object \| null` | Last per-index error, if any
|
|
484
|
-
| `processorState` | `'building' \| 'online' \| 'error' \| 'waiting' \| null` | State of the background FTS processor for this type. `null` if no processor is running.
|
|
485
|
-
| `lag` | `number \| null` | Number of writes the processor has not yet indexed. `0` = fully caught up. `null` if no processor.
|
|
490
|
+
| Field | Type | Description |
|
|
491
|
+
| ---------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
492
|
+
| `name` | `string` | Index name |
|
|
493
|
+
| `status` | `'creating' \| 'resetting' \| 'ready' \| 'waiting' \| null` | Per-index lifecycle status |
|
|
494
|
+
| `config` | `object \| null` | Index configuration as registered |
|
|
495
|
+
| `created` | `number \| null` | HLC clock at registration time |
|
|
496
|
+
| `updated` | `number \| null` | HLC clock at last config update |
|
|
497
|
+
| `error` | `object \| null` | Last per-index error, if any |
|
|
498
|
+
| `processorState` | `'building' \| 'online' \| 'error' \| 'waiting' \| null` | State of the background FTS processor for this type. `null` if no processor is running. |
|
|
499
|
+
| `lag` | `number \| null` | Number of writes the processor has not yet indexed. `0` = fully caught up. `null` if no processor. |
|
|
500
|
+
| `sizeBytes` | `number` | Approximate per-index payload (frozen blobs + live entries + forward index scoped by this `ftsId`). Excludes env-shared dictionaries. |
|
|
486
501
|
|
|
487
502
|
`processorState` and `lag` are per-type fields — they reflect the single background processor that drives all FTS indexes on a type and are identical on every entry of the same type.
|
|
488
503
|
|
|
489
504
|
### POST /api/:env/type/:type/fts/flush — flush processor
|
|
490
505
|
|
|
491
|
-
Waits for the background FTS processor to catch up to the current write position for this type. Responds `204 No Content` when fully caught up. This is a no-op if no processor is running or if the processor is already caught up.
|
|
506
|
+
Waits for the background FTS processor to catch up to the current write position for this type, then runs the FTS compactor to roll live-tier entries into the compacted Roaring bitmap tier. Responds `204 No Content` when fully caught up. This is a no-op if no processor is running or if the processor is already caught up.
|
|
492
507
|
|
|
493
508
|
- **Permission:** `schema:read`
|
|
494
509
|
- **Idempotent:** yes
|
package/docs/transactions.md
CHANGED
|
@@ -195,4 +195,4 @@ await okdb.fts.flush('articles'); // wait for processor to index the write
|
|
|
195
195
|
const results = okdb.fts.search('articles', 'main', 'hello'); // guaranteed to include a1
|
|
196
196
|
```
|
|
197
197
|
|
|
198
|
-
`fts.flush()` is a no-op if the processor is already caught up. It is safe to call unconditionally.
|
|
198
|
+
`fts.flush()` is a no-op if the processor is already caught up. It is safe to call unconditionally. In addition to waiting for the processor, `flush()` runs the FTS compactor — rolling live-tier entries into the compacted Roaring bitmap tier — so search performance and storage stay stable under bursty writes. See [Full-Text Search](./fts.md) for the storage architecture.
|