@ekodb/ekodb-client 0.2.1 → 0.4.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/src/client.ts CHANGED
@@ -2,14 +2,26 @@
2
2
  * ekoDB TypeScript Client
3
3
  */
4
4
 
5
+ import { encode, decode } from "@msgpack/msgpack";
5
6
  import { QueryBuilder, Query as QueryBuilderQuery } from "./query-builder";
6
7
  import { SearchQuery, SearchQueryBuilder, SearchResponse } from "./search";
7
8
  import { Schema, SchemaBuilder, CollectionMetadata } from "./schema";
9
+ import { Script, FunctionResult } from "./functions";
8
10
 
9
11
  export interface Record {
10
12
  [key: string]: any;
11
13
  }
12
14
 
15
+ /**
16
+ * Serialization format for client-server communication
17
+ */
18
+ export enum SerializationFormat {
19
+ /** JSON format (default, human-readable) */
20
+ Json = "Json",
21
+ /** MessagePack format (binary, 2-3x faster) */
22
+ MessagePack = "MessagePack",
23
+ }
24
+
13
25
  /**
14
26
  * Rate limit information from the server
15
27
  */
@@ -36,6 +48,8 @@ export interface ClientConfig {
36
48
  maxRetries?: number;
37
49
  /** Request timeout in milliseconds (default: 30000) */
38
50
  timeout?: number;
51
+ /** Serialization format (default: MessagePack for best performance, use Json for debugging) */
52
+ format?: SerializationFormat;
39
53
  }
40
54
 
41
55
  /**
@@ -184,6 +198,7 @@ export class EkoDBClient {
184
198
  private shouldRetry: boolean;
185
199
  private maxRetries: number;
186
200
  private timeout: number;
201
+ private format: SerializationFormat;
187
202
  private rateLimitInfo: RateLimitInfo | null = null;
188
203
 
189
204
  constructor(config: string | ClientConfig, apiKey?: string) {
@@ -194,12 +209,14 @@ export class EkoDBClient {
194
209
  this.shouldRetry = true;
195
210
  this.maxRetries = 3;
196
211
  this.timeout = 30000;
212
+ this.format = SerializationFormat.MessagePack; // Default to MessagePack for 2-3x performance
197
213
  } else {
198
214
  this.baseURL = config.baseURL;
199
215
  this.apiKey = config.apiKey;
200
216
  this.shouldRetry = config.shouldRetry ?? true;
201
217
  this.maxRetries = config.maxRetries ?? 3;
202
218
  this.timeout = config.timeout ?? 30000;
219
+ this.format = config.format ?? SerializationFormat.MessagePack; // Default to MessagePack for 2-3x performance
203
220
  }
204
221
  }
205
222
 
@@ -275,6 +292,33 @@ export class EkoDBClient {
275
292
  return new Promise((resolve) => setTimeout(resolve, seconds * 1000));
276
293
  }
277
294
 
295
+ /**
296
+ * Helper to determine if a path should use JSON
297
+ * Only CRUD operations (insert/update/delete/batch) use MessagePack
298
+ * Everything else uses JSON for compatibility
299
+ */
300
+ private shouldUseJSON(path: string): boolean {
301
+ // ONLY these operations support MessagePack
302
+ const msgpackPaths = [
303
+ "/api/insert/",
304
+ "/api/batch_insert/",
305
+ "/api/update/",
306
+ "/api/batch_update/",
307
+ "/api/delete/",
308
+ "/api/batch_delete/",
309
+ ];
310
+
311
+ // Check if path starts with any MessagePack-supported operation
312
+ for (const prefix of msgpackPaths) {
313
+ if (path.startsWith(prefix)) {
314
+ return false; // Use MessagePack
315
+ }
316
+ }
317
+
318
+ // Everything else uses JSON
319
+ return true;
320
+ }
321
+
278
322
  /**
279
323
  * Make an HTTP request to the ekoDB API with retry logic
280
324
  */
@@ -283,21 +327,36 @@ export class EkoDBClient {
283
327
  path: string,
284
328
  data?: any,
285
329
  attempt: number = 0,
330
+ forceJson: boolean = false,
286
331
  ): Promise<T> {
287
332
  if (!this.token) {
288
333
  await this.refreshToken();
289
334
  }
290
335
 
336
+ // Determine content type and serialization based on path
337
+ // Only CRUD operations support MessagePack, everything else uses JSON
338
+ const shouldForceJson = forceJson || this.shouldUseJSON(path);
339
+ const isMessagePack =
340
+ !shouldForceJson && this.format === SerializationFormat.MessagePack;
341
+ const contentType = isMessagePack
342
+ ? "application/msgpack"
343
+ : "application/json";
344
+
345
+ // Note: Modern fetch API automatically handles gzip/deflate decompression
346
+ // when server sends Content-Encoding header. No additional configuration needed.
291
347
  const options: RequestInit = {
292
348
  method,
293
349
  headers: {
294
350
  Authorization: `Bearer ${this.token}`,
295
- "Content-Type": "application/json",
351
+ "Content-Type": contentType,
352
+ Accept: contentType,
296
353
  },
297
354
  };
298
355
 
299
356
  if (data) {
300
- options.body = JSON.stringify(data);
357
+ options.body = isMessagePack
358
+ ? (encode(data) as any)
359
+ : JSON.stringify(data);
301
360
  }
302
361
 
303
362
  try {
@@ -306,7 +365,14 @@ export class EkoDBClient {
306
365
  // Extract rate limit info from successful responses
307
366
  if (response.ok) {
308
367
  this.extractRateLimitInfo(response);
309
- return response.json() as Promise<T>;
368
+
369
+ // Deserialize based on format
370
+ if (isMessagePack) {
371
+ const buffer = await response.arrayBuffer();
372
+ return decode(new Uint8Array(buffer)) as T;
373
+ } else {
374
+ return response.json() as Promise<T>;
375
+ }
310
376
  }
311
377
 
312
378
  // Handle rate limiting (429)
@@ -319,7 +385,13 @@ export class EkoDBClient {
319
385
  if (this.shouldRetry && attempt < this.maxRetries) {
320
386
  console.log(`Rate limited. Retrying after ${retryAfter} seconds...`);
321
387
  await this.sleep(retryAfter);
322
- return this.makeRequest<T>(method, path, data, attempt + 1);
388
+ return this.makeRequest<T>(
389
+ method,
390
+ path,
391
+ data,
392
+ attempt + 1,
393
+ forceJson,
394
+ );
323
395
  }
324
396
 
325
397
  throw new RateLimitError(retryAfter);
@@ -336,7 +408,7 @@ export class EkoDBClient {
336
408
  `Service unavailable. Retrying after ${retryDelay} seconds...`,
337
409
  );
338
410
  await this.sleep(retryDelay);
339
- return this.makeRequest<T>(method, path, data, attempt + 1);
411
+ return this.makeRequest<T>(method, path, data, attempt + 1, forceJson);
340
412
  }
341
413
 
342
414
  // Handle other errors
@@ -352,7 +424,7 @@ export class EkoDBClient {
352
424
  const retryDelay = 3;
353
425
  console.log(`Network error. Retrying after ${retryDelay} seconds...`);
354
426
  await this.sleep(retryDelay);
355
- return this.makeRequest<T>(method, path, data, attempt + 1);
427
+ return this.makeRequest<T>(method, path, data, attempt + 1, forceJson);
356
428
  }
357
429
 
358
430
  throw error;
@@ -440,14 +512,20 @@ export class EkoDBClient {
440
512
  /**
441
513
  * Batch insert multiple documents
442
514
  */
443
- async batchInsert(collection: string, records: Record[]): Promise<Record[]> {
444
- const inserts = records.map((data) => ({ data }));
445
- const result = await this.makeRequest<BatchOperationResult>(
515
+ async batchInsert(
516
+ collection: string,
517
+ records: Record[],
518
+ bypassRipple?: boolean,
519
+ ): Promise<BatchOperationResult> {
520
+ const inserts = records.map((data) => ({
521
+ data,
522
+ bypass_ripple: bypassRipple,
523
+ }));
524
+ return this.makeRequest<BatchOperationResult>(
446
525
  "POST",
447
526
  `/api/batch/insert/${collection}`,
448
527
  { inserts },
449
528
  );
450
- return result.successful.map((id) => ({ id }));
451
529
  }
452
530
 
453
531
  /**
@@ -455,27 +533,37 @@ export class EkoDBClient {
455
533
  */
456
534
  async batchUpdate(
457
535
  collection: string,
458
- updates: Array<{ id: string; data: Record }>,
459
- ): Promise<Record[]> {
460
- const result = await this.makeRequest<BatchOperationResult>(
536
+ updates: Array<{ id: string; data: Record; bypassRipple?: boolean }>,
537
+ ): Promise<BatchOperationResult> {
538
+ const formattedUpdates = updates.map((u) => ({
539
+ id: u.id,
540
+ data: u.data,
541
+ bypass_ripple: u.bypassRipple,
542
+ }));
543
+ return this.makeRequest<BatchOperationResult>(
461
544
  "PUT",
462
545
  `/api/batch/update/${collection}`,
463
- { updates },
546
+ { updates: formattedUpdates },
464
547
  );
465
- return result.successful.map((id) => ({ id }));
466
548
  }
467
549
 
468
550
  /**
469
551
  * Batch delete multiple documents
470
552
  */
471
- async batchDelete(collection: string, ids: string[]): Promise<number> {
472
- const deletes = ids.map((id) => ({ id }));
473
- const result = await this.makeRequest<BatchOperationResult>(
553
+ async batchDelete(
554
+ collection: string,
555
+ ids: string[],
556
+ bypassRipple?: boolean,
557
+ ): Promise<BatchOperationResult> {
558
+ const deletes = ids.map((id) => ({
559
+ id: id,
560
+ bypass_ripple: bypassRipple,
561
+ }));
562
+ return this.makeRequest<BatchOperationResult>(
474
563
  "DELETE",
475
564
  `/api/batch/delete/${collection}`,
476
565
  { deletes },
477
566
  );
478
- return result.successful.length;
479
567
  }
480
568
 
481
569
  /**
@@ -486,6 +574,8 @@ export class EkoDBClient {
486
574
  "POST",
487
575
  `/api/kv/set/${encodeURIComponent(key)}`,
488
576
  { value },
577
+ 0,
578
+ true, // Force JSON for KV operations
489
579
  );
490
580
  }
491
581
 
@@ -496,6 +586,9 @@ export class EkoDBClient {
496
586
  const result = await this.makeRequest<{ value: any }>(
497
587
  "GET",
498
588
  `/api/kv/get/${encodeURIComponent(key)}`,
589
+ undefined,
590
+ 0,
591
+ true, // Force JSON for KV operations
499
592
  );
500
593
  return result.value;
501
594
  }
@@ -507,6 +600,9 @@ export class EkoDBClient {
507
600
  await this.makeRequest<void>(
508
601
  "DELETE",
509
602
  `/api/kv/delete/${encodeURIComponent(key)}`,
603
+ undefined,
604
+ 0,
605
+ true, // Force JSON for KV operations
510
606
  );
511
607
  }
512
608
 
@@ -517,6 +613,9 @@ export class EkoDBClient {
517
613
  const result = await this.makeRequest<{ collections: string[] }>(
518
614
  "GET",
519
615
  "/api/collections",
616
+ undefined,
617
+ 0,
618
+ true, // Force JSON for metadata operations
520
619
  );
521
620
  return result.collections;
522
621
  }
@@ -525,7 +624,13 @@ export class EkoDBClient {
525
624
  * Delete a collection
526
625
  */
527
626
  async deleteCollection(collection: string): Promise<void> {
528
- await this.makeRequest<void>("DELETE", `/api/collections/${collection}`);
627
+ await this.makeRequest<void>(
628
+ "DELETE",
629
+ `/api/collections/${collection}`,
630
+ undefined,
631
+ 0,
632
+ true, // Force JSON for metadata operations
633
+ );
529
634
  }
530
635
 
531
636
  /**
@@ -553,6 +658,8 @@ export class EkoDBClient {
553
658
  "POST",
554
659
  `/api/collections/${collection}`,
555
660
  schemaObj,
661
+ 0,
662
+ true, // Force JSON for metadata operations
556
663
  );
557
664
  }
558
665
 
@@ -566,6 +673,9 @@ export class EkoDBClient {
566
673
  return this.makeRequest<CollectionMetadata>(
567
674
  "GET",
568
675
  `/api/collections/${collection}`,
676
+ undefined,
677
+ 0,
678
+ true, // Force JSON for metadata operations
569
679
  );
570
680
  }
571
681
 
@@ -625,6 +735,8 @@ export class EkoDBClient {
625
735
  "POST",
626
736
  `/api/search/${collection}`,
627
737
  queryObj,
738
+ 0,
739
+ true, // Force JSON for search operations
628
740
  );
629
741
  }
630
742
 
@@ -636,7 +748,13 @@ export class EkoDBClient {
636
748
  async createChatSession(
637
749
  request: CreateChatSessionRequest,
638
750
  ): Promise<ChatResponse> {
639
- return this.makeRequest<ChatResponse>("POST", "/api/chat", request);
751
+ return this.makeRequest<ChatResponse>(
752
+ "POST",
753
+ "/api/chat",
754
+ request,
755
+ 0,
756
+ true, // Force JSON for chat operations
757
+ );
640
758
  }
641
759
 
642
760
  /**
@@ -650,6 +768,8 @@ export class EkoDBClient {
650
768
  "POST",
651
769
  `/api/chat/${sessionId}/messages`,
652
770
  request,
771
+ 0,
772
+ true, // Force JSON for chat operations
653
773
  );
654
774
  }
655
775
 
@@ -660,6 +780,9 @@ export class EkoDBClient {
660
780
  return this.makeRequest<ChatSessionResponse>(
661
781
  "GET",
662
782
  `/api/chat/${sessionId}`,
783
+ undefined,
784
+ 0,
785
+ true, // Force JSON for chat operations
663
786
  );
664
787
  }
665
788
 
@@ -676,7 +799,13 @@ export class EkoDBClient {
676
799
 
677
800
  const queryString = params.toString();
678
801
  const path = queryString ? `/api/chat?${queryString}` : "/api/chat";
679
- return this.makeRequest<ListSessionsResponse>("GET", path);
802
+ return this.makeRequest<ListSessionsResponse>(
803
+ "GET",
804
+ path,
805
+ undefined,
806
+ 0,
807
+ true, // Force JSON for chat operations
808
+ );
680
809
  }
681
810
 
682
811
  /**
@@ -695,7 +824,13 @@ export class EkoDBClient {
695
824
  const path = queryString
696
825
  ? `/api/chat/${sessionId}/messages?${queryString}`
697
826
  : `/api/chat/${sessionId}/messages`;
698
- return this.makeRequest<GetMessagesResponse>("GET", path);
827
+ return this.makeRequest<GetMessagesResponse>(
828
+ "GET",
829
+ path,
830
+ undefined,
831
+ 0,
832
+ true, // Force JSON for chat operations
833
+ );
699
834
  }
700
835
 
701
836
  /**
@@ -709,6 +844,8 @@ export class EkoDBClient {
709
844
  "PUT",
710
845
  `/api/chat/${sessionId}`,
711
846
  request,
847
+ 0,
848
+ true, // Force JSON for chat operations
712
849
  );
713
850
  }
714
851
 
@@ -718,14 +855,26 @@ export class EkoDBClient {
718
855
  async branchChatSession(
719
856
  request: CreateChatSessionRequest,
720
857
  ): Promise<ChatResponse> {
721
- return this.makeRequest<ChatResponse>("POST", "/api/chat/branch", request);
858
+ return this.makeRequest<ChatResponse>(
859
+ "POST",
860
+ "/api/chat/branch",
861
+ request,
862
+ 0,
863
+ true, // Force JSON for chat operations
864
+ );
722
865
  }
723
866
 
724
867
  /**
725
868
  * Delete a chat session
726
869
  */
727
870
  async deleteChatSession(sessionId: string): Promise<void> {
728
- await this.makeRequest<void>("DELETE", `/api/chat/${sessionId}`);
871
+ await this.makeRequest<void>(
872
+ "DELETE",
873
+ `/api/chat/${sessionId}`,
874
+ undefined,
875
+ 0,
876
+ true, // Force JSON for chat operations
877
+ );
729
878
  }
730
879
 
731
880
  /**
@@ -738,6 +887,9 @@ export class EkoDBClient {
738
887
  return this.makeRequest<ChatResponse>(
739
888
  "POST",
740
889
  `/api/chat/${sessionId}/messages/${messageId}/regenerate`,
890
+ undefined,
891
+ 0,
892
+ true, // Force JSON for chat operations
741
893
  );
742
894
  }
743
895
 
@@ -753,6 +905,8 @@ export class EkoDBClient {
753
905
  "PUT",
754
906
  `/api/chat/${sessionId}/messages/${messageId}`,
755
907
  { content },
908
+ 0,
909
+ true, // Force JSON for chat operations
756
910
  );
757
911
  }
758
912
 
@@ -763,6 +917,9 @@ export class EkoDBClient {
763
917
  await this.makeRequest<void>(
764
918
  "DELETE",
765
919
  `/api/chat/${sessionId}/messages/${messageId}`,
920
+ undefined,
921
+ 0,
922
+ true, // Force JSON for chat operations
766
923
  );
767
924
  }
768
925
 
@@ -778,6 +935,8 @@ export class EkoDBClient {
778
935
  "PATCH",
779
936
  `/api/chat/${sessionId}/messages/${messageId}/forgotten`,
780
937
  { forgotten },
938
+ 0,
939
+ true, // Force JSON for chat operations
781
940
  );
782
941
  }
783
942
 
@@ -791,6 +950,67 @@ export class EkoDBClient {
791
950
  "POST",
792
951
  "/api/chat/merge",
793
952
  request,
953
+ 0,
954
+ true, // Force JSON for chat operations
955
+ );
956
+ }
957
+
958
+ // ========================================================================
959
+ // SCRIPTS API
960
+ // ========================================================================
961
+
962
+ /**
963
+ * Save a new script definition
964
+ */
965
+ async saveScript(script: Script): Promise<string> {
966
+ const result = await this.makeRequest<{ id: string }>(
967
+ "POST",
968
+ "/api/functions",
969
+ script,
970
+ );
971
+ return result.id;
972
+ }
973
+
974
+ /**
975
+ * Get a script by ID
976
+ */
977
+ async getScript(id: string): Promise<Script> {
978
+ return this.makeRequest<Script>("GET", `/api/functions/${id}`);
979
+ }
980
+
981
+ /**
982
+ * List all scripts, optionally filtered by tags
983
+ */
984
+ async listScripts(tags?: string[]): Promise<Script[]> {
985
+ const params = tags ? `?tags=${tags.join(",")}` : "";
986
+ return this.makeRequest<Script[]>("GET", `/api/functions${params}`);
987
+ }
988
+
989
+ /**
990
+ * Update an existing script by ID
991
+ */
992
+ async updateScript(id: string, script: Script): Promise<void> {
993
+ await this.makeRequest<void>("PUT", `/api/functions/${id}`, script);
994
+ }
995
+
996
+ /**
997
+ * Delete a script by ID
998
+ */
999
+ async deleteScript(id: string): Promise<void> {
1000
+ await this.makeRequest<void>("DELETE", `/api/functions/${id}`);
1001
+ }
1002
+
1003
+ /**
1004
+ * Call a saved script by ID or label
1005
+ */
1006
+ async callScript(
1007
+ idOrLabel: string,
1008
+ params?: { [key: string]: any },
1009
+ ): Promise<FunctionResult> {
1010
+ return this.makeRequest<FunctionResult>(
1011
+ "POST",
1012
+ `/api/functions/${idOrLabel}`,
1013
+ params || {},
794
1014
  );
795
1015
  }
796
1016