@wiscale/velesdb-sdk 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,690 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ConnectionError: () => ConnectionError,
34
+ NotFoundError: () => NotFoundError,
35
+ RestBackend: () => RestBackend,
36
+ ValidationError: () => ValidationError,
37
+ VelesDB: () => VelesDB,
38
+ VelesDBError: () => VelesDBError,
39
+ WasmBackend: () => WasmBackend
40
+ });
41
+ module.exports = __toCommonJS(index_exports);
42
+
43
+ // src/types.ts
44
+ var VelesDBError = class extends Error {
45
+ constructor(message, code, cause) {
46
+ super(message);
47
+ this.code = code;
48
+ this.cause = cause;
49
+ this.name = "VelesDBError";
50
+ }
51
+ };
52
+ var ConnectionError = class extends VelesDBError {
53
+ constructor(message, cause) {
54
+ super(message, "CONNECTION_ERROR", cause);
55
+ this.name = "ConnectionError";
56
+ }
57
+ };
58
+ var ValidationError = class extends VelesDBError {
59
+ constructor(message) {
60
+ super(message, "VALIDATION_ERROR");
61
+ this.name = "ValidationError";
62
+ }
63
+ };
64
+ var NotFoundError = class extends VelesDBError {
65
+ constructor(resource) {
66
+ super(`${resource} not found`, "NOT_FOUND");
67
+ this.name = "NotFoundError";
68
+ }
69
+ };
70
+
71
+ // src/backends/wasm.ts
72
+ var WasmBackend = class {
73
+ constructor() {
74
+ this.wasmModule = null;
75
+ this.collections = /* @__PURE__ */ new Map();
76
+ this._initialized = false;
77
+ }
78
+ async init() {
79
+ if (this._initialized) {
80
+ return;
81
+ }
82
+ try {
83
+ this.wasmModule = await import("velesdb-wasm");
84
+ await this.wasmModule.default();
85
+ this._initialized = true;
86
+ } catch (error) {
87
+ throw new ConnectionError(
88
+ "Failed to initialize WASM module",
89
+ error instanceof Error ? error : void 0
90
+ );
91
+ }
92
+ }
93
+ isInitialized() {
94
+ return this._initialized;
95
+ }
96
+ ensureInitialized() {
97
+ if (!this._initialized || !this.wasmModule) {
98
+ throw new ConnectionError("WASM backend not initialized");
99
+ }
100
+ }
101
+ async createCollection(name, config) {
102
+ this.ensureInitialized();
103
+ if (this.collections.has(name)) {
104
+ throw new VelesDBError(`Collection '${name}' already exists`, "COLLECTION_EXISTS");
105
+ }
106
+ const metric = config.metric ?? "cosine";
107
+ const store = new this.wasmModule.VectorStore(config.dimension, metric);
108
+ this.collections.set(name, {
109
+ config: { ...config, metric },
110
+ store,
111
+ payloads: /* @__PURE__ */ new Map(),
112
+ createdAt: /* @__PURE__ */ new Date()
113
+ });
114
+ }
115
+ async deleteCollection(name) {
116
+ this.ensureInitialized();
117
+ const collection = this.collections.get(name);
118
+ if (!collection) {
119
+ throw new NotFoundError(`Collection '${name}'`);
120
+ }
121
+ collection.store.free();
122
+ this.collections.delete(name);
123
+ }
124
+ async getCollection(name) {
125
+ this.ensureInitialized();
126
+ const collection = this.collections.get(name);
127
+ if (!collection) {
128
+ return null;
129
+ }
130
+ return {
131
+ name,
132
+ dimension: collection.config.dimension,
133
+ metric: collection.config.metric ?? "cosine",
134
+ count: collection.store.len,
135
+ createdAt: collection.createdAt
136
+ };
137
+ }
138
+ async listCollections() {
139
+ this.ensureInitialized();
140
+ const result = [];
141
+ for (const [name, data] of this.collections) {
142
+ result.push({
143
+ name,
144
+ dimension: data.config.dimension,
145
+ metric: data.config.metric ?? "cosine",
146
+ count: data.store.len,
147
+ createdAt: data.createdAt
148
+ });
149
+ }
150
+ return result;
151
+ }
152
+ async insert(collectionName, doc) {
153
+ this.ensureInitialized();
154
+ const collection = this.collections.get(collectionName);
155
+ if (!collection) {
156
+ throw new NotFoundError(`Collection '${collectionName}'`);
157
+ }
158
+ const id = this.toNumericId(doc.id);
159
+ const vector = doc.vector instanceof Float32Array ? doc.vector : new Float32Array(doc.vector);
160
+ if (vector.length !== collection.config.dimension) {
161
+ throw new VelesDBError(
162
+ `Vector dimension mismatch: expected ${collection.config.dimension}, got ${vector.length}`,
163
+ "DIMENSION_MISMATCH"
164
+ );
165
+ }
166
+ collection.store.insert(BigInt(id), vector);
167
+ if (doc.payload) {
168
+ collection.payloads.set(String(doc.id), doc.payload);
169
+ }
170
+ }
171
+ async insertBatch(collectionName, docs) {
172
+ this.ensureInitialized();
173
+ const collection = this.collections.get(collectionName);
174
+ if (!collection) {
175
+ throw new NotFoundError(`Collection '${collectionName}'`);
176
+ }
177
+ for (const doc of docs) {
178
+ const vectorLen = Array.isArray(doc.vector) ? doc.vector.length : doc.vector.length;
179
+ if (vectorLen !== collection.config.dimension) {
180
+ throw new VelesDBError(
181
+ `Vector dimension mismatch for doc ${doc.id}: expected ${collection.config.dimension}, got ${vectorLen}`,
182
+ "DIMENSION_MISMATCH"
183
+ );
184
+ }
185
+ }
186
+ collection.store.reserve(docs.length);
187
+ const batch = docs.map((doc) => [
188
+ BigInt(this.toNumericId(doc.id)),
189
+ Array.isArray(doc.vector) ? doc.vector : Array.from(doc.vector)
190
+ ]);
191
+ collection.store.insert_batch(batch);
192
+ for (const doc of docs) {
193
+ if (doc.payload) {
194
+ collection.payloads.set(String(doc.id), doc.payload);
195
+ }
196
+ }
197
+ }
198
+ async search(collectionName, query, options) {
199
+ this.ensureInitialized();
200
+ const collection = this.collections.get(collectionName);
201
+ if (!collection) {
202
+ throw new NotFoundError(`Collection '${collectionName}'`);
203
+ }
204
+ const queryVector = query instanceof Float32Array ? query : new Float32Array(query);
205
+ if (queryVector.length !== collection.config.dimension) {
206
+ throw new VelesDBError(
207
+ `Query dimension mismatch: expected ${collection.config.dimension}, got ${queryVector.length}`,
208
+ "DIMENSION_MISMATCH"
209
+ );
210
+ }
211
+ const k = options?.k ?? 10;
212
+ const rawResults = collection.store.search(queryVector, k);
213
+ return rawResults.map(([id, score]) => {
214
+ const stringId = String(id);
215
+ const result = {
216
+ id: stringId,
217
+ score
218
+ };
219
+ const payload = collection.payloads.get(stringId);
220
+ if (payload) {
221
+ result.payload = payload;
222
+ }
223
+ return result;
224
+ });
225
+ }
226
+ async delete(collectionName, id) {
227
+ this.ensureInitialized();
228
+ const collection = this.collections.get(collectionName);
229
+ if (!collection) {
230
+ throw new NotFoundError(`Collection '${collectionName}'`);
231
+ }
232
+ const numericId = this.toNumericId(id);
233
+ const removed = collection.store.remove(BigInt(numericId));
234
+ if (removed) {
235
+ collection.payloads.delete(String(id));
236
+ }
237
+ return removed;
238
+ }
239
+ async get(collectionName, id) {
240
+ this.ensureInitialized();
241
+ const collection = this.collections.get(collectionName);
242
+ if (!collection) {
243
+ throw new NotFoundError(`Collection '${collectionName}'`);
244
+ }
245
+ const payload = collection.payloads.get(String(id));
246
+ if (!payload) {
247
+ return null;
248
+ }
249
+ return {
250
+ id,
251
+ vector: [],
252
+ // Not available in current WASM impl
253
+ payload
254
+ };
255
+ }
256
+ async close() {
257
+ for (const [, data] of this.collections) {
258
+ data.store.free();
259
+ }
260
+ this.collections.clear();
261
+ this._initialized = false;
262
+ }
263
+ toNumericId(id) {
264
+ if (typeof id === "number") {
265
+ return id;
266
+ }
267
+ const parsed = parseInt(id, 10);
268
+ if (!isNaN(parsed)) {
269
+ return parsed;
270
+ }
271
+ let hash = 0;
272
+ for (let i = 0; i < id.length; i++) {
273
+ const char = id.charCodeAt(i);
274
+ hash = (hash << 5) - hash + char;
275
+ hash = hash & hash;
276
+ }
277
+ return Math.abs(hash);
278
+ }
279
+ };
280
+
281
+ // src/backends/rest.ts
282
+ var RestBackend = class {
283
+ constructor(url, apiKey, timeout = 3e4) {
284
+ this._initialized = false;
285
+ this.baseUrl = url.replace(/\/$/, "");
286
+ this.apiKey = apiKey;
287
+ this.timeout = timeout;
288
+ }
289
+ async init() {
290
+ if (this._initialized) {
291
+ return;
292
+ }
293
+ try {
294
+ const response = await this.request("GET", "/health");
295
+ if (response.error) {
296
+ throw new Error(response.error.message);
297
+ }
298
+ this._initialized = true;
299
+ } catch (error) {
300
+ throw new ConnectionError(
301
+ `Failed to connect to VelesDB server at ${this.baseUrl}`,
302
+ error instanceof Error ? error : void 0
303
+ );
304
+ }
305
+ }
306
+ isInitialized() {
307
+ return this._initialized;
308
+ }
309
+ ensureInitialized() {
310
+ if (!this._initialized) {
311
+ throw new ConnectionError("REST backend not initialized");
312
+ }
313
+ }
314
+ async request(method, path, body) {
315
+ const url = `${this.baseUrl}${path}`;
316
+ const headers = {
317
+ "Content-Type": "application/json"
318
+ };
319
+ if (this.apiKey) {
320
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
321
+ }
322
+ const controller = new AbortController();
323
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
324
+ try {
325
+ const response = await fetch(url, {
326
+ method,
327
+ headers,
328
+ body: body ? JSON.stringify(body) : void 0,
329
+ signal: controller.signal
330
+ });
331
+ clearTimeout(timeoutId);
332
+ const data = await response.json();
333
+ if (!response.ok) {
334
+ return {
335
+ error: {
336
+ code: data.code ?? "UNKNOWN_ERROR",
337
+ message: data.message ?? `HTTP ${response.status}`
338
+ }
339
+ };
340
+ }
341
+ return { data };
342
+ } catch (error) {
343
+ clearTimeout(timeoutId);
344
+ if (error instanceof Error && error.name === "AbortError") {
345
+ throw new ConnectionError("Request timeout");
346
+ }
347
+ throw new ConnectionError(
348
+ `Request failed: ${error instanceof Error ? error.message : "Unknown error"}`,
349
+ error instanceof Error ? error : void 0
350
+ );
351
+ }
352
+ }
353
+ async createCollection(name, config) {
354
+ this.ensureInitialized();
355
+ const response = await this.request("POST", "/collections", {
356
+ name,
357
+ dimension: config.dimension,
358
+ metric: config.metric ?? "cosine",
359
+ description: config.description
360
+ });
361
+ if (response.error) {
362
+ throw new VelesDBError(response.error.message, response.error.code);
363
+ }
364
+ }
365
+ async deleteCollection(name) {
366
+ this.ensureInitialized();
367
+ const response = await this.request("DELETE", `/collections/${encodeURIComponent(name)}`);
368
+ if (response.error) {
369
+ if (response.error.code === "NOT_FOUND") {
370
+ throw new NotFoundError(`Collection '${name}'`);
371
+ }
372
+ throw new VelesDBError(response.error.message, response.error.code);
373
+ }
374
+ }
375
+ async getCollection(name) {
376
+ this.ensureInitialized();
377
+ const response = await this.request(
378
+ "GET",
379
+ `/collections/${encodeURIComponent(name)}`
380
+ );
381
+ if (response.error) {
382
+ if (response.error.code === "NOT_FOUND") {
383
+ return null;
384
+ }
385
+ throw new VelesDBError(response.error.message, response.error.code);
386
+ }
387
+ return response.data ?? null;
388
+ }
389
+ async listCollections() {
390
+ this.ensureInitialized();
391
+ const response = await this.request("GET", "/collections");
392
+ if (response.error) {
393
+ throw new VelesDBError(response.error.message, response.error.code);
394
+ }
395
+ return response.data ?? [];
396
+ }
397
+ async insert(collection, doc) {
398
+ this.ensureInitialized();
399
+ const vector = doc.vector instanceof Float32Array ? Array.from(doc.vector) : doc.vector;
400
+ const response = await this.request(
401
+ "POST",
402
+ `/collections/${encodeURIComponent(collection)}/vectors`,
403
+ {
404
+ id: doc.id,
405
+ vector,
406
+ payload: doc.payload
407
+ }
408
+ );
409
+ if (response.error) {
410
+ if (response.error.code === "NOT_FOUND") {
411
+ throw new NotFoundError(`Collection '${collection}'`);
412
+ }
413
+ throw new VelesDBError(response.error.message, response.error.code);
414
+ }
415
+ }
416
+ async insertBatch(collection, docs) {
417
+ this.ensureInitialized();
418
+ const vectors = docs.map((doc) => ({
419
+ id: doc.id,
420
+ vector: doc.vector instanceof Float32Array ? Array.from(doc.vector) : doc.vector,
421
+ payload: doc.payload
422
+ }));
423
+ const response = await this.request(
424
+ "POST",
425
+ `/collections/${encodeURIComponent(collection)}/vectors/batch`,
426
+ { vectors }
427
+ );
428
+ if (response.error) {
429
+ if (response.error.code === "NOT_FOUND") {
430
+ throw new NotFoundError(`Collection '${collection}'`);
431
+ }
432
+ throw new VelesDBError(response.error.message, response.error.code);
433
+ }
434
+ }
435
+ async search(collection, query, options) {
436
+ this.ensureInitialized();
437
+ const queryVector = query instanceof Float32Array ? Array.from(query) : query;
438
+ const response = await this.request(
439
+ "POST",
440
+ `/collections/${encodeURIComponent(collection)}/search`,
441
+ {
442
+ vector: queryVector,
443
+ k: options?.k ?? 10,
444
+ filter: options?.filter,
445
+ include_vectors: options?.includeVectors ?? false
446
+ }
447
+ );
448
+ if (response.error) {
449
+ if (response.error.code === "NOT_FOUND") {
450
+ throw new NotFoundError(`Collection '${collection}'`);
451
+ }
452
+ throw new VelesDBError(response.error.message, response.error.code);
453
+ }
454
+ return response.data ?? [];
455
+ }
456
+ async delete(collection, id) {
457
+ this.ensureInitialized();
458
+ const response = await this.request(
459
+ "DELETE",
460
+ `/collections/${encodeURIComponent(collection)}/vectors/${encodeURIComponent(String(id))}`
461
+ );
462
+ if (response.error) {
463
+ if (response.error.code === "NOT_FOUND") {
464
+ return false;
465
+ }
466
+ throw new VelesDBError(response.error.message, response.error.code);
467
+ }
468
+ return response.data?.deleted ?? false;
469
+ }
470
+ async get(collection, id) {
471
+ this.ensureInitialized();
472
+ const response = await this.request(
473
+ "GET",
474
+ `/collections/${encodeURIComponent(collection)}/vectors/${encodeURIComponent(String(id))}`
475
+ );
476
+ if (response.error) {
477
+ if (response.error.code === "NOT_FOUND") {
478
+ return null;
479
+ }
480
+ throw new VelesDBError(response.error.message, response.error.code);
481
+ }
482
+ return response.data ?? null;
483
+ }
484
+ async close() {
485
+ this._initialized = false;
486
+ }
487
+ };
488
+
489
+ // src/client.ts
490
+ var VelesDB = class {
491
+ /**
492
+ * Create a new VelesDB client
493
+ *
494
+ * @param config - Client configuration
495
+ * @throws {ValidationError} If configuration is invalid
496
+ */
497
+ constructor(config) {
498
+ this.initialized = false;
499
+ this.validateConfig(config);
500
+ this.config = config;
501
+ this.backend = this.createBackend(config);
502
+ }
503
+ validateConfig(config) {
504
+ if (!config.backend) {
505
+ throw new ValidationError("Backend type is required");
506
+ }
507
+ if (config.backend !== "wasm" && config.backend !== "rest") {
508
+ throw new ValidationError(`Invalid backend type: ${config.backend}. Use 'wasm' or 'rest'`);
509
+ }
510
+ if (config.backend === "rest" && !config.url) {
511
+ throw new ValidationError("URL is required for REST backend");
512
+ }
513
+ }
514
+ createBackend(config) {
515
+ switch (config.backend) {
516
+ case "wasm":
517
+ return new WasmBackend();
518
+ case "rest":
519
+ return new RestBackend(config.url, config.apiKey, config.timeout);
520
+ default:
521
+ throw new ValidationError(`Unknown backend: ${config.backend}`);
522
+ }
523
+ }
524
+ /**
525
+ * Initialize the client
526
+ * Must be called before any other operations
527
+ */
528
+ async init() {
529
+ if (this.initialized) {
530
+ return;
531
+ }
532
+ await this.backend.init();
533
+ this.initialized = true;
534
+ }
535
+ /**
536
+ * Check if client is initialized
537
+ */
538
+ isInitialized() {
539
+ return this.initialized;
540
+ }
541
+ ensureInitialized() {
542
+ if (!this.initialized) {
543
+ throw new ValidationError("Client not initialized. Call init() first.");
544
+ }
545
+ }
546
+ /**
547
+ * Create a new collection
548
+ *
549
+ * @param name - Collection name
550
+ * @param config - Collection configuration
551
+ */
552
+ async createCollection(name, config) {
553
+ this.ensureInitialized();
554
+ if (!name || typeof name !== "string") {
555
+ throw new ValidationError("Collection name must be a non-empty string");
556
+ }
557
+ if (!config.dimension || config.dimension <= 0) {
558
+ throw new ValidationError("Dimension must be a positive integer");
559
+ }
560
+ await this.backend.createCollection(name, config);
561
+ }
562
+ /**
563
+ * Delete a collection
564
+ *
565
+ * @param name - Collection name
566
+ */
567
+ async deleteCollection(name) {
568
+ this.ensureInitialized();
569
+ await this.backend.deleteCollection(name);
570
+ }
571
+ /**
572
+ * Get collection information
573
+ *
574
+ * @param name - Collection name
575
+ * @returns Collection info or null if not found
576
+ */
577
+ async getCollection(name) {
578
+ this.ensureInitialized();
579
+ return this.backend.getCollection(name);
580
+ }
581
+ /**
582
+ * List all collections
583
+ *
584
+ * @returns Array of collections
585
+ */
586
+ async listCollections() {
587
+ this.ensureInitialized();
588
+ return this.backend.listCollections();
589
+ }
590
+ /**
591
+ * Insert a vector document
592
+ *
593
+ * @param collection - Collection name
594
+ * @param doc - Document to insert
595
+ */
596
+ async insert(collection, doc) {
597
+ this.ensureInitialized();
598
+ this.validateDocument(doc);
599
+ await this.backend.insert(collection, doc);
600
+ }
601
+ /**
602
+ * Insert multiple vector documents
603
+ *
604
+ * @param collection - Collection name
605
+ * @param docs - Documents to insert
606
+ */
607
+ async insertBatch(collection, docs) {
608
+ this.ensureInitialized();
609
+ if (!Array.isArray(docs)) {
610
+ throw new ValidationError("Documents must be an array");
611
+ }
612
+ for (const doc of docs) {
613
+ this.validateDocument(doc);
614
+ }
615
+ await this.backend.insertBatch(collection, docs);
616
+ }
617
+ validateDocument(doc) {
618
+ if (doc.id === void 0 || doc.id === null) {
619
+ throw new ValidationError("Document ID is required");
620
+ }
621
+ if (!doc.vector) {
622
+ throw new ValidationError("Document vector is required");
623
+ }
624
+ if (!Array.isArray(doc.vector) && !(doc.vector instanceof Float32Array)) {
625
+ throw new ValidationError("Vector must be an array or Float32Array");
626
+ }
627
+ }
628
+ /**
629
+ * Search for similar vectors
630
+ *
631
+ * @param collection - Collection name
632
+ * @param query - Query vector
633
+ * @param options - Search options
634
+ * @returns Search results sorted by relevance
635
+ */
636
+ async search(collection, query, options) {
637
+ this.ensureInitialized();
638
+ if (!query || !Array.isArray(query) && !(query instanceof Float32Array)) {
639
+ throw new ValidationError("Query must be an array or Float32Array");
640
+ }
641
+ return this.backend.search(collection, query, options);
642
+ }
643
+ /**
644
+ * Delete a vector by ID
645
+ *
646
+ * @param collection - Collection name
647
+ * @param id - Document ID
648
+ * @returns true if deleted, false if not found
649
+ */
650
+ async delete(collection, id) {
651
+ this.ensureInitialized();
652
+ return this.backend.delete(collection, id);
653
+ }
654
+ /**
655
+ * Get a vector by ID
656
+ *
657
+ * @param collection - Collection name
658
+ * @param id - Document ID
659
+ * @returns Document or null if not found
660
+ */
661
+ async get(collection, id) {
662
+ this.ensureInitialized();
663
+ return this.backend.get(collection, id);
664
+ }
665
+ /**
666
+ * Close the client and release resources
667
+ */
668
+ async close() {
669
+ if (this.initialized) {
670
+ await this.backend.close();
671
+ this.initialized = false;
672
+ }
673
+ }
674
+ /**
675
+ * Get the current backend type
676
+ */
677
+ get backendType() {
678
+ return this.config.backend;
679
+ }
680
+ };
681
+ // Annotate the CommonJS export names for ESM import in node:
682
+ 0 && (module.exports = {
683
+ ConnectionError,
684
+ NotFoundError,
685
+ RestBackend,
686
+ ValidationError,
687
+ VelesDB,
688
+ VelesDBError,
689
+ WasmBackend
690
+ });