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/index.js CHANGED
@@ -63,6 +63,7 @@ function loadNative() {
63
63
  const native = loadNative()
64
64
 
65
65
  const TOKEN_RE = /[a-z0-9]+/g
66
+ let otelTracer = null
66
67
 
67
68
  class VectLiteError extends Error {
68
69
  constructor(message, cause) {
@@ -102,10 +103,140 @@ function decode(value) {
102
103
  return value == null ? null : JSON.parse(value)
103
104
  }
104
105
 
106
+ function configureOpenTelemetry(options = {}) {
107
+ if (options === false || options?.enabled === false) {
108
+ otelTracer = null
109
+ return null
110
+ }
111
+ if (options?.tracer != null) {
112
+ otelTracer = options.tracer
113
+ return otelTracer
114
+ }
115
+ try {
116
+ const { trace } = require('@opentelemetry/api')
117
+ otelTracer = trace.getTracer(options?.tracerName ?? 'vectlite')
118
+ return otelTracer
119
+ } catch {
120
+ otelTracer = null
121
+ return null
122
+ }
123
+ }
124
+
125
+ function searchAttributes(query, options, stats = null) {
126
+ const attrs = {
127
+ 'db.system': 'vectlite',
128
+ 'db.operation.name': 'search',
129
+ 'vectlite.search.k': options?.k ?? 10,
130
+ 'vectlite.search.namespace': options?.namespace ?? '',
131
+ 'vectlite.search.all_namespaces': Boolean(options?.allNamespaces),
132
+ 'vectlite.search.has_dense': query != null,
133
+ 'vectlite.search.has_sparse': options?.sparse != null,
134
+ 'vectlite.search.fusion': options?.fusion ?? 'linear',
135
+ }
136
+ if (options?.vectorName != null) attrs['vectlite.search.vector_name'] = options.vectorName
137
+ if (options?.truncateDim != null) attrs['vectlite.search.truncate_dim'] = options.truncateDim
138
+ if (stats != null) {
139
+ attrs['vectlite.search.used_ann'] = Boolean(stats.used_ann)
140
+ attrs['vectlite.search.exact_fallback'] = Boolean(stats.exact_fallback)
141
+ attrs['vectlite.search.considered_count'] = stats.considered_count ?? 0
142
+ attrs['vectlite.search.result_count'] = stats.result_count ?? 0
143
+ attrs['vectlite.search.effective_dimension'] = stats.effective_dimension ?? 0
144
+ attrs['vectlite.search.matryoshka_truncated'] = Boolean(stats.matryoshka_truncated)
145
+ attrs['vectlite.search.total_us'] = stats.timings?.total_us ?? 0
146
+ }
147
+ return attrs
148
+ }
149
+
150
+ function withSearchSpan(query, options, fn) {
151
+ if (otelTracer == null) {
152
+ return fn()
153
+ }
154
+ return otelTracer.startActiveSpan('vectlite.search', { attributes: searchAttributes(query, options) }, (span) => {
155
+ try {
156
+ const value = fn()
157
+ if (isPromiseLike(value)) {
158
+ return value.then(
159
+ (resolved) => {
160
+ span.setAttributes(searchAttributes(query, options, resolved?.stats ?? null))
161
+ span.end()
162
+ return resolved
163
+ },
164
+ (error) => {
165
+ span.recordException?.(error)
166
+ span.setStatus?.({ code: 2, message: error?.message ?? String(error) })
167
+ span.end()
168
+ throw error
169
+ },
170
+ )
171
+ }
172
+ span.setAttributes(searchAttributes(query, options, value?.stats ?? null))
173
+ span.end()
174
+ return value
175
+ } catch (error) {
176
+ span.recordException?.(error)
177
+ span.setStatus?.({ code: 2, message: error?.message ?? String(error) })
178
+ span.end()
179
+ throw error
180
+ }
181
+ })
182
+ }
183
+
105
184
  function asArray(values) {
185
+ if (
186
+ values != null &&
187
+ typeof values === 'object' &&
188
+ !Array.isArray(values) &&
189
+ !ArrayBuffer.isView(values) &&
190
+ typeof values[Symbol.iterator] !== 'function' &&
191
+ typeof values.length !== 'number'
192
+ ) {
193
+ throw new TypeError('vector must be an array-like or iterable of numbers')
194
+ }
106
195
  return Array.from(values)
107
196
  }
108
197
 
198
+ function encodeNativeOptions(value) {
199
+ return typeof value === 'string' ? value : encode(value)
200
+ }
201
+
202
+ const SEARCH_OPTION_KEYS = new Set([
203
+ 'query',
204
+ 'k',
205
+ 'filter',
206
+ 'namespace',
207
+ 'allNamespaces',
208
+ 'sparse',
209
+ 'denseWeight',
210
+ 'sparseWeight',
211
+ 'fetchK',
212
+ 'mmrLambda',
213
+ 'vectorName',
214
+ 'fusion',
215
+ 'rrfK',
216
+ 'truncateDim',
217
+ 'explain',
218
+ 'queryVectors',
219
+ 'vectorWeights',
220
+ ])
221
+
222
+ function isSearchRequestObject(value) {
223
+ return (
224
+ value != null &&
225
+ typeof value === 'object' &&
226
+ !Array.isArray(value) &&
227
+ !ArrayBuffer.isView(value) &&
228
+ [...SEARCH_OPTION_KEYS].some((key) => Object.prototype.hasOwnProperty.call(value, key))
229
+ )
230
+ }
231
+
232
+ function normalizeSearchArgs(query, options) {
233
+ if (isSearchRequestObject(query) && (options == null || Object.keys(options).length === 0)) {
234
+ const { query: normalizedQuery = null, ...normalizedOptions } = query
235
+ return { query: normalizedQuery, options: normalizedOptions }
236
+ }
237
+ return { query, options: options ?? {} }
238
+ }
239
+
109
240
  function isPromiseLike(value) {
110
241
  return value != null && typeof value.then === 'function'
111
242
  }
@@ -123,6 +254,7 @@ function normalizeWriteOptions(options = {}) {
123
254
  namespace: options.namespace ?? null,
124
255
  sparse: options.sparse ?? null,
125
256
  vectors: options.vectors ?? null,
257
+ ttl: options.ttl ?? null,
126
258
  }
127
259
  }
128
260
 
@@ -150,16 +282,16 @@ class Transaction {
150
282
  }
151
283
 
152
284
  insert(id, vector, metadata = null, options = {}) {
153
- const { namespace, sparse, vectors } = normalizeWriteOptions(options)
285
+ const { namespace, sparse, vectors, ttl } = normalizeWriteOptions(options)
154
286
  return wrapError(() =>
155
- this._native.insert(id, asArray(vector), encode(metadata), namespace, encode(sparse), encode(vectors)),
287
+ this._native.insert(id, asArray(vector), encode(metadata), namespace, encode(sparse), encode(vectors), ttl),
156
288
  )
157
289
  }
158
290
 
159
291
  upsert(id, vector, metadata = null, options = {}) {
160
- const { namespace, sparse, vectors } = normalizeWriteOptions(options)
292
+ const { namespace, sparse, vectors, ttl } = normalizeWriteOptions(options)
161
293
  return wrapError(() =>
162
- this._native.upsert(id, asArray(vector), encode(metadata), namespace, encode(sparse), encode(vectors)),
294
+ this._native.upsert(id, asArray(vector), encode(metadata), namespace, encode(sparse), encode(vectors), ttl),
163
295
  )
164
296
  }
165
297
 
@@ -205,6 +337,10 @@ class Database {
205
337
  return wrapError(() => this._native.dimension)
206
338
  }
207
339
 
340
+ get metric() {
341
+ return wrapError(() => this._native.metric)
342
+ }
343
+
208
344
  get readOnly() {
209
345
  return wrapError(() => this._native.readOnly)
210
346
  }
@@ -234,21 +370,35 @@ class Database {
234
370
  )
235
371
  }
236
372
 
373
+ listCursor(options = {}) {
374
+ return wrapError(() => {
375
+ const raw = decode(
376
+ this._native.listCursor(
377
+ options.namespace ?? null,
378
+ encode(options.filter),
379
+ options.limit ?? null,
380
+ options.cursor ?? null,
381
+ ),
382
+ )
383
+ return { records: raw.records, cursor: raw.cursor ?? null }
384
+ })
385
+ }
386
+
237
387
  transaction() {
238
388
  return wrapError(() => new Transaction(this._native.transaction()))
239
389
  }
240
390
 
241
391
  insert(id, vector, metadata = null, options = {}) {
242
- const { namespace, sparse, vectors } = normalizeWriteOptions(options)
392
+ const { namespace, sparse, vectors, ttl } = normalizeWriteOptions(options)
243
393
  return wrapError(() =>
244
- this._native.insert(id, asArray(vector), encode(metadata), namespace, encode(sparse), encode(vectors)),
394
+ this._native.insert(id, asArray(vector), encode(metadata), namespace, encode(sparse), encode(vectors), ttl),
245
395
  )
246
396
  }
247
397
 
248
398
  upsert(id, vector, metadata = null, options = {}) {
249
- const { namespace, sparse, vectors } = normalizeWriteOptions(options)
399
+ const { namespace, sparse, vectors, ttl } = normalizeWriteOptions(options)
250
400
  return wrapError(() =>
251
- this._native.upsert(id, asArray(vector), encode(metadata), namespace, encode(sparse), encode(vectors)),
401
+ this._native.upsert(id, asArray(vector), encode(metadata), namespace, encode(sparse), encode(vectors), ttl),
252
402
  )
253
403
  }
254
404
 
@@ -282,6 +432,78 @@ class Database {
282
432
  return wrapError(() => this._native.deleteByFilter(encode(filter), options.namespace ?? null))
283
433
  }
284
434
 
435
+ updateMetadata(id, metadata, options = {}) {
436
+ return wrapError(() =>
437
+ this._native.updateMetadata(id, encode(metadata), options.namespace ?? null),
438
+ )
439
+ }
440
+
441
+ setTtl(id, ttl, options = {}) {
442
+ return wrapError(() => this._native.setTtl(id, ttl, options.namespace ?? null))
443
+ }
444
+
445
+ clearTtl(id, options = {}) {
446
+ return wrapError(() => this._native.clearTtl(id, options.namespace ?? null))
447
+ }
448
+
449
+ createIndex(field, indexType) {
450
+ return wrapError(() => this._native.createIndex(field, indexType))
451
+ }
452
+
453
+ dropIndex(field) {
454
+ return wrapError(() => this._native.dropIndex(field))
455
+ }
456
+
457
+ listIndexes() {
458
+ return wrapError(() => decode(this._native.listIndexes()))
459
+ }
460
+
461
+ enableQuantization(method = 'scalar', options = {}) {
462
+ return wrapError(() => this._native.enableQuantization(method, encodeNativeOptions(options)))
463
+ }
464
+
465
+ disableQuantization() {
466
+ return wrapError(() => this._native.disableQuantization())
467
+ }
468
+
469
+ get isQuantized() {
470
+ return wrapError(() => this._native.isQuantized)
471
+ }
472
+
473
+ get quantizationMethod() {
474
+ return wrapError(() => this._native.quantizationMethod)
475
+ }
476
+
477
+ validNumSubVectors() {
478
+ return wrapError(() => this._native.validNumSubVectors())
479
+ }
480
+
481
+ upsertMultiVectors(id, vector, multiVectors, options = {}) {
482
+ return wrapError(() =>
483
+ this._native.upsertMultiVectors(id, asArray(vector), encode(multiVectors), encode(options)),
484
+ )
485
+ }
486
+
487
+ searchMultiVector(space, queryTokens, options = {}) {
488
+ return wrapError(() =>
489
+ decode(this._native.searchMultiVector(space, encode(queryTokens), encode(options))),
490
+ )
491
+ }
492
+
493
+ enableMultiVectorQuantization(space, options = {}) {
494
+ return wrapError(() =>
495
+ this._native.enableMultiVectorQuantization(space, encodeNativeOptions(options)),
496
+ )
497
+ }
498
+
499
+ disableMultiVectorQuantization(space) {
500
+ return wrapError(() => this._native.disableMultiVectorQuantization(space))
501
+ }
502
+
503
+ isMultiVectorQuantized(space) {
504
+ return wrapError(() => this._native.isMultiVectorQuantized(space))
505
+ }
506
+
285
507
  flush() {
286
508
  return wrapError(() => this._native.flush())
287
509
  }
@@ -299,12 +521,68 @@ class Database {
299
521
  }
300
522
 
301
523
  search(query = null, options = {}) {
302
- return wrapError(() => decode(this._native.search(query == null ? null : asArray(query), encode(options))))
524
+ const normalized = normalizeSearchArgs(query, options)
525
+ return withSearchSpan(normalized.query, normalized.options, () =>
526
+ wrapError(() =>
527
+ decode(
528
+ this._native.search(
529
+ normalized.query == null ? null : asArray(normalized.query),
530
+ encode(normalized.options),
531
+ ),
532
+ ),
533
+ ),
534
+ )
303
535
  }
304
536
 
305
537
  searchWithStats(query = null, options = {}) {
306
- return wrapError(() =>
307
- decode(this._native.searchWithStats(query == null ? null : asArray(query), encode(options))),
538
+ const normalized = normalizeSearchArgs(query, options)
539
+ return withSearchSpan(normalized.query, normalized.options, () =>
540
+ wrapError(() =>
541
+ decode(
542
+ this._native.searchWithStats(
543
+ normalized.query == null ? null : asArray(normalized.query),
544
+ encode(normalized.options),
545
+ ),
546
+ ),
547
+ ),
548
+ )
549
+ }
550
+
551
+ searchAsync(query = null, options = {}) {
552
+ const normalized = normalizeSearchArgs(query, options)
553
+ return withSearchSpan(normalized.query, normalized.options, () =>
554
+ wrapAsync(
555
+ this._native.searchAsync(
556
+ normalized.query == null ? null : asArray(normalized.query),
557
+ encode(normalized.options),
558
+ ),
559
+ ).then(decode),
560
+ )
561
+ }
562
+
563
+ searchWithStatsAsync(query = null, options = {}) {
564
+ const normalized = normalizeSearchArgs(query, options)
565
+ return withSearchSpan(normalized.query, normalized.options, () =>
566
+ wrapAsync(
567
+ this._native.searchWithStatsAsync(
568
+ normalized.query == null ? null : asArray(normalized.query),
569
+ encode(normalized.options),
570
+ ),
571
+ ).then(decode),
572
+ )
573
+ }
574
+
575
+ flushAsync() {
576
+ return wrapAsync(this._native.flushAsync())
577
+ }
578
+
579
+ compactAsync() {
580
+ return wrapAsync(this._native.compactAsync())
581
+ }
582
+
583
+ bulkIngestAsync(records, options = {}) {
584
+ return wrapAsync(
585
+ this._native.bulkIngestAsync(encode(records), options.namespace ?? null, options.batchSize ?? 10_000),
308
586
  )
309
587
  }
310
588
  }
@@ -341,11 +619,15 @@ class Store {
341
619
  collections() {
342
620
  return wrapError(() => this._native.collections())
343
621
  }
622
+
623
+ close() {
624
+ return wrapError(() => this._native.close())
625
+ }
344
626
  }
345
627
 
346
628
  function open(path, options = {}) {
347
629
  return wrapError(() =>
348
- new Database(native.open(path, options.dimension ?? null, options.readOnly ?? false, options.lockTimeout ?? null)),
630
+ new Database(native.open(path, options.dimension ?? null, options.readOnly ?? false, options.lockTimeout ?? null, options.metric ?? null)),
349
631
  )
350
632
  }
351
633
 
@@ -393,6 +675,7 @@ module.exports = {
393
675
  Store,
394
676
  Transaction,
395
677
  VectLiteError,
678
+ configureOpenTelemetry,
396
679
  open,
397
680
  openStore,
398
681
  restore,
package/native/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "vectlite-node"
3
- version = "0.1.12"
3
+ version = "0.9.1"
4
4
  edition = "2024"
5
5
  license = "MIT"
6
6
  description = "Node.js bindings for vectlite."