@kedem/okdb 1.2.0 → 1.4.0

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/docs/http-api.md CHANGED
@@ -349,6 +349,8 @@ POST /api/:env/type/:type/query → query records
349
349
  POST /api/:env/transaction → atomic batch writes
350
350
  ```
351
351
 
352
+ **Hybrid search:** The `options` body field now accepts `fts` and `vector` sub-objects alongside `index`. See the querying guide for full documentation. When `fts` or `vector` is provided, the route automatically awaits the async result.
353
+
352
354
  ```bash
353
355
  # Get a record
354
356
  curl http://localhost:8080/api/default/type/users/item/alice \
package/docs/querying.md CHANGED
@@ -192,6 +192,116 @@ const open = okdb.countByIndex('orders', ['status', 'createdAt'], {
192
192
 
193
193
  ---
194
194
 
195
+ ## Hybrid queries
196
+
197
+ `query()` accepts `fts` and `vector` options to combine full-text search, semantic similarity, and index constraints in a single call. All signals compose — the result is a unified ranked list with sift post-filtering applied last.
198
+
199
+ ### FTS signal
200
+
201
+ ```javascript
202
+ // Full-text search — returns results ordered by relevance score
203
+ const results = await okdb.query(
204
+ 'articles',
205
+ {},
206
+ {
207
+ fts: { name: 'articles_fts', query: 'database indexing' },
208
+ limit: 20,
209
+ },
210
+ );
211
+ // Each result has: { key, value, ftsScore, numTerms, maxScore }
212
+ ```
213
+
214
+ ### FTS + index constraint
215
+
216
+ The index acts as a **hard constraint** — only documents present in the index range are returned. It does not affect ranking.
217
+
218
+ ```javascript
219
+ // FTS results, but only from published articles
220
+ const results = await okdb.query(
221
+ 'articles',
222
+ {},
223
+ {
224
+ fts: { name: 'articles_fts', query: 'database indexing' },
225
+ index: { fields: ['status'], prefix: ['published'] },
226
+ limit: 20,
227
+ },
228
+ );
229
+ ```
230
+
231
+ ### FTS + sift post-filter
232
+
233
+ Sift filter is always applied last, after FTS and index narrowing:
234
+
235
+ ```javascript
236
+ const results = await okdb.query(
237
+ 'articles',
238
+ { category: 'tech' },
239
+ {
240
+ fts: { name: 'articles_fts', query: 'indexing' },
241
+ limit: 20,
242
+ },
243
+ );
244
+ ```
245
+
246
+ ### FTS + vector (hybrid semantic + keyword)
247
+
248
+ When both `fts` and `vector` are provided, candidates from each signal are combined and re-ranked using **Reciprocal Rank Fusion (RRF)**. RRF works across incomparable score scales — no normalization needed.
249
+
250
+ ```javascript
251
+ const results = await okdb.query(
252
+ 'articles',
253
+ {},
254
+ {
255
+ fts: { name: 'articles_fts', query: 'database' },
256
+ vector: { engine: 'articles_vec', query: 'fast persistent storage', limit: 50 },
257
+ limit: 20,
258
+ },
259
+ );
260
+ // Each result has: { key, value, score (RRF), ftsScore?, vectorScore? }
261
+ ```
262
+
263
+ ### Signal semantics
264
+
265
+ | Signal | Ordering | Can enumerate all matches | Notes |
266
+ | -------- | ---------------- | ------------------------- | ------------------------------------- |
267
+ | `index` | Field value | Yes | Hard constraint only — never reorders |
268
+ | `fts` | Relevance score | Yes | Posting list intersection |
269
+ | `vector` | Similarity score | No — K-bounded | ANN search, approximate |
270
+
271
+ **Important:** `query()` returns a **Promise** when `fts` or `vector` is present (vector search requires async embedding inference). Without these options, `query()` remains synchronous.
272
+
273
+ ### FTS options
274
+
275
+ | Option | Type | Default | Description |
276
+ | -------- | --------------- | -------------------- | --------------------------------------------- |
277
+ | `name` | string | required | Registered FTS index name |
278
+ | `query` | string | `''` | Text to search |
279
+ | `mode` | `'and'`\|`'or'` | `'and'` | AND: all terms required. OR: any term matches |
280
+ | `prefix` | boolean | false | Enable prefix matching on last token |
281
+ | `limit` | number | `max(limit×10, 200)` | FTS candidate pool size |
282
+
283
+ ### Vector options
284
+
285
+ | Option | Type | Default | Description |
286
+ | -------- | ------ | ------------------- | ------------------------- |
287
+ | `engine` | string | required | Vector search engine name |
288
+ | `query` | string | required | Semantic query text |
289
+ | `limit` | number | `max(limit×4, 100)` | ANN candidate pool size |
290
+
291
+ ### Nested index form
292
+
293
+ The `index` option accepts a nested object for clarity:
294
+
295
+ ```javascript
296
+ // Nested form (preferred for hybrid queries)
297
+ { index: { fields: ['status'], prefix: ['active'] } }
298
+
299
+ // Flat form (still works — backward compatible)
300
+ { index: ['status'], prefix: ['active'] }
301
+ ```
302
+
303
+ ---
304
+
195
305
  ## Geo query
196
306
 
197
307
  OKDB has a built-in geospatial index based on geohash. Register a geo index on a field that stores `{ lat, lon }` (or an `[lat, lon]` array):