@ekodb/ekodb-client 0.6.0 → 0.7.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/utils.js ADDED
@@ -0,0 +1,325 @@
1
+ "use strict";
2
+ /**
3
+ * Utility functions for working with ekoDB records
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Field = void 0;
7
+ exports.getValue = getValue;
8
+ exports.getValues = getValues;
9
+ exports.getDateTimeValue = getDateTimeValue;
10
+ exports.getUUIDValue = getUUIDValue;
11
+ exports.getDecimalValue = getDecimalValue;
12
+ exports.getDurationValue = getDurationValue;
13
+ exports.getBytesValue = getBytesValue;
14
+ exports.getBinaryValue = getBinaryValue;
15
+ exports.getArrayValue = getArrayValue;
16
+ exports.getSetValue = getSetValue;
17
+ exports.getVectorValue = getVectorValue;
18
+ exports.getObjectValue = getObjectValue;
19
+ exports.extractRecord = extractRecord;
20
+ /**
21
+ * Extract the value from an ekoDB field object.
22
+ * ekoDB returns fields as { type: string, value: any } objects.
23
+ * This helper safely extracts the value or returns the input if it's not a field object.
24
+ *
25
+ * @param field - The field value from ekoDB (may be { type, value } or a plain value)
26
+ * @returns The extracted value
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const user = await client.findByID('users', userId);
31
+ * const email = getValue(user.email); // Extracts string from { type: 'String', value: 'user@example.com' }
32
+ * const age = getValue(user.age); // Extracts number from { type: 'Integer', value: 25 }
33
+ * ```
34
+ */
35
+ function getValue(field) {
36
+ if (field && typeof field === "object" && "value" in field) {
37
+ return field.value;
38
+ }
39
+ return field;
40
+ }
41
+ /**
42
+ * Extract values from multiple fields in a record.
43
+ * Useful for processing entire records returned from ekoDB.
44
+ *
45
+ * @param record - The record object from ekoDB
46
+ * @param fields - Array of field names to extract values from
47
+ * @returns Object with extracted values
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * const user = await client.findByID('users', userId);
52
+ * const { email, first_name, status } = getValues(user, ['email', 'first_name', 'status']);
53
+ * ```
54
+ */
55
+ function getValues(record, fields) {
56
+ const result = {};
57
+ for (const field of fields) {
58
+ result[field] = getValue(record[field]);
59
+ }
60
+ return result;
61
+ }
62
+ /**
63
+ * Extract a Date value from an ekoDB DateTime field
64
+ */
65
+ function getDateTimeValue(field) {
66
+ const val = getValue(field);
67
+ if (val instanceof Date)
68
+ return val;
69
+ if (typeof val === "string") {
70
+ const date = new Date(val);
71
+ return isNaN(date.getTime()) ? null : date;
72
+ }
73
+ return null;
74
+ }
75
+ /**
76
+ * Extract a UUID string from an ekoDB UUID field
77
+ */
78
+ function getUUIDValue(field) {
79
+ const val = getValue(field);
80
+ return typeof val === "string" ? val : null;
81
+ }
82
+ /**
83
+ * Extract a number from an ekoDB Decimal field
84
+ */
85
+ function getDecimalValue(field) {
86
+ const val = getValue(field);
87
+ if (typeof val === "number")
88
+ return val;
89
+ if (typeof val === "string") {
90
+ const num = parseFloat(val);
91
+ return isNaN(num) ? null : num;
92
+ }
93
+ return null;
94
+ }
95
+ /**
96
+ * Extract a number (milliseconds) from an ekoDB Duration field
97
+ */
98
+ function getDurationValue(field) {
99
+ const val = getValue(field);
100
+ if (typeof val === "number")
101
+ return val;
102
+ if (typeof val === "object" && val.secs !== undefined) {
103
+ return val.secs * 1000 + val.nanos / 1000000;
104
+ }
105
+ return null;
106
+ }
107
+ /**
108
+ * Extract a Uint8Array from an ekoDB Bytes field
109
+ */
110
+ function getBytesValue(field) {
111
+ const val = getValue(field);
112
+ if (val instanceof Uint8Array)
113
+ return val;
114
+ if (Array.isArray(val))
115
+ return new Uint8Array(val);
116
+ if (typeof val === "string") {
117
+ // Assume base64 encoded
118
+ try {
119
+ const binary = atob(val);
120
+ const bytes = new Uint8Array(binary.length);
121
+ for (let i = 0; i < binary.length; i++) {
122
+ bytes[i] = binary.charCodeAt(i);
123
+ }
124
+ return bytes;
125
+ }
126
+ catch {
127
+ return null;
128
+ }
129
+ }
130
+ return null;
131
+ }
132
+ /**
133
+ * Extract a Uint8Array from an ekoDB Binary field
134
+ */
135
+ function getBinaryValue(field) {
136
+ return getBytesValue(field);
137
+ }
138
+ /**
139
+ * Extract an array from an ekoDB Array field
140
+ */
141
+ function getArrayValue(field) {
142
+ const val = getValue(field);
143
+ return Array.isArray(val) ? val : null;
144
+ }
145
+ /**
146
+ * Extract an array from an ekoDB Set field
147
+ */
148
+ function getSetValue(field) {
149
+ const val = getValue(field);
150
+ return Array.isArray(val) ? val : null;
151
+ }
152
+ /**
153
+ * Extract an array from an ekoDB Vector field
154
+ */
155
+ function getVectorValue(field) {
156
+ const val = getValue(field);
157
+ if (Array.isArray(val)) {
158
+ return val
159
+ .map((v) => (typeof v === "number" ? v : parseFloat(v)))
160
+ .filter((v) => !isNaN(v));
161
+ }
162
+ return null;
163
+ }
164
+ /**
165
+ * Extract an object from an ekoDB Object field
166
+ */
167
+ function getObjectValue(field) {
168
+ const val = getValue(field);
169
+ return val && typeof val === "object" && !Array.isArray(val) ? val : null;
170
+ }
171
+ /**
172
+ * Transform an entire record by extracting all field values.
173
+ * Preserves the 'id' field and extracts values from all other fields.
174
+ *
175
+ * @param record - The record object from ekoDB
176
+ * @returns Object with all field values extracted
177
+ *
178
+ * @example
179
+ * ```typescript
180
+ * const user = await client.findByID('users', userId);
181
+ * const plainUser = extractRecord(user);
182
+ * // { id: '123', email: 'user@example.com', first_name: 'John', ... }
183
+ * ```
184
+ */
185
+ function extractRecord(record) {
186
+ if (!record || typeof record !== "object") {
187
+ return record;
188
+ }
189
+ const result = {};
190
+ for (const [key, value] of Object.entries(record)) {
191
+ if (key === "id") {
192
+ result[key] = value;
193
+ }
194
+ else {
195
+ result[key] = getValue(value);
196
+ }
197
+ }
198
+ return result;
199
+ }
200
+ /**
201
+ * Field builders for creating wrapped type values to send to ekoDB.
202
+ * These are the inverse of the getValue* extraction functions.
203
+ */
204
+ exports.Field = {
205
+ /**
206
+ * Create a UUID field value
207
+ * @param value - UUID string (e.g., "550e8400-e29b-41d4-a716-446655440000")
208
+ */
209
+ uuid: (value) => ({
210
+ type: "UUID",
211
+ value,
212
+ }),
213
+ /**
214
+ * Create a Decimal field value for precise numeric values
215
+ * @param value - Decimal as string (e.g., "99.99") to preserve precision
216
+ */
217
+ decimal: (value) => ({
218
+ type: "Decimal",
219
+ value,
220
+ }),
221
+ /**
222
+ * Create a DateTime field value
223
+ * @param value - Date object or RFC3339 string
224
+ */
225
+ dateTime: (value) => ({
226
+ type: "DateTime",
227
+ value: value instanceof Date ? value.toISOString() : value,
228
+ }),
229
+ /**
230
+ * Create a Duration field value
231
+ * @param milliseconds - Duration in milliseconds
232
+ */
233
+ duration: (milliseconds) => ({
234
+ type: "Duration",
235
+ value: milliseconds,
236
+ }),
237
+ /**
238
+ * Create a Number field value (flexible numeric type)
239
+ * @param value - Integer or float
240
+ */
241
+ number: (value) => ({
242
+ type: "Number",
243
+ value,
244
+ }),
245
+ /**
246
+ * Create a Set field value (unique elements)
247
+ * @param values - Array of values (duplicates will be removed by server)
248
+ */
249
+ set: (values) => ({
250
+ type: "Set",
251
+ value: values,
252
+ }),
253
+ /**
254
+ * Create a Vector field value (for embeddings/similarity search)
255
+ * @param values - Array of numbers representing the vector
256
+ */
257
+ vector: (values) => ({
258
+ type: "Vector",
259
+ value: values,
260
+ }),
261
+ /**
262
+ * Create a Binary field value
263
+ * @param value - Base64 encoded string or Uint8Array
264
+ */
265
+ binary: (value) => ({
266
+ type: "Binary",
267
+ value: value instanceof Uint8Array ? btoa(String.fromCharCode(...value)) : value,
268
+ }),
269
+ /**
270
+ * Create a Bytes field value
271
+ * @param value - Base64 encoded string or Uint8Array
272
+ */
273
+ bytes: (value) => ({
274
+ type: "Bytes",
275
+ value: value instanceof Uint8Array ? btoa(String.fromCharCode(...value)) : value,
276
+ }),
277
+ /**
278
+ * Create an Array field value
279
+ * @param values - Array of values
280
+ */
281
+ array: (values) => ({
282
+ type: "Array",
283
+ value: values,
284
+ }),
285
+ /**
286
+ * Create an Object field value
287
+ * @param value - Object/map of key-value pairs
288
+ */
289
+ object: (value) => ({
290
+ type: "Object",
291
+ value,
292
+ }),
293
+ /**
294
+ * Create a String field value (explicit wrapping)
295
+ * @param value - String value
296
+ */
297
+ string: (value) => ({
298
+ type: "String",
299
+ value,
300
+ }),
301
+ /**
302
+ * Create an Integer field value (explicit wrapping)
303
+ * @param value - Integer value
304
+ */
305
+ integer: (value) => ({
306
+ type: "Integer",
307
+ value: Math.floor(value),
308
+ }),
309
+ /**
310
+ * Create a Float field value (explicit wrapping)
311
+ * @param value - Float value
312
+ */
313
+ float: (value) => ({
314
+ type: "Float",
315
+ value,
316
+ }),
317
+ /**
318
+ * Create a Boolean field value (explicit wrapping)
319
+ * @param value - Boolean value
320
+ */
321
+ boolean: (value) => ({
322
+ type: "Boolean",
323
+ value,
324
+ }),
325
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekodb/ekodb-client",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Official TypeScript/JavaScript client for ekoDB",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/client.ts CHANGED
@@ -441,6 +441,9 @@ export class EkoDBClient {
441
441
 
442
442
  /**
443
443
  * Insert a document into a collection
444
+ * @param collection - Collection name
445
+ * @param record - Document to insert
446
+ * @param ttl - Optional TTL: duration string ("1h", "30m"), seconds ("3600"), or ISO8601 timestamp
444
447
  */
445
448
  async insert(
446
449
  collection: string,
@@ -449,7 +452,7 @@ export class EkoDBClient {
449
452
  ): Promise<Record> {
450
453
  const data = { ...record };
451
454
  if (ttl) {
452
- data.ttl_duration = ttl;
455
+ data.ttl = ttl;
453
456
  }
454
457
  return this.makeRequest<Record>("POST", `/api/insert/${collection}`, data);
455
458
  }
@@ -575,13 +578,20 @@ export class EkoDBClient {
575
578
  }
576
579
 
577
580
  /**
578
- * Set a key-value pair
581
+ * Set a key-value pair with optional TTL
582
+ * @param key - The key to set
583
+ * @param value - The value to store
584
+ * @param ttl - Optional TTL in seconds
579
585
  */
580
- async kvSet(key: string, value: any): Promise<void> {
586
+ async kvSet(key: string, value: any, ttl?: number): Promise<void> {
587
+ const body: any = { value };
588
+ if (ttl !== undefined) {
589
+ body.ttl = ttl;
590
+ }
581
591
  await this.makeRequest<void>(
582
592
  "POST",
583
593
  `/api/kv/set/${encodeURIComponent(key)}`,
584
- { value },
594
+ body,
585
595
  0,
586
596
  true, // Force JSON for KV operations
587
597
  );
@@ -614,6 +624,116 @@ export class EkoDBClient {
614
624
  );
615
625
  }
616
626
 
627
+ /**
628
+ * Check if a key exists
629
+ * @param key - The key to check
630
+ * @returns true if the key exists, false otherwise
631
+ */
632
+ async kvExists(key: string): Promise<boolean> {
633
+ try {
634
+ const result = await this.kvGet(key);
635
+ return result !== null && result !== undefined;
636
+ } catch {
637
+ return false;
638
+ }
639
+ }
640
+
641
+ /**
642
+ * Query/find KV entries with pattern matching
643
+ * @param options - Query options including pattern and include_expired
644
+ * @returns Array of matching records
645
+ */
646
+ async kvFind(options?: {
647
+ pattern?: string;
648
+ include_expired?: boolean;
649
+ }): Promise<any[]> {
650
+ const result = await this.makeRequest<any[]>(
651
+ "POST",
652
+ "/api/kv/find",
653
+ options || {},
654
+ 0,
655
+ true, // Force JSON for KV operations
656
+ );
657
+ return result;
658
+ }
659
+
660
+ /**
661
+ * Alias for kvFind - query KV store with pattern
662
+ */
663
+ async kvQuery(options?: {
664
+ pattern?: string;
665
+ include_expired?: boolean;
666
+ }): Promise<any[]> {
667
+ return this.kvFind(options);
668
+ }
669
+
670
+ // ============================================================================
671
+ // Transaction Operations
672
+ // ============================================================================
673
+
674
+ /**
675
+ * Begin a new transaction
676
+ * @param isolationLevel - Transaction isolation level (default: "ReadCommitted")
677
+ * @returns Transaction ID
678
+ */
679
+ async beginTransaction(
680
+ isolationLevel: string = "ReadCommitted",
681
+ ): Promise<string> {
682
+ const result = await this.makeRequest<{ transaction_id: string }>(
683
+ "POST",
684
+ "/api/transactions",
685
+ { isolation_level: isolationLevel },
686
+ 0,
687
+ true,
688
+ );
689
+ return result.transaction_id;
690
+ }
691
+
692
+ /**
693
+ * Get transaction status
694
+ * @param transactionId - The transaction ID
695
+ * @returns Transaction status object
696
+ */
697
+ async getTransactionStatus(
698
+ transactionId: string,
699
+ ): Promise<{ state: string; operations_count: number }> {
700
+ return this.makeRequest<{ state: string; operations_count: number }>(
701
+ "GET",
702
+ `/api/transactions/${encodeURIComponent(transactionId)}`,
703
+ undefined,
704
+ 0,
705
+ true,
706
+ );
707
+ }
708
+
709
+ /**
710
+ * Commit a transaction
711
+ * @param transactionId - The transaction ID to commit
712
+ */
713
+ async commitTransaction(transactionId: string): Promise<void> {
714
+ await this.makeRequest<void>(
715
+ "POST",
716
+ `/api/transactions/${encodeURIComponent(transactionId)}/commit`,
717
+ undefined,
718
+ 0,
719
+ true,
720
+ );
721
+ }
722
+
723
+ /**
724
+ * Rollback a transaction
725
+ * @param transactionId - The transaction ID to rollback
726
+ */
727
+ async rollbackTransaction(transactionId: string): Promise<void> {
728
+ await this.makeRequest<void>(
729
+ "POST",
730
+ `/api/transactions/${encodeURIComponent(transactionId)}/rollback`,
731
+ undefined,
732
+ 0,
733
+ true,
734
+ );
735
+ }
736
+
617
737
  /**
618
738
  * List all collections
619
739
  */
package/src/functions.ts CHANGED
@@ -187,6 +187,31 @@ export type FunctionStageConfig =
187
187
  | {
188
188
  type: "ReleaseSavepoint";
189
189
  name: string;
190
+ }
191
+ | {
192
+ type: "KvGet";
193
+ key: string;
194
+ output_field?: string;
195
+ }
196
+ | {
197
+ type: "KvSet";
198
+ key: string;
199
+ value: any;
200
+ ttl?: number;
201
+ }
202
+ | {
203
+ type: "KvDelete";
204
+ key: string;
205
+ }
206
+ | {
207
+ type: "KvExists";
208
+ key: string;
209
+ output_field?: string;
210
+ }
211
+ | {
212
+ type: "KvQuery";
213
+ pattern?: string;
214
+ include_expired?: boolean;
190
215
  };
191
216
 
192
217
  export interface ChatMessage {
@@ -572,4 +597,38 @@ export const Stage = {
572
597
  type: "ReleaseSavepoint",
573
598
  name,
574
599
  }),
600
+
601
+ // KV Store operations - faster than collection lookups for simple key-value data
602
+ kvGet: (key: string, output_field?: string): FunctionStageConfig => ({
603
+ type: "KvGet",
604
+ key,
605
+ output_field,
606
+ }),
607
+
608
+ kvSet: (key: string, value: any, ttl?: number): FunctionStageConfig => ({
609
+ type: "KvSet",
610
+ key,
611
+ value,
612
+ ttl,
613
+ }),
614
+
615
+ kvDelete: (key: string): FunctionStageConfig => ({
616
+ type: "KvDelete",
617
+ key,
618
+ }),
619
+
620
+ kvExists: (key: string, output_field?: string): FunctionStageConfig => ({
621
+ type: "KvExists",
622
+ key,
623
+ output_field,
624
+ }),
625
+
626
+ kvQuery: (
627
+ pattern?: string,
628
+ include_expired?: boolean,
629
+ ): FunctionStageConfig => ({
630
+ type: "KvQuery",
631
+ pattern,
632
+ include_expired,
633
+ }),
575
634
  };
package/src/index.ts CHANGED
@@ -15,6 +15,23 @@ export {
15
15
  } from "./schema";
16
16
  export { JoinBuilder } from "./join";
17
17
  export { Stage, ChatMessage } from "./functions";
18
+ export {
19
+ getValue,
20
+ getValues,
21
+ extractRecord,
22
+ getDateTimeValue,
23
+ getUUIDValue,
24
+ getDecimalValue,
25
+ getDurationValue,
26
+ getBytesValue,
27
+ getBinaryValue,
28
+ getArrayValue,
29
+ getSetValue,
30
+ getVectorValue,
31
+ getObjectValue,
32
+ Field,
33
+ } from "./utils";
34
+ export type { WrappedFieldValue } from "./utils";
18
35
  export type { SearchQuery, SearchResult, SearchResponse } from "./search";
19
36
  export type {
20
37
  Schema,