vectlite 0.1.12 → 0.9.1

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 CHANGED
@@ -43,10 +43,12 @@ db.close()
43
43
  ### Core
44
44
 
45
45
  - **Single-file storage** -- one `.vdb` file per database, portable and easy to back up
46
- - **Dense vectors** -- cosine similarity with automatic HNSW indexing for large collections
46
+ - **Distance metrics** -- cosine (default), euclidean (L2), dot product, manhattan (L1) with SIMD acceleration
47
+ - **Dense vectors** -- automatic HNSW indexing with metric-aware distance functions
47
48
  - **Sparse vectors** -- BM25-scored inverted index for keyword retrieval
48
49
  - **Hybrid search** -- dense + sparse fusion with linear or RRF strategies
49
50
  - **Vector quantization** -- scalar (int8, 4x), binary (32x), and product quantization (PQ) with 2-stage rescoring
51
+ - **Multi-vector / ColBERT** -- late interaction search with per-token MaxSim scoring and 2-bit quantization (~16x compression)
50
52
  - **Rich metadata** -- string, number, boolean, null, array, and nested object values
51
53
  - **Crash-safe WAL** -- writes land in a write-ahead log first, then checkpoint with `compact()`
52
54
  - **Transactions** -- atomic batched writes with `db.transaction()`
@@ -61,6 +63,7 @@ db.close()
61
63
  - **MMR diversification** -- `mmrLambda` controls relevance vs. diversity trade-off
62
64
  - **Namespaces** -- logical isolation with per-namespace or cross-namespace search
63
65
  - **Observability** -- `searchWithStats()` returns timings, BM25 term scores, ANN stats, and per-result explain payloads
66
+ - **Payload indexes** -- keyword and numeric indexes on metadata fields accelerate filtered queries on large collections
64
67
 
65
68
  ### Data Management
66
69
 
@@ -68,14 +71,35 @@ db.close()
68
71
  - **Bulk ingestion** -- `bulkIngest()` with deferred index rebuilds for fast imports
69
72
  - **Listing & filtered counts** -- `list()` and `count({ namespace, filter })` without a vector query
70
73
  - **Delete by filter** -- `deleteByFilter()` for bulk deletion by metadata filter
74
+ - **Partial metadata updates** -- `updateMetadata()` merges a patch without re-writing the vector or rebuilding indexes
71
75
  - **Snapshots** -- `db.snapshot(path)` creates a self-contained copy
72
76
  - **Backup / Restore** -- `db.backup(dir)` and `vectlite.restore(dir, path)` for full roundtrips
73
77
  - **Read-only mode** -- `vectlite.open(path, { readOnly: true })` for safe concurrent readers
74
78
  - **Explicit close** -- `db.close()` to release locks deterministically
75
79
  - **Lock timeouts** -- `lockTimeout` for bounded lock acquisition waits
80
+ - **TTL / Expiry** -- `setTtl()` / `clearTtl()` or `ttl` option on insert/upsert; expired records auto-filtered from reads and GC'd on compact
81
+ - **Cursor-based pagination** -- `listCursor()` for efficient iteration over large collections
82
+ - **Async API** -- `searchAsync()`, `compactAsync()`, `flushAsync()`, `bulkIngestAsync()` run on the libuv threadpool
76
83
 
77
84
  ## Usage
78
85
 
86
+ ### Distance Metrics
87
+
88
+ ```js
89
+ // Default is cosine similarity
90
+ const db = vectlite.open('knowledge.vdb', { dimension: 384 })
91
+
92
+ // Choose a different metric at creation time
93
+ const db2 = vectlite.open('knowledge.vdb', { dimension: 384, metric: 'euclidean' })
94
+ const db3 = vectlite.open('knowledge.vdb', { dimension: 384, metric: 'dotproduct' })
95
+ const db4 = vectlite.open('knowledge.vdb', { dimension: 384, metric: 'manhattan' })
96
+
97
+ // Aliases: 'l2', 'dot', 'ip', 'l1'
98
+ console.log(db2.metric) // "euclidean"
99
+ ```
100
+
101
+ The metric is persisted in the database file. Scores are always oriented so that **higher is better**.
102
+
79
103
  ### Hybrid Search
80
104
 
81
105
  ```js
@@ -114,6 +138,10 @@ products.upsert('p1', embedding, { name: 'Widget', price: 9.99 })
114
138
 
115
139
  const logs = store.openOrCreateCollection('logs', 128)
116
140
  console.log(store.collections()) // ["logs", "products"]
141
+
142
+ products.close()
143
+ logs.close()
144
+ store.close()
117
145
  ```
118
146
 
119
147
  ### Transactions
@@ -167,6 +195,9 @@ const records = db.list({ namespace: 'docs', filter: { stale: false }, limit: 20
167
195
  const count = db.count({ namespace: 'docs', filter: { source: 'blog' } })
168
196
  const deleted = db.deleteByFilter({ stale: true }, { namespace: 'docs' })
169
197
 
198
+ // Partial metadata update (merge patch -- only touches specified keys)
199
+ db.updateMetadata('doc1', { status: 'reviewed', score: 0.95 })
200
+
170
201
  db.close()
171
202
  ```
172
203
 
@@ -184,22 +215,42 @@ console.log(outcome.stats.used_ann) // true
184
215
  console.log(outcome.results[0].explain) // Detailed scoring breakdown
185
216
  ```
186
217
 
218
+ ### Payload Indexes
219
+
220
+ Create keyword or numeric indexes on metadata fields to accelerate filtered queries on large collections. Indexes are automatically used by `search()`, `count()`, and `list()`.
221
+
222
+ ```js
223
+ // Create indexes on frequently-filtered fields
224
+ db.createIndex('source', 'keyword') // string equality, $in
225
+ db.createIndex('score', 'numeric') // range queries: $gt, $gte, $lt, $lte
226
+
227
+ // Filtered queries now use indexes automatically
228
+ const count = db.count({ filter: { source: 'blog' } })
229
+ const results = db.search(query, { k: 10, filter: { score: { $gte: 0.8 } } })
230
+
231
+ // Inspect and manage indexes
232
+ console.log(db.listIndexes()) // [{ field: 'source', type: 'keyword' }, ...]
233
+ db.dropIndex('score')
234
+ ```
235
+
187
236
  ### Vector Quantization
188
237
 
189
- Reduce memory usage and accelerate search with quantized vectors. All methods use a 2-stage pipeline: fast quantized candidate selection followed by exact float32 rescoring.
238
+ Reduce in-memory candidate-index usage and accelerate search with quantized vectors. All methods use a 2-stage pipeline: fast quantized candidate selection followed by exact float32 rescoring.
190
239
 
191
240
  ```js
192
- // Scalar quantization (int8) -- 4x memory reduction, minimal recall loss
241
+ // Scalar quantization (int8) -- smaller in-memory candidate index, minimal recall loss
193
242
  db.enableQuantization('scalar')
194
243
 
195
- // Binary quantization -- 32x memory reduction, best for normalized embeddings
196
- db.enableQuantization('binary', JSON.stringify({ rescoreMultiplier: 10 }))
244
+ // Binary quantization -- smallest in-memory candidate index, best for normalized embeddings
245
+ db.enableQuantization('binary', { rescoreMultiplier: 10 })
197
246
 
198
- // Product quantization -- configurable compression for very large datasets
199
- db.enableQuantization('product', JSON.stringify({ numSubVectors: 16, numCentroids: 256 }))
247
+ // Product quantization -- "pq" and "product" are accepted case-insensitively
248
+ console.log(db.validNumSubVectors()) // valid PQ partitions for this dimension
249
+ db.enableQuantization('pq', { numSubVectors: 16, numCentroids: 256 })
200
250
 
201
251
  // Search works exactly the same -- quantization accelerates it transparently
202
252
  const results = db.search(queryEmbedding, { k: 10 })
253
+ const sameResults = db.search({ query: queryEmbedding, k: 10 })
203
254
 
204
255
  // Check quantization status
205
256
  console.log(db.isQuantized) // true
@@ -209,7 +260,133 @@ console.log(db.quantizationMethod) // "scalar", "binary", or "product"
209
260
  db.disableQuantization()
210
261
  ```
211
262
 
212
- Quantization parameters persist across reopens in a `.vdb.quant` sidecar file. The quantized index auto-rebuilds on inserts and upserts.
263
+ `rescoreMultiplier` controls the number of quantized candidates rescored with exact float32 scoring: `k * rescoreMultiplier`, capped at the collection size. Increase it to trade latency for recall.
264
+
265
+ For PQ, `numSubVectors` must divide the database dimension. If omitted, Vectlite chooses a compatible default; use `db.validNumSubVectors()` to inspect all valid values.
266
+
267
+ Quantization does not shrink the `.vdb` file on disk. Vectlite keeps the original float32 vectors for exact rescoring and stores quantization parameters in a `.vdb.quant` sidecar file, so total disk footprint can increase slightly. The quantized index auto-rebuilds on inserts and upserts.
268
+
269
+ ### Multi-Vector / ColBERT Search
270
+
271
+ Store token-level embeddings (ColBERT, ColPali) and search with MaxSim late interaction scoring.
272
+
273
+ ```js
274
+ // Upsert with per-token ColBERT embeddings
275
+ db.upsertMultiVectors('doc1', denseVector,
276
+ { colbert: [tokenVec1, tokenVec2] },
277
+ { metadata: { source: 'paper' } }
278
+ )
279
+
280
+ // MaxSim search
281
+ const results = db.searchMultiVector('colbert', queryTokenVectors)
282
+
283
+ // Enable 2-bit quantization (~16x compression)
284
+ db.enableMultiVectorQuantization('colbert')
285
+
286
+ // Check and disable
287
+ console.log(db.isMultiVectorQuantized('colbert')) // true
288
+ db.disableMultiVectorQuantization('colbert')
289
+ ```
290
+
291
+ ### TTL / Expiry
292
+
293
+ Records can automatically expire after a time-to-live. Expired records are transparently filtered from all reads and permanently removed on `compact()`.
294
+
295
+ ```js
296
+ // Set TTL on insert/upsert (seconds)
297
+ db.upsert('session1', embedding, { user: 'alice' }, { ttl: 3600 }) // expires in 1 hour
298
+
299
+ // Set/clear TTL on existing records
300
+ db.setTtl('doc1', 86400) // expire in 24 hours
301
+ db.clearTtl('doc1') // remove expiry
302
+
303
+ // Expired records are invisible to get/list/count/search
304
+ const record = db.get('session1') // null after TTL elapses
305
+
306
+ // compact() garbage-collects expired records from disk
307
+ db.compact()
308
+ ```
309
+
310
+ ### Cursor-Based Pagination
311
+
312
+ Efficiently iterate over large collections without offset overhead.
313
+
314
+ ```js
315
+ // Paginate 100 records at a time
316
+ let cursor = null
317
+ do {
318
+ const page = db.listCursor({ limit: 100, cursor })
319
+ for (const record of page.records) {
320
+ process(record)
321
+ }
322
+ cursor = page.cursor
323
+ } while (cursor !== null)
324
+
325
+ // Works with namespace and filter
326
+ const page = db.listCursor({ namespace: 'docs', filter: { source: 'blog' }, limit: 50 })
327
+ ```
328
+
329
+ ### Async API
330
+
331
+ Non-blocking versions of heavy operations that run on the libuv threadpool.
332
+
333
+ ```js
334
+ // Async search (returns a Promise)
335
+ const results = await db.searchAsync(queryEmbedding, { k: 10, filter: { source: 'blog' } })
336
+
337
+ // Async search with stats
338
+ const outcome = await db.searchWithStatsAsync(queryEmbedding, { k: 10 })
339
+
340
+ // Async maintenance
341
+ await db.flushAsync()
342
+ await db.compactAsync()
343
+
344
+ // Async bulk ingestion
345
+ const count = await db.bulkIngestAsync(records, { batchSize: 5000 })
346
+ ```
347
+
348
+ ### OpenTelemetry Integration
349
+
350
+ vectlite ships with optional OpenTelemetry tracing. When enabled, every search
351
+ call is wrapped in a span carrying semantic DB attributes and search-specific
352
+ metrics. `@opentelemetry/api` is loaded lazily -- it is **not** a runtime
353
+ dependency.
354
+
355
+ ```js
356
+ const vectlite = require('vectlite')
357
+
358
+ // Auto-detect: resolves a tracer from @opentelemetry/api if installed
359
+ const tracer = vectlite.configureOpenTelemetry()
360
+
361
+ // Or supply your own tracer
362
+ vectlite.configureOpenTelemetry({ tracer: myTracer })
363
+
364
+ // Custom tracer name (default: 'vectlite')
365
+ vectlite.configureOpenTelemetry({ tracerName: 'my-app' })
366
+
367
+ // Disable
368
+ vectlite.configureOpenTelemetry(false)
369
+ ```
370
+
371
+ When a tracer is active, each `search` / `searchWithStats` / `searchAsync` /
372
+ `searchWithStatsAsync` call creates a `vectlite.search` span with these
373
+ attributes:
374
+
375
+ | Attribute | Description |
376
+ |---|---|
377
+ | `db.system` | Always `"vectlite"` |
378
+ | `db.operation.name` | Always `"search"` |
379
+ | `vectlite.search.k` | Requested result count |
380
+ | `vectlite.search.namespace` | Target namespace |
381
+ | `vectlite.search.has_dense` | Whether a dense query vector was provided |
382
+ | `vectlite.search.has_sparse` | Whether sparse terms were provided |
383
+ | `vectlite.search.fusion` | Fusion strategy (`"linear"` or `"rrf"`) |
384
+ | `vectlite.search.used_ann` | Whether HNSW was used (set after completion) |
385
+ | `vectlite.search.result_count` | Number of results returned (set after completion) |
386
+ | `vectlite.search.total_us` | Total search time in microseconds (set after completion) |
387
+
388
+ If a search throws, the span records the exception and sets an error status
389
+ before re-throwing.
213
390
 
214
391
  ## Database Methods Reference
215
392
 
@@ -225,29 +402,46 @@ Quantization parameters persist across reopens in a `.vdb.quant` sidecar file. T
225
402
  | `db.delete(id, { namespace })` | Delete a single record |
226
403
  | `db.deleteMany(ids, { namespace })` | Delete multiple records by id |
227
404
  | `db.deleteByFilter(filter, { namespace })` | Delete all records matching a filter |
405
+ | `db.updateMetadata(id, metadata, { namespace })` | Merge a metadata patch into an existing record (no vector rewrite) |
406
+ | `db.setTtl(id, seconds, { namespace })` | Set time-to-live on a record (seconds from now) |
407
+ | `db.clearTtl(id, { namespace })` | Remove TTL from a record |
228
408
 
229
409
  ### Read Methods
230
410
 
231
411
  | Method | Description |
232
412
  |---|---|
233
413
  | `db.get(id, { namespace })` | Get a single record by id |
234
- | `db.search(query, options)` | Search and return a list of results |
414
+ | `db.search(query, options)` or `db.search({ query, ...options })` | Search and return a list of results |
235
415
  | `db.searchWithStats(query, options)` | Search with detailed performance stats |
236
416
  | `db.count({ namespace, filter })` | Count records, optionally scoped by namespace/filter |
237
417
  | `db.list({ namespace, filter, limit, offset })` | List records without issuing a vector query |
418
+ | `db.listCursor({ namespace, filter, limit, cursor })` | Cursor-based pagination for large collections |
238
419
  | `db.namespaces()` | List all namespaces |
239
420
  | `db.dimension` | Vector dimension (property) |
240
421
  | `db.path` | Database file path (property) |
422
+ | `db.metric` | Distance metric name: `"cosine"`, `"euclidean"`, `"dotproduct"`, or `"manhattan"` (property) |
241
423
  | `db.readOnly` | Whether the database is read-only (property) |
242
424
 
425
+ ### Index Methods
426
+
427
+ | Method | Description |
428
+ |---|---|
429
+ | `db.createIndex(field, indexType)` | Create a payload index (`'keyword'` or `'numeric'`) on a metadata field |
430
+ | `db.dropIndex(field)` | Remove an index |
431
+ | `db.listIndexes()` | List all active indexes as `[{ field, type }, ...]` |
432
+
243
433
  ### Quantization Methods
244
434
 
245
435
  | Method | Description |
246
436
  |---|---|
247
- | `db.enableQuantization(method, optionsJson)` | Enable quantization (`'scalar'`, `'binary'`, or `'product'`) |
437
+ | `db.enableQuantization(method, options)` | Enable quantization (`'scalar'`, `'binary'`, or `'pq'` / `'product'`) |
248
438
  | `db.disableQuantization()` | Disable quantization and remove persisted parameters |
249
439
  | `db.isQuantized` | Whether quantization is enabled (property) |
250
440
  | `db.quantizationMethod` | Active method name or `null` (property) |
441
+ | `db.validNumSubVectors()` | Valid PQ `numSubVectors` values for this database dimension |
442
+ | `db.enableMultiVectorQuantization(space, options)` | Enable 2-bit quantization for a multi-vector space |
443
+ | `db.disableMultiVectorQuantization(space)` | Disable multi-vector quantization for a space |
444
+ | `db.isMultiVectorQuantized(space)` | Whether multi-vector quantization is enabled for a space |
251
445
 
252
446
  ### Maintenance Methods
253
447
 
@@ -260,6 +454,16 @@ Quantization parameters persist across reopens in a `.vdb.quant` sidecar file. T
260
454
  | `db.transaction()` | Begin an atomic transaction |
261
455
  | `db.close()` | Flush pending state, release the file lock, and invalidate the handle |
262
456
 
457
+ ### Async Methods
458
+
459
+ | Method | Description |
460
+ |---|---|
461
+ | `db.searchAsync(query, options)` | Non-blocking search (returns Promise) |
462
+ | `db.searchWithStatsAsync(query, options)` | Non-blocking search with stats (returns Promise) |
463
+ | `db.flushAsync()` | Non-blocking flush/compact (returns Promise) |
464
+ | `db.compactAsync()` | Non-blocking compact (returns Promise) |
465
+ | `db.bulkIngestAsync(records, options)` | Non-blocking bulk import (returns Promise) |
466
+
263
467
  ## Filter Operators
264
468
 
265
469
  | Operator | Example | Description |
package/index.d.ts CHANGED
@@ -9,6 +9,7 @@ export type MetadataValue =
9
9
  export type Metadata = { [key: string]: MetadataValue }
10
10
  export type SparseVector = { [term: string]: number }
11
11
  export type NamedVectors = { [name: string]: number[] }
12
+ export type MultiVectors = { [space: string]: number[][] }
12
13
  export type Filter = { [key: string]: unknown }
13
14
  export type TextEmbedding = ArrayLike<number>
14
15
  export type TextEmbeddingResult = TextEmbedding | Promise<TextEmbedding>
@@ -21,6 +22,7 @@ export interface Record {
21
22
  vectors: NamedVectors
22
23
  sparse: SparseVector
23
24
  metadata: Metadata
25
+ expires_at: number | null
24
26
  }
25
27
 
26
28
  export interface SearchTimings {
@@ -41,6 +43,8 @@ export interface SearchStats {
41
43
  ann_loaded_from_disk: boolean
42
44
  wal_entries_replayed: number
43
45
  fusion: string
46
+ effective_dimension: number
47
+ matryoshka_truncated: boolean
44
48
  rerank_applied: boolean
45
49
  rerank_count: number
46
50
  timings: SearchTimings
@@ -80,6 +84,7 @@ export interface WriteOptions {
80
84
  namespace?: string | null
81
85
  sparse?: SparseVector | null
82
86
  vectors?: NamedVectors | null
87
+ ttl?: number | null
83
88
  }
84
89
 
85
90
  export interface CountOptions {
@@ -92,6 +97,16 @@ export interface ListOptions extends CountOptions {
92
97
  offset?: number | null
93
98
  }
94
99
 
100
+ export interface ListCursorOptions extends CountOptions {
101
+ limit?: number | null
102
+ cursor?: string | null
103
+ }
104
+
105
+ export interface ListCursorResult {
106
+ records: Record[]
107
+ cursor: string | null
108
+ }
109
+
95
110
  export interface BulkIngestOptions {
96
111
  namespace?: string | null
97
112
  batchSize?: number
@@ -110,15 +125,59 @@ export interface SearchOptions {
110
125
  vectorName?: string | null
111
126
  fusion?: 'linear' | 'rrf'
112
127
  rrfK?: number
128
+ truncateDim?: number | null
113
129
  explain?: boolean
114
130
  queryVectors?: { [name: string]: number[] } | null
115
131
  vectorWeights?: { [name: string]: number } | null
116
132
  }
117
133
 
134
+ export interface SearchRequest extends SearchOptions {
135
+ query?: number[] | null
136
+ }
137
+
138
+ export type QuantizationMethod = 'scalar' | 'int8' | 'binary' | 'product' | 'pq'
139
+ export interface QuantizationOptions {
140
+ rescoreMultiplier?: number
141
+ rescore_multiplier?: number
142
+ numSubVectors?: number
143
+ num_sub_vectors?: number
144
+ numCentroids?: number
145
+ num_centroids?: number
146
+ trainingIterations?: number
147
+ training_iterations?: number
148
+ }
149
+
150
+ export interface MultiVectorWriteOptions {
151
+ namespace?: string | null
152
+ metadata?: Metadata | null
153
+ }
154
+
155
+ export interface MultiVectorSearchOptions {
156
+ k?: number
157
+ filter?: Filter | null
158
+ namespace?: string | null
159
+ }
160
+
161
+ export interface MultiVectorSearchResult {
162
+ namespace: string
163
+ id: string
164
+ score: number
165
+ metadata: Metadata
166
+ }
167
+
168
+ export interface MultiVectorQuantizationOptions {
169
+ method?: 'two_bit'
170
+ rescoreMultiplier?: number
171
+ rescore_multiplier?: number
172
+ }
173
+
174
+ export type DistanceMetric = 'cosine' | 'euclidean' | 'dotproduct' | 'manhattan' | 'l2' | 'dot' | 'ip' | 'l1'
175
+
118
176
  export interface OpenOptions {
119
177
  dimension?: number | null
120
178
  readOnly?: boolean
121
179
  lockTimeout?: number | null
180
+ metric?: DistanceMetric | null
122
181
  }
123
182
 
124
183
  export class VectLiteError extends Error {}
@@ -139,12 +198,14 @@ export class Database {
139
198
  readonly path: string
140
199
  readonly walPath: string
141
200
  readonly dimension: number
201
+ readonly metric: string
142
202
  readonly readOnly: boolean
143
203
 
144
204
  count(options?: CountOptions): number
145
205
  namespaces(): string[]
146
206
  close(): void
147
207
  list(options?: ListOptions): Record[]
208
+ listCursor(options?: ListCursorOptions): ListCursorResult
148
209
  transaction(): Transaction
149
210
  insert(id: string, vector: number[], metadata?: Metadata | null, options?: WriteOptions): void
150
211
  upsert(id: string, vector: number[], metadata?: Metadata | null, options?: WriteOptions): void
@@ -155,12 +216,37 @@ export class Database {
155
216
  delete(id: string, options?: { namespace?: string | null }): boolean
156
217
  deleteMany(ids: string[], options?: { namespace?: string | null }): number
157
218
  deleteByFilter(filter: Filter, options?: { namespace?: string | null }): number
219
+ updateMetadata(id: string, metadata: Metadata, options?: { namespace?: string | null }): boolean
220
+ setTtl(id: string, ttl: number, options?: { namespace?: string | null }): boolean
221
+ clearTtl(id: string, options?: { namespace?: string | null }): boolean
222
+ createIndex(field: string, indexType: 'keyword' | 'numeric'): boolean
223
+ dropIndex(field: string): boolean
224
+ listIndexes(): Array<{ field: string; type: 'keyword' | 'numeric' }>
225
+ readonly isQuantized: boolean
226
+ readonly quantizationMethod: 'scalar' | 'binary' | 'product' | null
227
+ enableQuantization(method?: QuantizationMethod, options?: QuantizationOptions | string): void
228
+ disableQuantization(): void
229
+ validNumSubVectors(): number[]
230
+ upsertMultiVectors(id: string, vector: number[], multiVectors: MultiVectors, options?: MultiVectorWriteOptions): void
231
+ searchMultiVector(space: string, queryTokens: number[][], options?: MultiVectorSearchOptions): MultiVectorSearchResult[]
232
+ enableMultiVectorQuantization(space: string, options?: MultiVectorQuantizationOptions | string): void
233
+ disableMultiVectorQuantization(space: string): void
234
+ isMultiVectorQuantized(space: string): boolean
158
235
  flush(): void
159
236
  compact(): void
160
237
  snapshot(dest: string): void
161
238
  backup(dest: string): void
239
+ search(request: SearchRequest): SearchResult[]
162
240
  search(query?: number[] | null, options?: SearchOptions): SearchResult[]
241
+ searchWithStats(request: SearchRequest): SearchResponse
163
242
  searchWithStats(query?: number[] | null, options?: SearchOptions): SearchResponse
243
+ searchAsync(request: SearchRequest): Promise<SearchResult[]>
244
+ searchAsync(query?: number[] | null, options?: SearchOptions): Promise<SearchResult[]>
245
+ searchWithStatsAsync(request: SearchRequest): Promise<SearchResponse>
246
+ searchWithStatsAsync(query?: number[] | null, options?: SearchOptions): Promise<SearchResponse>
247
+ flushAsync(): Promise<void>
248
+ compactAsync(): Promise<void>
249
+ bulkIngestAsync(records: Record[], options?: BulkIngestOptions): Promise<number>
164
250
  }
165
251
 
166
252
  export class Store {
@@ -171,11 +257,36 @@ export class Store {
171
257
  openCollectionReadOnly(name: string): Database
172
258
  dropCollection(name: string): boolean
173
259
  collections(): string[]
260
+ close(): void
174
261
  }
175
262
 
176
263
  export function open(path: string, options?: OpenOptions): Database
177
264
  export function openStore(root: string): Store
178
265
  export function restore(source: string, dest: string): Database
266
+ export interface OpenTelemetryOptions {
267
+ /** Pass `false` or `{ enabled: false }` to disable tracing. */
268
+ enabled?: boolean
269
+ /** Supply your own OTel `Tracer` instance. */
270
+ tracer?: unknown
271
+ /** Tracer name used when auto-resolving via `@opentelemetry/api`. Defaults to `'vectlite'`. */
272
+ tracerName?: string
273
+ }
274
+
275
+ /**
276
+ * Configure optional OpenTelemetry tracing for search operations.
277
+ *
278
+ * When a tracer is active, every `search`, `searchWithStats`, `searchAsync`,
279
+ * and `searchWithStatsAsync` call is wrapped in a span with semantic
280
+ * `db.system` / `db.operation.name` attributes and search-specific metrics.
281
+ *
282
+ * `@opentelemetry/api` is loaded lazily via `require()` -- it is **not** a
283
+ * runtime dependency. If the package is not installed the function returns
284
+ * `null` and search calls remain un-instrumented.
285
+ *
286
+ * @returns The resolved tracer, or `null` if tracing could not be configured.
287
+ */
288
+ export function configureOpenTelemetry(options?: OpenTelemetryOptions | false): unknown | null
289
+
179
290
  export function sparseTerms(text: string): SparseVector
180
291
  export function upsertText(
181
292
  db: Database,