@anvailabs/proximadb-client 0.3.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.
Files changed (65) hide show
  1. package/README.md +160 -0
  2. package/dist/client.d.ts +319 -0
  3. package/dist/client.d.ts.map +1 -0
  4. package/dist/client.js +681 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/collection.d.ts +227 -0
  7. package/dist/collection.d.ts.map +1 -0
  8. package/dist/collection.js +436 -0
  9. package/dist/collection.js.map +1 -0
  10. package/dist/esm/client.js +674 -0
  11. package/dist/esm/client.js.map +1 -0
  12. package/dist/esm/collection.js +428 -0
  13. package/dist/esm/collection.js.map +1 -0
  14. package/dist/esm/filter.js +228 -0
  15. package/dist/esm/filter.js.map +1 -0
  16. package/dist/esm/generated/schema.js +6 -0
  17. package/dist/esm/generated/schema.js.map +1 -0
  18. package/dist/esm/graph.js +678 -0
  19. package/dist/esm/graph.js.map +1 -0
  20. package/dist/esm/index.js +86 -0
  21. package/dist/esm/index.js.map +1 -0
  22. package/dist/esm/search.js +317 -0
  23. package/dist/esm/search.js.map +1 -0
  24. package/dist/esm/transport.js +33 -0
  25. package/dist/esm/transport.js.map +1 -0
  26. package/dist/esm/types.js +181 -0
  27. package/dist/esm/types.js.map +1 -0
  28. package/dist/filter.d.ts +51 -0
  29. package/dist/filter.d.ts.map +1 -0
  30. package/dist/filter.js +241 -0
  31. package/dist/filter.js.map +1 -0
  32. package/dist/generated/schema.d.ts +4271 -0
  33. package/dist/generated/schema.d.ts.map +1 -0
  34. package/dist/generated/schema.js +7 -0
  35. package/dist/generated/schema.js.map +1 -0
  36. package/dist/graph.d.ts +350 -0
  37. package/dist/graph.d.ts.map +1 -0
  38. package/dist/graph.js +690 -0
  39. package/dist/graph.js.map +1 -0
  40. package/dist/index.d.ts +58 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +126 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/search.d.ts +160 -0
  45. package/dist/search.d.ts.map +1 -0
  46. package/dist/search.js +322 -0
  47. package/dist/search.js.map +1 -0
  48. package/dist/transport.d.ts +40 -0
  49. package/dist/transport.d.ts.map +1 -0
  50. package/dist/transport.js +39 -0
  51. package/dist/transport.js.map +1 -0
  52. package/dist/types.d.ts +632 -0
  53. package/dist/types.d.ts.map +1 -0
  54. package/dist/types.js +185 -0
  55. package/dist/types.js.map +1 -0
  56. package/package.json +72 -0
  57. package/src/client.ts +856 -0
  58. package/src/collection.ts +578 -0
  59. package/src/filter.ts +280 -0
  60. package/src/generated/schema.ts +4271 -0
  61. package/src/graph.ts +811 -0
  62. package/src/index.ts +184 -0
  63. package/src/search.ts +401 -0
  64. package/src/transport.ts +52 -0
  65. package/src/types.ts +726 -0
@@ -0,0 +1,578 @@
1
+ /**
2
+ * ProximaDB TypeScript SDK - Collection Management
3
+ *
4
+ * Provides a fluent API for collection CRUD operations and search.
5
+ *
6
+ * Copyright 2025 ProximaDB Contributors
7
+ * Licensed under the Apache License, Version 2.0
8
+ */
9
+
10
+ import type {
11
+ CollectionConfig,
12
+ CollectionInfo,
13
+ ProximaRecord,
14
+ VectorRecord,
15
+ SearchResult,
16
+ JsonValue,
17
+ } from "./types";
18
+ import {
19
+ DistanceMetric,
20
+ StorageEngine,
21
+ IndexType,
22
+ } from "./types";
23
+ import { SearchBuilder, SearchHttpClient } from "./search";
24
+
25
+ type RecordBatchResponse = {
26
+ inserted_count?: number;
27
+ success_count?: number;
28
+ success?: number;
29
+ };
30
+
31
+ function recordPayload(record: {
32
+ id: string;
33
+ vector?: number[];
34
+ metadata?: Record<string, JsonValue>;
35
+ props?: Record<string, JsonValue>;
36
+ source?: string;
37
+ }): ProximaRecord {
38
+ return {
39
+ id: record.id,
40
+ vector: record.vector ?? [],
41
+ props: record.props ?? record.metadata ?? {},
42
+ source: record.source,
43
+ };
44
+ }
45
+
46
+ function insertedCount(response: RecordBatchResponse): number {
47
+ return response.success_count ?? response.inserted_count ?? response.success ?? 0;
48
+ }
49
+
50
+ /**
51
+ * HTTP client interface for collection operations
52
+ */
53
+ export interface CollectionHttpClient extends SearchHttpClient {
54
+ get<T>(url: string): Promise<T>;
55
+ delete<T>(url: string): Promise<T>;
56
+ /**
57
+ * Typed transport for core collection / record ops (TD-126 Phase 4). These
58
+ * route through the generated REST client (src/generated/schema.ts) instead
59
+ * of hand-built URL strings.
60
+ */
61
+ createCollectionRequest(body: Record<string, unknown>): Promise<unknown>;
62
+ getCollectionRequest(collectionId: string): Promise<unknown>;
63
+ insertRecordsRequest(
64
+ collectionId: string,
65
+ body: Record<string, unknown>,
66
+ ): Promise<unknown>;
67
+ getRecordRequest(
68
+ collectionId: string,
69
+ recordId: string,
70
+ includeVector: boolean,
71
+ includeText: boolean,
72
+ ): Promise<unknown>;
73
+ deleteRecordRequest(collectionId: string, recordId: string): Promise<void>;
74
+ }
75
+
76
+ // ============================================================================
77
+ // COLLECTION BUILDER
78
+ // ============================================================================
79
+
80
+ /**
81
+ * Builder for creating collections
82
+ */
83
+ export class CollectionBuilder {
84
+ private client: CollectionHttpClient;
85
+ private collectionName: string;
86
+ private dimensionValue: number | null = null;
87
+ private engineValue: StorageEngine = StorageEngine.Sst;
88
+ private indexValue: IndexType = IndexType.Hnsw;
89
+ private metricValue: DistanceMetric = DistanceMetric.Cosine;
90
+ private descriptionValue: string | null = null;
91
+ private tagsValue: string[] = [];
92
+
93
+ constructor(client: CollectionHttpClient, name: string) {
94
+ this.client = client;
95
+ this.collectionName = name;
96
+ }
97
+
98
+ /**
99
+ * Set the vector dimension (required)
100
+ */
101
+ dimension(dim: number): CollectionBuilder {
102
+ this.dimensionValue = dim;
103
+ return this;
104
+ }
105
+
106
+ /**
107
+ * Set the storage engine
108
+ */
109
+ engine(eng: StorageEngine): CollectionBuilder {
110
+ this.engineValue = eng;
111
+ return this;
112
+ }
113
+
114
+ /**
115
+ * Set the storage engine from string
116
+ */
117
+ engineStr(eng: string): CollectionBuilder {
118
+ this.engineValue = eng as StorageEngine;
119
+ return this;
120
+ }
121
+
122
+ /**
123
+ * Set the index type
124
+ */
125
+ index(idx: IndexType): CollectionBuilder {
126
+ this.indexValue = idx;
127
+ return this;
128
+ }
129
+
130
+ /**
131
+ * Set the distance metric
132
+ */
133
+ metric(met: DistanceMetric): CollectionBuilder {
134
+ this.metricValue = met;
135
+ return this;
136
+ }
137
+
138
+ /**
139
+ * Set the collection description
140
+ */
141
+ description(desc: string): CollectionBuilder {
142
+ this.descriptionValue = desc;
143
+ return this;
144
+ }
145
+
146
+ /**
147
+ * Add tags to the collection
148
+ */
149
+ tags(tagList: string[]): CollectionBuilder {
150
+ this.tagsValue = tagList;
151
+ return this;
152
+ }
153
+
154
+ /**
155
+ * Execute the collection creation
156
+ */
157
+ async execute(): Promise<void> {
158
+ if (this.dimensionValue === null) {
159
+ throw new Error("Dimension is required for collection creation");
160
+ }
161
+
162
+ const request = {
163
+ name: this.collectionName,
164
+ dimension: this.dimensionValue,
165
+ engine: this.engineValue,
166
+ index_type: this.indexValue,
167
+ distance_metric: this.metricValue,
168
+ description: this.descriptionValue ?? undefined,
169
+ tags: this.tagsValue.length > 0 ? this.tagsValue : undefined,
170
+ };
171
+
172
+ await this.client.createCollectionRequest(request as Record<string, unknown>);
173
+ }
174
+
175
+ /**
176
+ * Build the configuration without executing
177
+ */
178
+ build(): CollectionConfig {
179
+ if (this.dimensionValue === null) {
180
+ throw new Error("Dimension is required");
181
+ }
182
+
183
+ return {
184
+ name: this.collectionName,
185
+ dimension: this.dimensionValue,
186
+ distanceMetric: this.metricValue,
187
+ storageEngine: this.engineValue,
188
+ indexType: this.indexValue,
189
+ description: this.descriptionValue ?? undefined,
190
+ tags: this.tagsValue.length > 0 ? this.tagsValue : undefined,
191
+ };
192
+ }
193
+ }
194
+
195
+ // ============================================================================
196
+ // INSERT BUILDER
197
+ // ============================================================================
198
+
199
+ /**
200
+ * Builder for insert operations
201
+ */
202
+ export class InsertBuilder {
203
+ private client: CollectionHttpClient;
204
+ private collectionName: string;
205
+ private currentId: string | null = null;
206
+ private currentVector: number[] | null = null;
207
+ private currentMetadata: Record<string, JsonValue> = {};
208
+
209
+ constructor(client: CollectionHttpClient, collection: string) {
210
+ this.client = client;
211
+ this.collectionName = collection;
212
+ }
213
+
214
+ /**
215
+ * Set the vector ID
216
+ */
217
+ id(vectorId: string): InsertBuilder {
218
+ this.currentId = vectorId;
219
+ return this;
220
+ }
221
+
222
+ /**
223
+ * Set the vector data
224
+ */
225
+ vector(vec: number[]): InsertBuilder {
226
+ this.currentVector = vec;
227
+ return this;
228
+ }
229
+
230
+ /**
231
+ * Set metadata from object
232
+ */
233
+ metadata(meta: Record<string, JsonValue>): InsertBuilder {
234
+ Object.assign(this.currentMetadata, meta);
235
+ return this;
236
+ }
237
+
238
+ /**
239
+ * Set a single metadata field
240
+ */
241
+ meta(key: string, value: JsonValue): InsertBuilder {
242
+ this.currentMetadata[key] = value;
243
+ return this;
244
+ }
245
+
246
+ /**
247
+ * Execute single vector insert
248
+ */
249
+ async execute(): Promise<void> {
250
+ if (this.currentId === null) {
251
+ throw new Error("Vector ID is required");
252
+ }
253
+ if (this.currentVector === null) {
254
+ throw new Error("Vector data is required");
255
+ }
256
+
257
+ const record = {
258
+ id: this.currentId,
259
+ vector: this.currentVector,
260
+ props: this.currentMetadata,
261
+ };
262
+
263
+ const request = {
264
+ records: [record],
265
+ validate_schema: true,
266
+ };
267
+
268
+ await this.client.insertRecordsRequest(
269
+ this.collectionName,
270
+ request as unknown as Record<string, unknown>,
271
+ );
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Builder for batch insert operations
277
+ */
278
+ export class BatchInsertBuilder {
279
+ private client: CollectionHttpClient;
280
+ private collectionName: string;
281
+ private records: Array<{
282
+ id: string;
283
+ vector: number[];
284
+ metadata: Record<string, JsonValue>;
285
+ }> = [];
286
+
287
+ constructor(
288
+ client: CollectionHttpClient,
289
+ collection: string,
290
+ ids: string[],
291
+ vectors: number[][]
292
+ ) {
293
+ this.client = client;
294
+ this.collectionName = collection;
295
+
296
+ if (ids.length !== vectors.length) {
297
+ throw new Error("IDs and vectors arrays must have the same length");
298
+ }
299
+
300
+ for (let i = 0; i < ids.length; i++) {
301
+ this.records.push({
302
+ id: ids[i]!,
303
+ vector: vectors[i]!,
304
+ metadata: {},
305
+ });
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Add metadata for all vectors
311
+ */
312
+ withMetadata(metadataList: Array<Record<string, JsonValue>>): BatchInsertBuilder {
313
+ if (metadataList.length !== this.records.length) {
314
+ throw new Error("Metadata array must match the number of vectors");
315
+ }
316
+
317
+ for (let i = 0; i < metadataList.length; i++) {
318
+ this.records[i]!.metadata = metadataList[i]!;
319
+ }
320
+
321
+ return this;
322
+ }
323
+
324
+ /**
325
+ * Execute the batch insert
326
+ */
327
+ async execute(): Promise<number> {
328
+ const request = {
329
+ records: this.records.map(recordPayload),
330
+ validate_schema: true,
331
+ };
332
+
333
+ const response = (await this.client.insertRecordsRequest(
334
+ this.collectionName,
335
+ request as unknown as Record<string, unknown>,
336
+ )) as RecordBatchResponse;
337
+ return insertedCount(response);
338
+ }
339
+ }
340
+
341
+ // ============================================================================
342
+ // UPDATE BUILDER
343
+ // ============================================================================
344
+
345
+ /**
346
+ * Builder for update operations
347
+ */
348
+ export class UpdateBuilder {
349
+ private client: CollectionHttpClient;
350
+ private collectionName: string;
351
+ private vectorId: string;
352
+ private newVector: number[] | null = null;
353
+ private newMetadata: Record<string, JsonValue> = {};
354
+ private replaceMetadataFlag: boolean = false;
355
+
356
+ constructor(client: CollectionHttpClient, collection: string, id: string) {
357
+ this.client = client;
358
+ this.collectionName = collection;
359
+ this.vectorId = id;
360
+ }
361
+
362
+ /**
363
+ * Set a new vector
364
+ */
365
+ vector(vec: number[]): UpdateBuilder {
366
+ this.newVector = vec;
367
+ return this;
368
+ }
369
+
370
+ /**
371
+ * Set metadata from object (merges with existing)
372
+ */
373
+ metadata(meta: Record<string, JsonValue>): UpdateBuilder {
374
+ Object.assign(this.newMetadata, meta);
375
+ return this;
376
+ }
377
+
378
+ /**
379
+ * Set a single metadata field
380
+ */
381
+ meta(key: string, value: JsonValue): UpdateBuilder {
382
+ this.newMetadata[key] = value;
383
+ return this;
384
+ }
385
+
386
+ /**
387
+ * Replace all metadata instead of merging
388
+ */
389
+ replaceMetadata(replace: boolean = true): UpdateBuilder {
390
+ this.replaceMetadataFlag = replace;
391
+ return this;
392
+ }
393
+
394
+ /**
395
+ * Execute the update
396
+ */
397
+ async execute(): Promise<void> {
398
+ const request = {
399
+ records: [{
400
+ id: this.vectorId,
401
+ vector: this.newVector ?? [],
402
+ props: Object.keys(this.newMetadata).length > 0 ? this.newMetadata : undefined,
403
+ }],
404
+ validate_schema: true,
405
+ upsert: true,
406
+ replace_metadata: this.replaceMetadataFlag,
407
+ };
408
+
409
+ await this.client.insertRecordsRequest(
410
+ this.collectionName,
411
+ request as unknown as Record<string, unknown>,
412
+ );
413
+ }
414
+ }
415
+
416
+ // ============================================================================
417
+ // COLLECTION HANDLE
418
+ // ============================================================================
419
+
420
+ /**
421
+ * Handle to a collection for fluent operations
422
+ */
423
+ export class CollectionHandle {
424
+ private client: CollectionHttpClient;
425
+ private collectionName: string;
426
+
427
+ constructor(client: CollectionHttpClient, name: string) {
428
+ this.client = client;
429
+ this.collectionName = name;
430
+ }
431
+
432
+ /**
433
+ * Get the collection name
434
+ */
435
+ getName(): string {
436
+ return this.collectionName;
437
+ }
438
+
439
+ /**
440
+ * Start building a search query
441
+ */
442
+ search(): SearchBuilder {
443
+ return new SearchBuilder(this.client, this.collectionName);
444
+ }
445
+
446
+ /**
447
+ * Start building an insert operation
448
+ */
449
+ insert(): InsertBuilder {
450
+ return new InsertBuilder(this.client, this.collectionName);
451
+ }
452
+
453
+ /**
454
+ * Start building a batch insert operation
455
+ */
456
+ batch(ids: string[], vectors: number[][]): BatchInsertBuilder {
457
+ return new BatchInsertBuilder(this.client, this.collectionName, ids, vectors);
458
+ }
459
+
460
+ /**
461
+ * Start building an update operation for a vector
462
+ */
463
+ update(id: string): UpdateBuilder {
464
+ return new UpdateBuilder(this.client, this.collectionName, id);
465
+ }
466
+
467
+ /**
468
+ * Get collection information
469
+ */
470
+ async info(): Promise<CollectionInfo> {
471
+ return (await this.client.getCollectionRequest(
472
+ this.collectionName,
473
+ )) as CollectionInfo;
474
+ }
475
+
476
+ /**
477
+ * Get vector count
478
+ */
479
+ async count(): Promise<number> {
480
+ const info = await this.info();
481
+ return info.vectorCount ?? 0;
482
+ }
483
+
484
+ /**
485
+ * Delete the collection
486
+ */
487
+ async delete(): Promise<void> {
488
+ const url = this.client.url() + "/api/v2/collections/" + this.collectionName;
489
+ await this.client.delete<unknown>(url);
490
+ }
491
+
492
+ /**
493
+ * Get a vector by ID
494
+ */
495
+ async getVector(id: string): Promise<VectorRecord | null> {
496
+ try {
497
+ return (await this.client.getRecordRequest(
498
+ this.collectionName,
499
+ id,
500
+ true,
501
+ false,
502
+ )) as VectorRecord;
503
+ } catch (e: unknown) {
504
+ if (e instanceof Error && e.message.includes("404")) {
505
+ return null;
506
+ }
507
+ throw e;
508
+ }
509
+ }
510
+
511
+ /**
512
+ * Check if a vector exists
513
+ */
514
+ async exists(id: string): Promise<boolean> {
515
+ const vector = await this.getVector(id);
516
+ return vector !== null;
517
+ }
518
+
519
+ /**
520
+ * Delete a vector by ID
521
+ */
522
+ async deleteVector(id: string): Promise<void> {
523
+ await this.client.deleteRecordRequest(this.collectionName, id);
524
+ }
525
+
526
+ /**
527
+ * Delete multiple vectors by IDs
528
+ */
529
+ async deleteVectors(ids: string[]): Promise<number> {
530
+ await Promise.all(ids.map((id) => this.deleteVector(id)));
531
+ return ids.length;
532
+ }
533
+
534
+ /**
535
+ * Insert a single vector (convenience method)
536
+ */
537
+ async insertVector(
538
+ id: string,
539
+ vector: number[],
540
+ metadata?: Record<string, JsonValue>
541
+ ): Promise<void> {
542
+ const builder = this.insert().id(id).vector(vector);
543
+ if (metadata) {
544
+ builder.metadata(metadata);
545
+ }
546
+ await builder.execute();
547
+ }
548
+
549
+ /**
550
+ * Insert multiple vectors (convenience method)
551
+ */
552
+ async insertVectors(
553
+ ids: string[],
554
+ vectors: number[][],
555
+ metadata?: Array<Record<string, JsonValue>>
556
+ ): Promise<number> {
557
+ const builder = this.batch(ids, vectors);
558
+ if (metadata) {
559
+ builder.withMetadata(metadata);
560
+ }
561
+ return await builder.execute();
562
+ }
563
+
564
+ /**
565
+ * Search vectors (convenience method)
566
+ */
567
+ async searchVectors(
568
+ queryVector: number[],
569
+ topK: number = 10,
570
+ filter?: string
571
+ ): Promise<SearchResult[]> {
572
+ const builder = this.search().vector(queryVector).topK(topK);
573
+ if (filter) {
574
+ builder.filter(filter);
575
+ }
576
+ return await builder.execute();
577
+ }
578
+ }