@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/README.md +12 -2
- package/dist/index.d.mts +132 -5
- package/dist/index.d.ts +132 -5
- package/dist/index.js +511 -47
- package/dist/index.mjs +510 -47
- package/package.json +2 -2
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.
|
|
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 =
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
220
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
"
|
|
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
|
|
425
|
+
async collectionSanity(_collection) {
|
|
426
|
+
this.ensureInitialized();
|
|
249
427
|
throw new VelesDBError(
|
|
250
|
-
"
|
|
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
|
|
281
|
-
if (
|
|
282
|
-
|
|
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
|
|
409
|
-
const
|
|
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:
|
|
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(
|
|
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(
|
|
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,
|
|
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
|
-
|
|
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,
|