@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/src/utils.ts ADDED
@@ -0,0 +1,355 @@
1
+ /**
2
+ * Utility functions for working with ekoDB records
3
+ */
4
+
5
+ /**
6
+ * Extract the value from an ekoDB field object.
7
+ * ekoDB returns fields as { type: string, value: any } objects.
8
+ * This helper safely extracts the value or returns the input if it's not a field object.
9
+ *
10
+ * @param field - The field value from ekoDB (may be { type, value } or a plain value)
11
+ * @returns The extracted value
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const user = await client.findByID('users', userId);
16
+ * const email = getValue(user.email); // Extracts string from { type: 'String', value: 'user@example.com' }
17
+ * const age = getValue(user.age); // Extracts number from { type: 'Integer', value: 25 }
18
+ * ```
19
+ */
20
+ export function getValue<T = any>(field: any): T {
21
+ if (field && typeof field === "object" && "value" in field) {
22
+ return field.value as T;
23
+ }
24
+ return field as T;
25
+ }
26
+
27
+ /**
28
+ * Extract values from multiple fields in a record.
29
+ * Useful for processing entire records returned from ekoDB.
30
+ *
31
+ * @param record - The record object from ekoDB
32
+ * @param fields - Array of field names to extract values from
33
+ * @returns Object with extracted values
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const user = await client.findByID('users', userId);
38
+ * const { email, first_name, status } = getValues(user, ['email', 'first_name', 'status']);
39
+ * ```
40
+ */
41
+ export function getValues<T extends Record<string, any>>(
42
+ record: any,
43
+ fields: (keyof T)[],
44
+ ): Partial<T> {
45
+ const result: any = {};
46
+ for (const field of fields) {
47
+ result[field] = getValue(record[field]);
48
+ }
49
+ return result;
50
+ }
51
+
52
+ /**
53
+ * Extract a Date value from an ekoDB DateTime field
54
+ */
55
+ export function getDateTimeValue(field: any): Date | null {
56
+ const val = getValue(field);
57
+ if (val instanceof Date) return val;
58
+ if (typeof val === "string") {
59
+ const date = new Date(val);
60
+ return isNaN(date.getTime()) ? null : date;
61
+ }
62
+ return null;
63
+ }
64
+
65
+ /**
66
+ * Extract a UUID string from an ekoDB UUID field
67
+ */
68
+ export function getUUIDValue(field: any): string | null {
69
+ const val = getValue(field);
70
+ return typeof val === "string" ? val : null;
71
+ }
72
+
73
+ /**
74
+ * Extract a number from an ekoDB Decimal field
75
+ */
76
+ export function getDecimalValue(field: any): number | null {
77
+ const val = getValue(field);
78
+ if (typeof val === "number") return val;
79
+ if (typeof val === "string") {
80
+ const num = parseFloat(val);
81
+ return isNaN(num) ? null : num;
82
+ }
83
+ return null;
84
+ }
85
+
86
+ /**
87
+ * Extract a number (milliseconds) from an ekoDB Duration field
88
+ */
89
+ export function getDurationValue(field: any): number | null {
90
+ const val = getValue(field);
91
+ if (typeof val === "number") return val;
92
+ if (typeof val === "object" && val.secs !== undefined) {
93
+ return val.secs * 1000 + val.nanos / 1_000_000;
94
+ }
95
+ return null;
96
+ }
97
+
98
+ /**
99
+ * Extract a Uint8Array from an ekoDB Bytes field
100
+ */
101
+ export function getBytesValue(field: any): Uint8Array | null {
102
+ const val = getValue(field);
103
+ if (val instanceof Uint8Array) return val;
104
+ if (Array.isArray(val)) return new Uint8Array(val);
105
+ if (typeof val === "string") {
106
+ // Assume base64 encoded
107
+ try {
108
+ const binary = atob(val);
109
+ const bytes = new Uint8Array(binary.length);
110
+ for (let i = 0; i < binary.length; i++) {
111
+ bytes[i] = binary.charCodeAt(i);
112
+ }
113
+ return bytes;
114
+ } catch {
115
+ return null;
116
+ }
117
+ }
118
+ return null;
119
+ }
120
+
121
+ /**
122
+ * Extract a Uint8Array from an ekoDB Binary field
123
+ */
124
+ export function getBinaryValue(field: any): Uint8Array | null {
125
+ return getBytesValue(field);
126
+ }
127
+
128
+ /**
129
+ * Extract an array from an ekoDB Array field
130
+ */
131
+ export function getArrayValue<T = any>(field: any): T[] | null {
132
+ const val = getValue(field);
133
+ return Array.isArray(val) ? val : null;
134
+ }
135
+
136
+ /**
137
+ * Extract an array from an ekoDB Set field
138
+ */
139
+ export function getSetValue<T = any>(field: any): T[] | null {
140
+ const val = getValue(field);
141
+ return Array.isArray(val) ? val : null;
142
+ }
143
+
144
+ /**
145
+ * Extract an array from an ekoDB Vector field
146
+ */
147
+ export function getVectorValue(field: any): number[] | null {
148
+ const val = getValue(field);
149
+ if (Array.isArray(val)) {
150
+ return val
151
+ .map((v) => (typeof v === "number" ? v : parseFloat(v)))
152
+ .filter((v) => !isNaN(v));
153
+ }
154
+ return null;
155
+ }
156
+
157
+ /**
158
+ * Extract an object from an ekoDB Object field
159
+ */
160
+ export function getObjectValue<T = any>(field: any): T | null {
161
+ const val = getValue(field);
162
+ return val && typeof val === "object" && !Array.isArray(val) ? val : null;
163
+ }
164
+
165
+ /**
166
+ * Transform an entire record by extracting all field values.
167
+ * Preserves the 'id' field and extracts values from all other fields.
168
+ *
169
+ * @param record - The record object from ekoDB
170
+ * @returns Object with all field values extracted
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * const user = await client.findByID('users', userId);
175
+ * const plainUser = extractRecord(user);
176
+ * // { id: '123', email: 'user@example.com', first_name: 'John', ... }
177
+ * ```
178
+ */
179
+ export function extractRecord<T extends Record<string, any>>(record: any): T {
180
+ if (!record || typeof record !== "object") {
181
+ return record;
182
+ }
183
+
184
+ const result: any = {};
185
+ for (const [key, value] of Object.entries(record)) {
186
+ if (key === "id") {
187
+ result[key] = value;
188
+ } else {
189
+ result[key] = getValue(value);
190
+ }
191
+ }
192
+ return result as T;
193
+ }
194
+
195
+ // ============================================================================
196
+ // Wrapped Type Builders
197
+ // ============================================================================
198
+ // These functions create wrapped type objects for sending to ekoDB.
199
+ // Use these when inserting/updating records with special field types.
200
+ //
201
+ // Example:
202
+ // await client.insert("orders", {
203
+ // id: Field.uuid("550e8400-e29b-41d4-a716-446655440000"),
204
+ // total: Field.decimal("99.99"),
205
+ // created_at: Field.dateTime(new Date()),
206
+ // tags: Field.set(["sale", "featured"]),
207
+ // });
208
+
209
+ export interface WrappedFieldValue {
210
+ type: string;
211
+ value: unknown;
212
+ }
213
+
214
+ /**
215
+ * Field builders for creating wrapped type values to send to ekoDB.
216
+ * These are the inverse of the getValue* extraction functions.
217
+ */
218
+ export const Field = {
219
+ /**
220
+ * Create a UUID field value
221
+ * @param value - UUID string (e.g., "550e8400-e29b-41d4-a716-446655440000")
222
+ */
223
+ uuid: (value: string): WrappedFieldValue => ({
224
+ type: "UUID",
225
+ value,
226
+ }),
227
+
228
+ /**
229
+ * Create a Decimal field value for precise numeric values
230
+ * @param value - Decimal as string (e.g., "99.99") to preserve precision
231
+ */
232
+ decimal: (value: string): WrappedFieldValue => ({
233
+ type: "Decimal",
234
+ value,
235
+ }),
236
+
237
+ /**
238
+ * Create a DateTime field value
239
+ * @param value - Date object or RFC3339 string
240
+ */
241
+ dateTime: (value: Date | string): WrappedFieldValue => ({
242
+ type: "DateTime",
243
+ value: value instanceof Date ? value.toISOString() : value,
244
+ }),
245
+
246
+ /**
247
+ * Create a Duration field value
248
+ * @param milliseconds - Duration in milliseconds
249
+ */
250
+ duration: (milliseconds: number): WrappedFieldValue => ({
251
+ type: "Duration",
252
+ value: milliseconds,
253
+ }),
254
+
255
+ /**
256
+ * Create a Number field value (flexible numeric type)
257
+ * @param value - Integer or float
258
+ */
259
+ number: (value: number): WrappedFieldValue => ({
260
+ type: "Number",
261
+ value,
262
+ }),
263
+
264
+ /**
265
+ * Create a Set field value (unique elements)
266
+ * @param values - Array of values (duplicates will be removed by server)
267
+ */
268
+ set: <T>(values: T[]): WrappedFieldValue => ({
269
+ type: "Set",
270
+ value: values,
271
+ }),
272
+
273
+ /**
274
+ * Create a Vector field value (for embeddings/similarity search)
275
+ * @param values - Array of numbers representing the vector
276
+ */
277
+ vector: (values: number[]): WrappedFieldValue => ({
278
+ type: "Vector",
279
+ value: values,
280
+ }),
281
+
282
+ /**
283
+ * Create a Binary field value
284
+ * @param value - Base64 encoded string or Uint8Array
285
+ */
286
+ binary: (value: string | Uint8Array): WrappedFieldValue => ({
287
+ type: "Binary",
288
+ value:
289
+ value instanceof Uint8Array ? btoa(String.fromCharCode(...value)) : value,
290
+ }),
291
+
292
+ /**
293
+ * Create a Bytes field value
294
+ * @param value - Base64 encoded string or Uint8Array
295
+ */
296
+ bytes: (value: string | Uint8Array): WrappedFieldValue => ({
297
+ type: "Bytes",
298
+ value:
299
+ value instanceof Uint8Array ? btoa(String.fromCharCode(...value)) : value,
300
+ }),
301
+
302
+ /**
303
+ * Create an Array field value
304
+ * @param values - Array of values
305
+ */
306
+ array: <T>(values: T[]): WrappedFieldValue => ({
307
+ type: "Array",
308
+ value: values,
309
+ }),
310
+
311
+ /**
312
+ * Create an Object field value
313
+ * @param value - Object/map of key-value pairs
314
+ */
315
+ object: (value: Record<string, unknown>): WrappedFieldValue => ({
316
+ type: "Object",
317
+ value,
318
+ }),
319
+
320
+ /**
321
+ * Create a String field value (explicit wrapping)
322
+ * @param value - String value
323
+ */
324
+ string: (value: string): WrappedFieldValue => ({
325
+ type: "String",
326
+ value,
327
+ }),
328
+
329
+ /**
330
+ * Create an Integer field value (explicit wrapping)
331
+ * @param value - Integer value
332
+ */
333
+ integer: (value: number): WrappedFieldValue => ({
334
+ type: "Integer",
335
+ value: Math.floor(value),
336
+ }),
337
+
338
+ /**
339
+ * Create a Float field value (explicit wrapping)
340
+ * @param value - Float value
341
+ */
342
+ float: (value: number): WrappedFieldValue => ({
343
+ type: "Float",
344
+ value,
345
+ }),
346
+
347
+ /**
348
+ * Create a Boolean field value (explicit wrapping)
349
+ * @param value - Boolean value
350
+ */
351
+ boolean: (value: boolean): WrappedFieldValue => ({
352
+ type: "Boolean",
353
+ value,
354
+ }),
355
+ };