@wiscale/velesdb-sdk 1.4.1 → 1.5.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/dist/index.mjs CHANGED
@@ -25,6 +25,12 @@ var NotFoundError = class extends VelesDBError {
25
25
  this.name = "NotFoundError";
26
26
  }
27
27
  };
28
+ var BackpressureError = class extends VelesDBError {
29
+ constructor(message = "Server backpressure: too many requests") {
30
+ super(message, "BACKPRESSURE");
31
+ this.name = "BackpressureError";
32
+ }
33
+ };
28
34
 
29
35
  // src/backends/wasm.ts
30
36
  var WasmBackend = class {
@@ -56,6 +62,33 @@ var WasmBackend = class {
56
62
  throw new ConnectionError("WASM backend not initialized");
57
63
  }
58
64
  }
65
+ normalizeIdString(id) {
66
+ const trimmed = id.trim();
67
+ return /^\d+$/.test(trimmed) ? trimmed : null;
68
+ }
69
+ canonicalPayloadKeyFromResultId(id) {
70
+ if (typeof id === "bigint") {
71
+ return id.toString();
72
+ }
73
+ if (typeof id === "number") {
74
+ return String(Math.trunc(id));
75
+ }
76
+ const normalized = this.normalizeIdString(id);
77
+ if (normalized !== null) {
78
+ return normalized.replace(/^0+(?=\d)/, "");
79
+ }
80
+ return String(this.toNumericId(id));
81
+ }
82
+ canonicalPayloadKey(id) {
83
+ if (typeof id === "number") {
84
+ return String(Math.trunc(id));
85
+ }
86
+ const normalized = this.normalizeIdString(id);
87
+ if (normalized !== null) {
88
+ return normalized.replace(/^0+(?=\d)/, "");
89
+ }
90
+ return String(this.toNumericId(id));
91
+ }
59
92
  async createCollection(name, config) {
60
93
  this.ensureInitialized();
61
94
  if (this.collections.has(name)) {
@@ -121,9 +154,13 @@ var WasmBackend = class {
121
154
  "DIMENSION_MISMATCH"
122
155
  );
123
156
  }
124
- collection.store.insert(BigInt(id), vector);
125
157
  if (doc.payload) {
126
- collection.payloads.set(String(doc.id), doc.payload);
158
+ collection.store.insert_with_payload(BigInt(id), vector, doc.payload);
159
+ } else {
160
+ collection.store.insert(BigInt(id), vector);
161
+ }
162
+ if (doc.payload) {
163
+ collection.payloads.set(this.canonicalPayloadKey(doc.id), doc.payload);
127
164
  }
128
165
  }
129
166
  async insertBatch(collectionName, docs) {
@@ -142,14 +179,22 @@ var WasmBackend = class {
142
179
  }
143
180
  }
144
181
  collection.store.reserve(docs.length);
145
- const batch = docs.map((doc) => [
146
- BigInt(this.toNumericId(doc.id)),
147
- Array.isArray(doc.vector) ? doc.vector : Array.from(doc.vector)
148
- ]);
149
- collection.store.insert_batch(batch);
182
+ const batch = [];
183
+ for (const doc of docs) {
184
+ const id = BigInt(this.toNumericId(doc.id));
185
+ const vector = doc.vector instanceof Float32Array ? doc.vector : new Float32Array(doc.vector);
186
+ if (doc.payload) {
187
+ collection.store.insert_with_payload(id, vector, doc.payload);
188
+ } else {
189
+ batch.push([id, Array.from(vector)]);
190
+ }
191
+ }
192
+ if (batch.length > 0) {
193
+ collection.store.insert_batch(batch);
194
+ }
150
195
  for (const doc of docs) {
151
196
  if (doc.payload) {
152
- collection.payloads.set(String(doc.id), doc.payload);
197
+ collection.payloads.set(this.canonicalPayloadKey(doc.id), doc.payload);
153
198
  }
154
199
  }
155
200
  }
@@ -167,12 +212,43 @@ var WasmBackend = class {
167
212
  );
168
213
  }
169
214
  const k = options?.k ?? 10;
215
+ if (options?.sparseVector) {
216
+ const { indices, values } = this.sparseVectorToArrays(options.sparseVector);
217
+ if (queryVector.length > 0 && collection.config.dimension && collection.config.dimension > 0) {
218
+ const denseResults = collection.store.search(queryVector, k);
219
+ const sparseResults = collection.store.sparse_search(
220
+ new Uint32Array(indices),
221
+ new Float32Array(values),
222
+ k
223
+ );
224
+ const sparseArray = sparseResults;
225
+ const denseForFuse = denseResults.map(([id, score]) => [Number(id), score]);
226
+ const sparseForFuse = sparseArray.map((r) => [Number(r.doc_id), r.score]);
227
+ const fused = this.wasmModule.hybrid_search_fuse(denseForFuse, sparseForFuse, 60);
228
+ return fused.slice(0, k).map((r) => ({
229
+ id: String(r.doc_id),
230
+ score: r.score,
231
+ payload: collection.payloads.get(this.canonicalPayloadKeyFromResultId(r.doc_id))
232
+ }));
233
+ } else {
234
+ const sparseResults = collection.store.sparse_search(
235
+ new Uint32Array(indices),
236
+ new Float32Array(values),
237
+ k
238
+ );
239
+ return sparseResults.map((r) => ({
240
+ id: String(r.doc_id),
241
+ score: r.score,
242
+ payload: collection.payloads.get(this.canonicalPayloadKeyFromResultId(r.doc_id))
243
+ }));
244
+ }
245
+ }
170
246
  if (options?.filter) {
171
247
  const results = collection.store.search_with_filter(queryVector, k, options.filter);
172
248
  return results.map((r) => ({
173
249
  id: String(r.id),
174
250
  score: r.score,
175
- payload: r.payload || collection.payloads.get(String(r.id))
251
+ payload: r.payload || collection.payloads.get(this.canonicalPayloadKeyFromResultId(r.id))
176
252
  }));
177
253
  }
178
254
  const rawResults = collection.store.search(queryVector, k);
@@ -182,7 +258,7 @@ var WasmBackend = class {
182
258
  id: stringId,
183
259
  score
184
260
  };
185
- const payload = collection.payloads.get(stringId);
261
+ const payload = collection.payloads.get(this.canonicalPayloadKeyFromResultId(id));
186
262
  if (payload) {
187
263
  result.payload = payload;
188
264
  }
@@ -206,7 +282,7 @@ var WasmBackend = class {
206
282
  const numericId = this.toNumericId(id);
207
283
  const removed = collection.store.remove(BigInt(numericId));
208
284
  if (removed) {
209
- collection.payloads.delete(String(id));
285
+ collection.payloads.delete(this.canonicalPayloadKey(id));
210
286
  }
211
287
  return removed;
212
288
  }
@@ -216,38 +292,140 @@ var WasmBackend = class {
216
292
  if (!collection) {
217
293
  throw new NotFoundError(`Collection '${collectionName}'`);
218
294
  }
219
- const payload = collection.payloads.get(String(id));
220
- if (!payload) {
295
+ const numericId = this.toNumericId(id);
296
+ const point = collection.store.get(BigInt(numericId));
297
+ if (!point) {
221
298
  return null;
222
299
  }
300
+ const payload = point.payload ?? collection.payloads.get(this.canonicalPayloadKey(numericId));
223
301
  return {
224
- id,
225
- vector: [],
226
- // Not available in current WASM impl
302
+ id: String(point.id),
303
+ vector: Array.isArray(point.vector) ? point.vector : Array.from(point.vector),
227
304
  payload
228
305
  };
229
306
  }
230
307
  async textSearch(_collection, _query, _options) {
231
- throw new VelesDBError(
232
- "Text search is not supported in WASM backend. Use REST backend for BM25 search.",
233
- "NOT_SUPPORTED"
234
- );
308
+ this.ensureInitialized();
309
+ const collection = this.collections.get(_collection);
310
+ if (!collection) {
311
+ throw new NotFoundError(`Collection '${_collection}'`);
312
+ }
313
+ const k = _options?.k ?? 10;
314
+ const field = void 0;
315
+ const raw = collection.store.text_search(_query, k, field);
316
+ return raw.map((r) => {
317
+ if (Array.isArray(r)) {
318
+ const key2 = this.canonicalPayloadKeyFromResultId(r[0]);
319
+ return { id: String(r[0]), score: r[1], payload: collection.payloads.get(key2) };
320
+ }
321
+ const key = this.canonicalPayloadKeyFromResultId(r.id);
322
+ return { id: String(r.id), score: r.score, payload: r.payload ?? collection.payloads.get(key) };
323
+ });
235
324
  }
236
325
  async hybridSearch(_collection, _vector, _textQuery, _options) {
237
- throw new VelesDBError(
238
- "Hybrid search is not supported in WASM backend. Use REST backend for hybrid search.",
239
- "NOT_SUPPORTED"
240
- );
326
+ this.ensureInitialized();
327
+ const collection = this.collections.get(_collection);
328
+ if (!collection) {
329
+ throw new NotFoundError(`Collection '${_collection}'`);
330
+ }
331
+ const queryVector = _vector instanceof Float32Array ? _vector : new Float32Array(_vector);
332
+ const k = _options?.k ?? 10;
333
+ const vectorWeight = _options?.vectorWeight ?? 0.5;
334
+ const raw = collection.store.hybrid_search(queryVector, _textQuery, k, vectorWeight);
335
+ return raw.map((r) => {
336
+ const key = this.canonicalPayloadKeyFromResultId(r.id);
337
+ return {
338
+ id: String(r.id),
339
+ score: r.score,
340
+ payload: r.payload ?? collection.payloads.get(key)
341
+ };
342
+ });
241
343
  }
242
344
  async query(_collection, _queryString, _params, _options) {
345
+ this.ensureInitialized();
346
+ const collection = this.collections.get(_collection);
347
+ if (!collection) {
348
+ throw new NotFoundError(`Collection '${_collection}'`);
349
+ }
350
+ const paramsVector = _params?.q;
351
+ if (!Array.isArray(paramsVector) && !(paramsVector instanceof Float32Array)) {
352
+ throw new VelesDBError(
353
+ "WASM query() expects params.q to contain the query embedding vector.",
354
+ "BAD_REQUEST"
355
+ );
356
+ }
357
+ const requestedK = _params?.k;
358
+ const k = typeof requestedK === "number" && Number.isInteger(requestedK) && requestedK > 0 ? requestedK : 10;
359
+ const raw = collection.store.query(
360
+ paramsVector instanceof Float32Array ? paramsVector : new Float32Array(paramsVector),
361
+ k
362
+ );
363
+ return {
364
+ results: raw.map((r) => ({
365
+ nodeId: r.nodeId ?? r.node_id,
366
+ vectorScore: r.vectorScore ?? r.vector_score ?? null,
367
+ graphScore: r.graphScore ?? r.graph_score ?? null,
368
+ fusedScore: r.fusedScore ?? r.fused_score ?? 0,
369
+ bindings: r.bindings ?? {},
370
+ columnData: r.columnData ?? r.column_data ?? null
371
+ })),
372
+ stats: {
373
+ executionTimeMs: 0,
374
+ strategy: "wasm-query",
375
+ scannedNodes: raw.length
376
+ }
377
+ };
378
+ }
379
+ async multiQuerySearch(_collection, _vectors, _options) {
380
+ this.ensureInitialized();
381
+ const collection = this.collections.get(_collection);
382
+ if (!collection) {
383
+ throw new NotFoundError(`Collection '${_collection}'`);
384
+ }
385
+ if (_vectors.length === 0) {
386
+ return [];
387
+ }
388
+ const numVectors = _vectors.length;
389
+ const dimension = collection.config.dimension ?? 0;
390
+ const flat = new Float32Array(numVectors * dimension);
391
+ _vectors.forEach((vector, idx) => {
392
+ const src = vector instanceof Float32Array ? vector : new Float32Array(vector);
393
+ flat.set(src, idx * dimension);
394
+ });
395
+ const strategy = _options?.fusion ?? "rrf";
396
+ if (strategy === "weighted") {
397
+ throw new VelesDBError(
398
+ "Fusion strategy 'weighted' is not supported in WASM backend.",
399
+ "NOT_SUPPORTED"
400
+ );
401
+ }
402
+ const raw = collection.store.multi_query_search(
403
+ flat,
404
+ numVectors,
405
+ _options?.k ?? 10,
406
+ strategy,
407
+ _options?.fusionParams?.k ?? 60
408
+ );
409
+ return raw.map((r) => {
410
+ if (Array.isArray(r)) {
411
+ const key2 = this.canonicalPayloadKeyFromResultId(r[0]);
412
+ return { id: String(r[0]), score: r[1], payload: collection.payloads.get(key2) };
413
+ }
414
+ const key = this.canonicalPayloadKeyFromResultId(r.id);
415
+ return { id: String(r.id), score: r.score, payload: r.payload ?? collection.payloads.get(key) };
416
+ });
417
+ }
418
+ async queryExplain(_queryString, _params) {
419
+ this.ensureInitialized();
243
420
  throw new VelesDBError(
244
- "VelesQL queries are not supported in WASM backend. Use REST backend for query support.",
421
+ "Query explain is not supported in WASM backend. Use REST backend for EXPLAIN support.",
245
422
  "NOT_SUPPORTED"
246
423
  );
247
424
  }
248
- async multiQuerySearch(_collection, _vectors, _options) {
425
+ async collectionSanity(_collection) {
426
+ this.ensureInitialized();
249
427
  throw new VelesDBError(
250
- "Multi-query fusion is not supported in WASM backend. Use REST backend for MQF search.",
428
+ "Collection sanity endpoint is not supported in WASM backend. Use REST backend for diagnostics.",
251
429
  "NOT_SUPPORTED"
252
430
  );
253
431
  }
@@ -273,13 +451,25 @@ var WasmBackend = class {
273
451
  this.collections.clear();
274
452
  this._initialized = false;
275
453
  }
454
+ sparseVectorToArrays(sv) {
455
+ const indices = [];
456
+ const values = [];
457
+ for (const [k, v] of Object.entries(sv)) {
458
+ indices.push(Number(k));
459
+ values.push(v);
460
+ }
461
+ return { indices, values };
462
+ }
276
463
  toNumericId(id) {
277
464
  if (typeof id === "number") {
278
465
  return id;
279
466
  }
280
- const parsed = parseInt(id, 10);
281
- if (!isNaN(parsed)) {
282
- return parsed;
467
+ const normalized = this.normalizeIdString(id);
468
+ if (normalized !== null) {
469
+ const parsed = Number(normalized);
470
+ if (Number.isSafeInteger(parsed)) {
471
+ return parsed;
472
+ }
283
473
  }
284
474
  let hash = 0;
285
475
  for (let i = 0; i < id.length; i++) {
@@ -343,6 +533,23 @@ var WasmBackend = class {
343
533
  "NOT_SUPPORTED"
344
534
  );
345
535
  }
536
+ // ========================================================================
537
+ // Sparse / PQ / Streaming (v1.5)
538
+ // ========================================================================
539
+ async trainPq(_collection, _options) {
540
+ this.ensureInitialized();
541
+ throw new VelesDBError(
542
+ "PQ training is not available in WASM mode. Use REST backend for PQ training.",
543
+ "NOT_SUPPORTED"
544
+ );
545
+ }
546
+ async streamInsert(_collection, _docs) {
547
+ this.ensureInitialized();
548
+ throw new VelesDBError(
549
+ "Streaming insert is not available in WASM mode. Use REST backend for streaming.",
550
+ "NOT_SUPPORTED"
551
+ );
552
+ }
346
553
  };
347
554
 
348
555
  // src/backends/rest.ts
@@ -405,8 +612,10 @@ var RestBackend = class {
405
612
  return {};
406
613
  }
407
614
  const payload = data;
408
- const code = typeof payload.code === "string" ? payload.code : void 0;
409
- const messageField = payload.message ?? payload.error;
615
+ const nestedError = payload.error && typeof payload.error === "object" ? payload.error : void 0;
616
+ const codeField = nestedError?.code ?? payload.code;
617
+ const code = typeof codeField === "string" ? codeField : void 0;
618
+ const messageField = nestedError?.message ?? payload.message ?? payload.error;
410
619
  const message = typeof messageField === "string" ? messageField : void 0;
411
620
  return { code, message };
412
621
  }
@@ -436,6 +645,19 @@ var RestBackend = class {
436
645
  }
437
646
  return 0;
438
647
  }
648
+ parseRestPointId(id) {
649
+ if (typeof id !== "number" || !Number.isFinite(id) || id < 0 || !Number.isInteger(id) || id > Number.MAX_SAFE_INTEGER) {
650
+ throw new ValidationError(
651
+ `REST backend requires numeric u64-compatible IDs in JS safe integer range (0..${Number.MAX_SAFE_INTEGER}). Received: ${String(id)}`
652
+ );
653
+ }
654
+ return id;
655
+ }
656
+ isLikelyAggregationQuery(query) {
657
+ return /\bGROUP\s+BY\b|\bHAVING\b|\bCOUNT\s*\(|\bSUM\s*\(|\bAVG\s*\(|\bMIN\s*\(|\bMAX\s*\(/i.test(
658
+ query
659
+ );
660
+ }
439
661
  async request(method, path, body) {
440
662
  const url = `${this.baseUrl}${path}`;
441
663
  const headers = {
@@ -524,13 +746,14 @@ var RestBackend = class {
524
746
  }
525
747
  async insert(collection, doc) {
526
748
  this.ensureInitialized();
749
+ const restId = this.parseRestPointId(doc.id);
527
750
  const vector = doc.vector instanceof Float32Array ? Array.from(doc.vector) : doc.vector;
528
751
  const response = await this.request(
529
752
  "POST",
530
753
  `/collections/${encodeURIComponent(collection)}/points`,
531
754
  {
532
755
  points: [{
533
- id: doc.id,
756
+ id: restId,
534
757
  vector,
535
758
  payload: doc.payload
536
759
  }]
@@ -546,7 +769,7 @@ var RestBackend = class {
546
769
  async insertBatch(collection, docs) {
547
770
  this.ensureInitialized();
548
771
  const vectors = docs.map((doc) => ({
549
- id: doc.id,
772
+ id: this.parseRestPointId(doc.id),
550
773
  vector: doc.vector instanceof Float32Array ? Array.from(doc.vector) : doc.vector,
551
774
  payload: doc.payload
552
775
  }));
@@ -562,18 +785,29 @@ var RestBackend = class {
562
785
  throw new VelesDBError(response.error.message, response.error.code);
563
786
  }
564
787
  }
788
+ sparseVectorToRestFormat(sv) {
789
+ const result = {};
790
+ for (const [k, v] of Object.entries(sv)) {
791
+ result[String(k)] = v;
792
+ }
793
+ return result;
794
+ }
565
795
  async search(collection, query, options) {
566
796
  this.ensureInitialized();
567
797
  const queryVector = query instanceof Float32Array ? Array.from(query) : query;
798
+ const body = {
799
+ vector: queryVector,
800
+ top_k: options?.k ?? 10,
801
+ filter: options?.filter,
802
+ include_vectors: options?.includeVectors ?? false
803
+ };
804
+ if (options?.sparseVector) {
805
+ body.sparse_vector = this.sparseVectorToRestFormat(options.sparseVector);
806
+ }
568
807
  const response = await this.request(
569
808
  "POST",
570
809
  `/collections/${encodeURIComponent(collection)}/search`,
571
- {
572
- vector: queryVector,
573
- k: options?.k ?? 10,
574
- filter: options?.filter,
575
- include_vectors: options?.includeVectors ?? false
576
- }
810
+ body
577
811
  );
578
812
  if (response.error) {
579
813
  if (response.error.code === "NOT_FOUND") {
@@ -581,7 +815,7 @@ var RestBackend = class {
581
815
  }
582
816
  throw new VelesDBError(response.error.message, response.error.code);
583
817
  }
584
- return response.data ?? [];
818
+ return response.data?.results ?? [];
585
819
  }
586
820
  async searchBatch(collection, searches) {
587
821
  this.ensureInitialized();
@@ -605,9 +839,10 @@ var RestBackend = class {
605
839
  }
606
840
  async delete(collection, id) {
607
841
  this.ensureInitialized();
842
+ const restId = this.parseRestPointId(id);
608
843
  const response = await this.request(
609
844
  "DELETE",
610
- `/collections/${encodeURIComponent(collection)}/points/${encodeURIComponent(String(id))}`
845
+ `/collections/${encodeURIComponent(collection)}/points/${encodeURIComponent(String(restId))}`
611
846
  );
612
847
  if (response.error) {
613
848
  if (response.error.code === "NOT_FOUND") {
@@ -619,9 +854,10 @@ var RestBackend = class {
619
854
  }
620
855
  async get(collection, id) {
621
856
  this.ensureInitialized();
857
+ const restId = this.parseRestPointId(id);
622
858
  const response = await this.request(
623
859
  "GET",
624
- `/collections/${encodeURIComponent(collection)}/points/${encodeURIComponent(String(id))}`
860
+ `/collections/${encodeURIComponent(collection)}/points/${encodeURIComponent(String(restId))}`
625
861
  );
626
862
  if (response.error) {
627
863
  if (response.error.code === "NOT_FOUND") {
@@ -672,14 +908,18 @@ var RestBackend = class {
672
908
  }
673
909
  return response.data?.results ?? [];
674
910
  }
675
- async query(collection, queryString, params, _options) {
911
+ async query(collection, queryString, params, options) {
676
912
  this.ensureInitialized();
913
+ const endpoint = this.isLikelyAggregationQuery(queryString) ? "/aggregate" : "/query";
677
914
  const response = await this.request(
678
915
  "POST",
679
- "/query",
916
+ endpoint,
680
917
  {
681
918
  query: queryString,
682
- params: params ?? {}
919
+ params: params ?? {},
920
+ collection,
921
+ timeout_ms: options?.timeoutMs,
922
+ stream: options?.stream ?? false
683
923
  }
684
924
  );
685
925
  if (response.error) {
@@ -689,6 +929,16 @@ var RestBackend = class {
689
929
  throw new VelesDBError(response.error.message, response.error.code);
690
930
  }
691
931
  const rawData = response.data;
932
+ if (rawData && Object.prototype.hasOwnProperty.call(rawData, "result")) {
933
+ return {
934
+ result: rawData.result,
935
+ stats: {
936
+ executionTimeMs: rawData.timing_ms ?? 0,
937
+ strategy: "aggregation",
938
+ scannedNodes: 0
939
+ }
940
+ };
941
+ }
692
942
  return {
693
943
  results: (rawData?.results ?? []).map((r) => ({
694
944
  // Server returns `id` (u64), map to nodeId with precision handling
@@ -710,6 +960,82 @@ var RestBackend = class {
710
960
  }
711
961
  };
712
962
  }
963
+ async queryExplain(queryString, params) {
964
+ this.ensureInitialized();
965
+ const response = await this.request(
966
+ "POST",
967
+ "/query/explain",
968
+ {
969
+ query: queryString,
970
+ params: params ?? {}
971
+ }
972
+ );
973
+ if (response.error) {
974
+ throw new VelesDBError(response.error.message, response.error.code);
975
+ }
976
+ const data = response.data;
977
+ return {
978
+ query: data.query,
979
+ queryType: data.query_type,
980
+ collection: data.collection,
981
+ plan: data.plan.map((step) => ({
982
+ step: step.step,
983
+ operation: step.operation,
984
+ description: step.description,
985
+ estimatedRows: step.estimated_rows
986
+ })),
987
+ estimatedCost: {
988
+ usesIndex: data.estimated_cost.uses_index,
989
+ indexName: data.estimated_cost.index_name,
990
+ selectivity: data.estimated_cost.selectivity,
991
+ complexity: data.estimated_cost.complexity
992
+ },
993
+ features: {
994
+ hasVectorSearch: data.features.has_vector_search,
995
+ hasFilter: data.features.has_filter,
996
+ hasOrderBy: data.features.has_order_by,
997
+ hasGroupBy: data.features.has_group_by,
998
+ hasAggregation: data.features.has_aggregation,
999
+ hasJoin: data.features.has_join,
1000
+ hasFusion: data.features.has_fusion,
1001
+ limit: data.features.limit,
1002
+ offset: data.features.offset
1003
+ }
1004
+ };
1005
+ }
1006
+ async collectionSanity(collection) {
1007
+ this.ensureInitialized();
1008
+ const response = await this.request(
1009
+ "GET",
1010
+ `/collections/${encodeURIComponent(collection)}/sanity`
1011
+ );
1012
+ if (response.error) {
1013
+ if (response.error.code === "NOT_FOUND") {
1014
+ throw new NotFoundError(`Collection '${collection}'`);
1015
+ }
1016
+ throw new VelesDBError(response.error.message, response.error.code);
1017
+ }
1018
+ const data = response.data;
1019
+ return {
1020
+ collection: data.collection,
1021
+ dimension: data.dimension,
1022
+ metric: data.metric,
1023
+ pointCount: data.point_count,
1024
+ isEmpty: data.is_empty,
1025
+ checks: {
1026
+ hasVectors: data.checks.has_vectors,
1027
+ searchReady: data.checks.search_ready,
1028
+ dimensionConfigured: data.checks.dimension_configured
1029
+ },
1030
+ diagnostics: {
1031
+ searchRequestsTotal: data.diagnostics.search_requests_total,
1032
+ dimensionMismatchTotal: data.diagnostics.dimension_mismatch_total,
1033
+ emptySearchResultsTotal: data.diagnostics.empty_search_results_total,
1034
+ filterParseErrorsTotal: data.diagnostics.filter_parse_errors_total
1035
+ },
1036
+ hints: data.hints ?? []
1037
+ };
1038
+ }
713
1039
  async multiQuerySearch(collection, vectors, options) {
714
1040
  this.ensureInitialized();
715
1041
  const formattedVectors = vectors.map(
@@ -723,6 +1049,9 @@ var RestBackend = class {
723
1049
  top_k: options?.k ?? 10,
724
1050
  strategy: options?.fusion ?? "rrf",
725
1051
  rrf_k: options?.fusionParams?.k ?? 60,
1052
+ avg_weight: options?.fusionParams?.avgWeight,
1053
+ max_weight: options?.fusionParams?.maxWeight,
1054
+ hit_weight: options?.fusionParams?.hitWeight,
726
1055
  filter: options?.filter
727
1056
  }
728
1057
  );
@@ -761,6 +1090,81 @@ var RestBackend = class {
761
1090
  throw new VelesDBError(response.error.message, response.error.code);
762
1091
  }
763
1092
  }
1093
+ // ========================================================================
1094
+ // Sparse / PQ / Streaming (v1.5)
1095
+ // ========================================================================
1096
+ async trainPq(collection, options) {
1097
+ this.ensureInitialized();
1098
+ const m = options?.m ?? 8;
1099
+ const k = options?.k ?? 256;
1100
+ const withClause = options?.opq ? `WITH (m=${m}, k=${k}, opq=true)` : `WITH (m=${m}, k=${k})`;
1101
+ const queryString = `TRAIN QUANTIZER ON ${collection} ${withClause}`;
1102
+ const response = await this.request(
1103
+ "POST",
1104
+ "/query",
1105
+ { query: queryString }
1106
+ );
1107
+ if (response.error) {
1108
+ throw new VelesDBError(response.error.message, response.error.code);
1109
+ }
1110
+ return response.data?.message ?? "PQ training initiated";
1111
+ }
1112
+ async streamInsert(collection, docs) {
1113
+ this.ensureInitialized();
1114
+ for (const doc of docs) {
1115
+ const restId = this.parseRestPointId(doc.id);
1116
+ const vector = doc.vector instanceof Float32Array ? Array.from(doc.vector) : doc.vector;
1117
+ const body = {
1118
+ id: restId,
1119
+ vector,
1120
+ payload: doc.payload
1121
+ };
1122
+ if (doc.sparseVector) {
1123
+ body.sparse_vector = this.sparseVectorToRestFormat(doc.sparseVector);
1124
+ }
1125
+ const url = `${this.baseUrl}/collections/${encodeURIComponent(collection)}/stream/insert`;
1126
+ const headers = {
1127
+ "Content-Type": "application/json"
1128
+ };
1129
+ if (this.apiKey) {
1130
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
1131
+ }
1132
+ const controller = new AbortController();
1133
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1134
+ try {
1135
+ const response = await fetch(url, {
1136
+ method: "POST",
1137
+ headers,
1138
+ body: JSON.stringify(body),
1139
+ signal: controller.signal
1140
+ });
1141
+ clearTimeout(timeoutId);
1142
+ if (response.status === 429) {
1143
+ throw new BackpressureError();
1144
+ }
1145
+ if (!response.ok && response.status !== 202) {
1146
+ const data = await response.json().catch(() => ({}));
1147
+ const errorPayload = this.extractErrorPayload(data);
1148
+ throw new VelesDBError(
1149
+ errorPayload.message ?? `HTTP ${response.status}`,
1150
+ errorPayload.code ?? this.mapStatusToErrorCode(response.status)
1151
+ );
1152
+ }
1153
+ } catch (error) {
1154
+ clearTimeout(timeoutId);
1155
+ if (error instanceof BackpressureError || error instanceof VelesDBError) {
1156
+ throw error;
1157
+ }
1158
+ if (error instanceof Error && error.name === "AbortError") {
1159
+ throw new ConnectionError("Request timeout");
1160
+ }
1161
+ throw new ConnectionError(
1162
+ `Stream insert failed: ${error instanceof Error ? error.message : "Unknown error"}`,
1163
+ error instanceof Error ? error : void 0
1164
+ );
1165
+ }
1166
+ }
1167
+ }
764
1168
  async close() {
765
1169
  this._initialized = false;
766
1170
  }
@@ -1077,6 +1481,18 @@ var VelesDB = class {
1077
1481
  if (!Array.isArray(doc.vector) && !(doc.vector instanceof Float32Array)) {
1078
1482
  throw new ValidationError("Vector must be an array or Float32Array");
1079
1483
  }
1484
+ if (this.config.backend === "rest" && (typeof doc.id !== "number" || !Number.isInteger(doc.id) || doc.id < 0 || doc.id > Number.MAX_SAFE_INTEGER)) {
1485
+ throw new ValidationError(
1486
+ `REST backend requires numeric u64-compatible document IDs in JS safe integer range (0..${Number.MAX_SAFE_INTEGER})`
1487
+ );
1488
+ }
1489
+ }
1490
+ validateRestPointId(id) {
1491
+ if (this.config.backend === "rest" && (typeof id !== "number" || !Number.isInteger(id) || id < 0 || id > Number.MAX_SAFE_INTEGER)) {
1492
+ throw new ValidationError(
1493
+ `REST backend requires numeric u64-compatible document IDs in JS safe integer range (0..${Number.MAX_SAFE_INTEGER})`
1494
+ );
1495
+ }
1080
1496
  }
1081
1497
  /**
1082
1498
  * Search for similar vectors
@@ -1121,6 +1537,7 @@ var VelesDB = class {
1121
1537
  */
1122
1538
  async delete(collection, id) {
1123
1539
  this.ensureInitialized();
1540
+ this.validateRestPointId(id);
1124
1541
  return this.backend.delete(collection, id);
1125
1542
  }
1126
1543
  /**
@@ -1132,6 +1549,7 @@ var VelesDB = class {
1132
1549
  */
1133
1550
  async get(collection, id) {
1134
1551
  this.ensureInitialized();
1552
+ this.validateRestPointId(id);
1135
1553
  return this.backend.get(collection, id);
1136
1554
  }
1137
1555
  /**
@@ -1227,6 +1645,20 @@ var VelesDB = class {
1227
1645
  * });
1228
1646
  * ```
1229
1647
  */
1648
+ async queryExplain(queryString, params) {
1649
+ this.ensureInitialized();
1650
+ if (!queryString || typeof queryString !== "string") {
1651
+ throw new ValidationError("Query string must be a non-empty string");
1652
+ }
1653
+ return this.backend.queryExplain(queryString, params);
1654
+ }
1655
+ async collectionSanity(collection) {
1656
+ this.ensureInitialized();
1657
+ if (!collection || typeof collection !== "string") {
1658
+ throw new ValidationError("Collection name must be a non-empty string");
1659
+ }
1660
+ return this.backend.collectionSanity(collection);
1661
+ }
1230
1662
  async multiQuerySearch(collection, vectors, options) {
1231
1663
  this.ensureInitialized();
1232
1664
  if (!Array.isArray(vectors) || vectors.length === 0) {
@@ -1239,9 +1671,39 @@ var VelesDB = class {
1239
1671
  }
1240
1672
  return this.backend.multiQuerySearch(collection, vectors, options);
1241
1673
  }
1674
+ /**
1675
+ * Train Product Quantization on a collection
1676
+ *
1677
+ * @param collection - Collection name
1678
+ * @param options - PQ training options (m, k, opq)
1679
+ * @returns Server response message
1680
+ */
1681
+ async trainPq(collection, options) {
1682
+ this.ensureInitialized();
1683
+ return this.backend.trainPq(collection, options);
1684
+ }
1685
+ /**
1686
+ * Stream-insert documents with backpressure support
1687
+ *
1688
+ * Sends documents sequentially to respect server backpressure.
1689
+ * Throws BackpressureError on 429 responses.
1690
+ *
1691
+ * @param collection - Collection name
1692
+ * @param docs - Documents to insert
1693
+ */
1694
+ async streamInsert(collection, docs) {
1695
+ this.ensureInitialized();
1696
+ if (!Array.isArray(docs)) {
1697
+ throw new ValidationError("Documents must be an array");
1698
+ }
1699
+ for (const doc of docs) {
1700
+ this.validateDocument(doc);
1701
+ }
1702
+ await this.backend.streamInsert(collection, docs);
1703
+ }
1242
1704
  /**
1243
1705
  * Check if a collection is empty
1244
- *
1706
+ *
1245
1707
  * @param collection - Collection name
1246
1708
  * @returns true if empty, false otherwise
1247
1709
  */
@@ -1739,6 +2201,7 @@ function velesql() {
1739
2201
  return new VelesQLBuilder();
1740
2202
  }
1741
2203
  export {
2204
+ BackpressureError,
1742
2205
  ConnectionError,
1743
2206
  NotFoundError,
1744
2207
  RestBackend,