@ekodb/ekodb-client 0.6.1 → 0.7.1
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/client.d.ts +56 -3
- package/dist/client.js +78 -5
- package/dist/functions.d.ts +25 -0
- package/dist/functions.js +26 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/query-builder.test.d.ts +4 -0
- package/dist/query-builder.test.js +318 -0
- package/dist/utils.d.ts +88 -3
- package/dist/utils.js +130 -3
- package/dist/utils.test.d.ts +4 -0
- package/dist/utils.test.js +411 -0
- package/package.json +7 -4
- package/src/client.ts +125 -5
- package/src/functions.ts +59 -0
- package/src/index.ts +2 -0
- package/src/query-builder.test.ts +404 -0
- package/src/utils.test.ts +506 -0
- package/src/utils.ts +165 -3
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Unit tests for ekoDB TypeScript client utility functions
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const vitest_1 = require("vitest");
|
|
7
|
+
const utils_1 = require("./utils");
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// getValue Tests
|
|
10
|
+
// ============================================================================
|
|
11
|
+
(0, vitest_1.describe)("getValue", () => {
|
|
12
|
+
(0, vitest_1.it)("extracts value from wrapped string field", () => {
|
|
13
|
+
const field = { type: "String", value: "hello world" };
|
|
14
|
+
(0, vitest_1.expect)((0, utils_1.getValue)(field)).toBe("hello world");
|
|
15
|
+
});
|
|
16
|
+
(0, vitest_1.it)("extracts value from wrapped integer field", () => {
|
|
17
|
+
const field = { type: "Integer", value: 42 };
|
|
18
|
+
(0, vitest_1.expect)((0, utils_1.getValue)(field)).toBe(42);
|
|
19
|
+
});
|
|
20
|
+
(0, vitest_1.it)("extracts value from wrapped float field", () => {
|
|
21
|
+
const field = { type: "Float", value: 3.14 };
|
|
22
|
+
(0, vitest_1.expect)((0, utils_1.getValue)(field)).toBe(3.14);
|
|
23
|
+
});
|
|
24
|
+
(0, vitest_1.it)("extracts value from wrapped boolean field", () => {
|
|
25
|
+
const field = { type: "Boolean", value: true };
|
|
26
|
+
(0, vitest_1.expect)((0, utils_1.getValue)(field)).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
(0, vitest_1.it)("extracts value from wrapped null field", () => {
|
|
29
|
+
const field = { type: "Null", value: null };
|
|
30
|
+
(0, vitest_1.expect)((0, utils_1.getValue)(field)).toBeNull();
|
|
31
|
+
});
|
|
32
|
+
(0, vitest_1.it)("returns plain string as-is", () => {
|
|
33
|
+
(0, vitest_1.expect)((0, utils_1.getValue)("plain string")).toBe("plain string");
|
|
34
|
+
});
|
|
35
|
+
(0, vitest_1.it)("returns plain number as-is", () => {
|
|
36
|
+
(0, vitest_1.expect)((0, utils_1.getValue)(123)).toBe(123);
|
|
37
|
+
});
|
|
38
|
+
(0, vitest_1.it)("returns plain boolean as-is", () => {
|
|
39
|
+
(0, vitest_1.expect)((0, utils_1.getValue)(true)).toBe(true);
|
|
40
|
+
(0, vitest_1.expect)((0, utils_1.getValue)(false)).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
(0, vitest_1.it)("returns null as-is", () => {
|
|
43
|
+
(0, vitest_1.expect)((0, utils_1.getValue)(null)).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
(0, vitest_1.it)("returns undefined as-is", () => {
|
|
46
|
+
(0, vitest_1.expect)((0, utils_1.getValue)(undefined)).toBeUndefined();
|
|
47
|
+
});
|
|
48
|
+
(0, vitest_1.it)("returns plain array as-is", () => {
|
|
49
|
+
const arr = [1, 2, 3];
|
|
50
|
+
(0, vitest_1.expect)((0, utils_1.getValue)(arr)).toEqual([1, 2, 3]);
|
|
51
|
+
});
|
|
52
|
+
(0, vitest_1.it)("returns object without value key as-is", () => {
|
|
53
|
+
const obj = { name: "test", count: 5 };
|
|
54
|
+
(0, vitest_1.expect)((0, utils_1.getValue)(obj)).toEqual({ name: "test", count: 5 });
|
|
55
|
+
});
|
|
56
|
+
(0, vitest_1.it)("supports generic type parameter", () => {
|
|
57
|
+
const field = { type: "String", value: "typed" };
|
|
58
|
+
const result = (0, utils_1.getValue)(field);
|
|
59
|
+
(0, vitest_1.expect)(result).toBe("typed");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
// ============================================================================
|
|
63
|
+
// getValues Tests
|
|
64
|
+
// ============================================================================
|
|
65
|
+
(0, vitest_1.describe)("getValues", () => {
|
|
66
|
+
(0, vitest_1.it)("extracts multiple field values from record", () => {
|
|
67
|
+
const record = {
|
|
68
|
+
name: { type: "String", value: "John" },
|
|
69
|
+
age: { type: "Integer", value: 30 },
|
|
70
|
+
active: { type: "Boolean", value: true },
|
|
71
|
+
};
|
|
72
|
+
const result = (0, utils_1.getValues)(record, ["name", "age", "active"]);
|
|
73
|
+
(0, vitest_1.expect)(result).toEqual({ name: "John", age: 30, active: true });
|
|
74
|
+
});
|
|
75
|
+
(0, vitest_1.it)("extracts only requested fields", () => {
|
|
76
|
+
const record = {
|
|
77
|
+
name: { type: "String", value: "John" },
|
|
78
|
+
age: { type: "Integer", value: 30 },
|
|
79
|
+
email: { type: "String", value: "john@example.com" },
|
|
80
|
+
};
|
|
81
|
+
const result = (0, utils_1.getValues)(record, ["name", "email"]);
|
|
82
|
+
(0, vitest_1.expect)(result).toEqual({ name: "John", email: "john@example.com" });
|
|
83
|
+
(0, vitest_1.expect)(result).not.toHaveProperty("age");
|
|
84
|
+
});
|
|
85
|
+
(0, vitest_1.it)("handles missing fields gracefully", () => {
|
|
86
|
+
const record = {
|
|
87
|
+
name: { type: "String", value: "John" },
|
|
88
|
+
};
|
|
89
|
+
const result = (0, utils_1.getValues)(record, ["name", "missing"]);
|
|
90
|
+
(0, vitest_1.expect)(result.name).toBe("John");
|
|
91
|
+
(0, vitest_1.expect)(result.missing).toBeUndefined();
|
|
92
|
+
});
|
|
93
|
+
(0, vitest_1.it)("handles empty record", () => {
|
|
94
|
+
const result = (0, utils_1.getValues)({}, ["name", "age"]);
|
|
95
|
+
(0, vitest_1.expect)(result.name).toBeUndefined();
|
|
96
|
+
(0, vitest_1.expect)(result.age).toBeUndefined();
|
|
97
|
+
});
|
|
98
|
+
(0, vitest_1.it)("handles empty fields list", () => {
|
|
99
|
+
const record = { name: { type: "String", value: "John" } };
|
|
100
|
+
const result = (0, utils_1.getValues)(record, []);
|
|
101
|
+
(0, vitest_1.expect)(result).toEqual({});
|
|
102
|
+
});
|
|
103
|
+
(0, vitest_1.it)("handles mixed wrapped and plain values", () => {
|
|
104
|
+
const record = {
|
|
105
|
+
wrapped: { type: "String", value: "wrapped_value" },
|
|
106
|
+
plain: "plain_value",
|
|
107
|
+
};
|
|
108
|
+
const result = (0, utils_1.getValues)(record, ["wrapped", "plain"]);
|
|
109
|
+
(0, vitest_1.expect)(result).toEqual({ wrapped: "wrapped_value", plain: "plain_value" });
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
// ============================================================================
|
|
113
|
+
// Specialized Value Extractors Tests
|
|
114
|
+
// ============================================================================
|
|
115
|
+
(0, vitest_1.describe)("getDateTimeValue", () => {
|
|
116
|
+
(0, vitest_1.it)("parses ISO format datetime string", () => {
|
|
117
|
+
const field = { type: "DateTime", value: "2024-01-15T10:30:00Z" };
|
|
118
|
+
const result = (0, utils_1.getDateTimeValue)(field);
|
|
119
|
+
(0, vitest_1.expect)(result).toBeInstanceOf(Date);
|
|
120
|
+
(0, vitest_1.expect)(result?.getFullYear()).toBe(2024);
|
|
121
|
+
(0, vitest_1.expect)(result?.getMonth()).toBe(0); // January is 0
|
|
122
|
+
(0, vitest_1.expect)(result?.getDate()).toBe(15);
|
|
123
|
+
});
|
|
124
|
+
(0, vitest_1.it)("parses ISO format with timezone offset", () => {
|
|
125
|
+
const field = { type: "DateTime", value: "2024-01-15T10:30:00+00:00" };
|
|
126
|
+
const result = (0, utils_1.getDateTimeValue)(field);
|
|
127
|
+
(0, vitest_1.expect)(result).toBeInstanceOf(Date);
|
|
128
|
+
(0, vitest_1.expect)(result?.getFullYear()).toBe(2024);
|
|
129
|
+
});
|
|
130
|
+
(0, vitest_1.it)("returns Date object as-is", () => {
|
|
131
|
+
const dt = new Date(2024, 0, 15, 10, 30, 0);
|
|
132
|
+
const field = { type: "DateTime", value: dt };
|
|
133
|
+
const result = (0, utils_1.getDateTimeValue)(field);
|
|
134
|
+
(0, vitest_1.expect)(result).toBe(dt);
|
|
135
|
+
});
|
|
136
|
+
(0, vitest_1.it)("returns null for invalid datetime string", () => {
|
|
137
|
+
const field = { type: "DateTime", value: "not-a-datetime" };
|
|
138
|
+
const result = (0, utils_1.getDateTimeValue)(field);
|
|
139
|
+
(0, vitest_1.expect)(result).toBeNull();
|
|
140
|
+
});
|
|
141
|
+
(0, vitest_1.it)("returns null for null value", () => {
|
|
142
|
+
(0, vitest_1.expect)((0, utils_1.getDateTimeValue)(null)).toBeNull();
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
(0, vitest_1.describe)("getUUIDValue", () => {
|
|
146
|
+
(0, vitest_1.it)("extracts UUID from wrapped field", () => {
|
|
147
|
+
const field = {
|
|
148
|
+
type: "UUID",
|
|
149
|
+
value: "550e8400-e29b-41d4-a716-446655440000",
|
|
150
|
+
};
|
|
151
|
+
(0, vitest_1.expect)((0, utils_1.getUUIDValue)(field)).toBe("550e8400-e29b-41d4-a716-446655440000");
|
|
152
|
+
});
|
|
153
|
+
(0, vitest_1.it)("extracts UUID from plain string", () => {
|
|
154
|
+
(0, vitest_1.expect)((0, utils_1.getUUIDValue)("550e8400-e29b-41d4-a716-446655440000")).toBe("550e8400-e29b-41d4-a716-446655440000");
|
|
155
|
+
});
|
|
156
|
+
(0, vitest_1.it)("returns null for non-string", () => {
|
|
157
|
+
(0, vitest_1.expect)((0, utils_1.getUUIDValue)(12345)).toBeNull();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
(0, vitest_1.describe)("getDecimalValue", () => {
|
|
161
|
+
(0, vitest_1.it)("extracts decimal from integer", () => {
|
|
162
|
+
const field = { type: "Decimal", value: 42 };
|
|
163
|
+
(0, vitest_1.expect)((0, utils_1.getDecimalValue)(field)).toBe(42);
|
|
164
|
+
});
|
|
165
|
+
(0, vitest_1.it)("extracts decimal from float", () => {
|
|
166
|
+
const field = { type: "Decimal", value: 3.14159 };
|
|
167
|
+
(0, vitest_1.expect)((0, utils_1.getDecimalValue)(field)).toBeCloseTo(3.14159);
|
|
168
|
+
});
|
|
169
|
+
(0, vitest_1.it)("extracts decimal from string representation", () => {
|
|
170
|
+
const field = { type: "Decimal", value: "123.456" };
|
|
171
|
+
(0, vitest_1.expect)((0, utils_1.getDecimalValue)(field)).toBeCloseTo(123.456);
|
|
172
|
+
});
|
|
173
|
+
(0, vitest_1.it)("returns null for invalid decimal string", () => {
|
|
174
|
+
const field = { type: "Decimal", value: "not-a-number" };
|
|
175
|
+
(0, vitest_1.expect)((0, utils_1.getDecimalValue)(field)).toBeNull();
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
(0, vitest_1.describe)("getDurationValue", () => {
|
|
179
|
+
(0, vitest_1.it)("extracts duration from number (milliseconds)", () => {
|
|
180
|
+
const field = { type: "Duration", value: 3600000 };
|
|
181
|
+
(0, vitest_1.expect)((0, utils_1.getDurationValue)(field)).toBe(3600000);
|
|
182
|
+
});
|
|
183
|
+
(0, vitest_1.it)("extracts duration from secs/nanos object", () => {
|
|
184
|
+
const field = { type: "Duration", value: { secs: 10, nanos: 500000000 } };
|
|
185
|
+
(0, vitest_1.expect)((0, utils_1.getDurationValue)(field)).toBeCloseTo(10500); // 10.5 seconds in ms
|
|
186
|
+
});
|
|
187
|
+
(0, vitest_1.it)("extracts duration from plain number", () => {
|
|
188
|
+
(0, vitest_1.expect)((0, utils_1.getDurationValue)(10500)).toBe(10500);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
(0, vitest_1.describe)("getBytesValue", () => {
|
|
192
|
+
(0, vitest_1.it)("extracts bytes from Uint8Array", () => {
|
|
193
|
+
const bytes = new Uint8Array([104, 101, 108, 108, 111]);
|
|
194
|
+
const field = { type: "Bytes", value: bytes };
|
|
195
|
+
const result = (0, utils_1.getBytesValue)(field);
|
|
196
|
+
(0, vitest_1.expect)(result).toEqual(bytes);
|
|
197
|
+
});
|
|
198
|
+
(0, vitest_1.it)("extracts bytes from array of integers", () => {
|
|
199
|
+
const field = { type: "Bytes", value: [104, 101, 108, 108, 111] };
|
|
200
|
+
const result = (0, utils_1.getBytesValue)(field);
|
|
201
|
+
(0, vitest_1.expect)(result).toBeInstanceOf(Uint8Array);
|
|
202
|
+
(0, vitest_1.expect)(Array.from(result)).toEqual([104, 101, 108, 108, 111]);
|
|
203
|
+
});
|
|
204
|
+
(0, vitest_1.it)("extracts bytes from base64 string", () => {
|
|
205
|
+
const field = { type: "Bytes", value: btoa("hello") };
|
|
206
|
+
const result = (0, utils_1.getBytesValue)(field);
|
|
207
|
+
(0, vitest_1.expect)(result).toBeInstanceOf(Uint8Array);
|
|
208
|
+
(0, vitest_1.expect)(new TextDecoder().decode(result)).toBe("hello");
|
|
209
|
+
});
|
|
210
|
+
(0, vitest_1.it)("returns null for invalid bytes", () => {
|
|
211
|
+
(0, vitest_1.expect)((0, utils_1.getBytesValue)(12345)).toBeNull();
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
(0, vitest_1.describe)("getBinaryValue", () => {
|
|
215
|
+
(0, vitest_1.it)("extracts binary from Uint8Array (alias for getBytesValue)", () => {
|
|
216
|
+
const bytes = new Uint8Array([1, 2, 3, 4]);
|
|
217
|
+
const field = { type: "Binary", value: bytes };
|
|
218
|
+
(0, vitest_1.expect)((0, utils_1.getBinaryValue)(field)).toEqual(bytes);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
(0, vitest_1.describe)("getArrayValue", () => {
|
|
222
|
+
(0, vitest_1.it)("extracts array from wrapped field", () => {
|
|
223
|
+
const field = { type: "Array", value: [1, 2, 3, 4, 5] };
|
|
224
|
+
(0, vitest_1.expect)((0, utils_1.getArrayValue)(field)).toEqual([1, 2, 3, 4, 5]);
|
|
225
|
+
});
|
|
226
|
+
(0, vitest_1.it)("extracts array from plain array", () => {
|
|
227
|
+
(0, vitest_1.expect)((0, utils_1.getArrayValue)([1, 2, 3])).toEqual([1, 2, 3]);
|
|
228
|
+
});
|
|
229
|
+
(0, vitest_1.it)("returns null for non-array", () => {
|
|
230
|
+
(0, vitest_1.expect)((0, utils_1.getArrayValue)("not an array")).toBeNull();
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
(0, vitest_1.describe)("getSetValue", () => {
|
|
234
|
+
(0, vitest_1.it)("extracts set from wrapped field", () => {
|
|
235
|
+
const field = { type: "Set", value: ["a", "b", "c"] };
|
|
236
|
+
(0, vitest_1.expect)((0, utils_1.getSetValue)(field)).toEqual(["a", "b", "c"]);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
(0, vitest_1.describe)("getVectorValue", () => {
|
|
240
|
+
(0, vitest_1.it)("extracts vector from array of floats", () => {
|
|
241
|
+
const field = { type: "Vector", value: [0.1, 0.2, 0.3, 0.4] };
|
|
242
|
+
(0, vitest_1.expect)((0, utils_1.getVectorValue)(field)).toEqual([0.1, 0.2, 0.3, 0.4]);
|
|
243
|
+
});
|
|
244
|
+
(0, vitest_1.it)("converts integers to floats", () => {
|
|
245
|
+
const field = { type: "Vector", value: [1, 2, 3] };
|
|
246
|
+
(0, vitest_1.expect)((0, utils_1.getVectorValue)(field)).toEqual([1, 2, 3]);
|
|
247
|
+
});
|
|
248
|
+
(0, vitest_1.it)("filters out invalid values", () => {
|
|
249
|
+
const field = { type: "Vector", value: [1, "invalid", 3] };
|
|
250
|
+
const result = (0, utils_1.getVectorValue)(field);
|
|
251
|
+
(0, vitest_1.expect)(result).toEqual([1, 3]);
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
(0, vitest_1.describe)("getObjectValue", () => {
|
|
255
|
+
(0, vitest_1.it)("extracts object from wrapped field", () => {
|
|
256
|
+
const field = { type: "Object", value: { key: "value", count: 5 } };
|
|
257
|
+
(0, vitest_1.expect)((0, utils_1.getObjectValue)(field)).toEqual({ key: "value", count: 5 });
|
|
258
|
+
});
|
|
259
|
+
(0, vitest_1.it)("returns null for non-object", () => {
|
|
260
|
+
(0, vitest_1.expect)((0, utils_1.getObjectValue)([1, 2, 3])).toBeNull();
|
|
261
|
+
});
|
|
262
|
+
(0, vitest_1.it)("returns null for array (not a plain object)", () => {
|
|
263
|
+
const field = { type: "Object", value: [1, 2, 3] };
|
|
264
|
+
(0, vitest_1.expect)((0, utils_1.getObjectValue)(field)).toBeNull();
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
// ============================================================================
|
|
268
|
+
// Field Builder Tests
|
|
269
|
+
// ============================================================================
|
|
270
|
+
(0, vitest_1.describe)("Field builders", () => {
|
|
271
|
+
(0, vitest_1.it)("builds string field", () => {
|
|
272
|
+
(0, vitest_1.expect)(utils_1.Field.string("hello")).toEqual({ type: "String", value: "hello" });
|
|
273
|
+
});
|
|
274
|
+
(0, vitest_1.it)("builds integer field", () => {
|
|
275
|
+
(0, vitest_1.expect)(utils_1.Field.integer(42)).toEqual({ type: "Integer", value: 42 });
|
|
276
|
+
});
|
|
277
|
+
(0, vitest_1.it)("builds integer field (floors float)", () => {
|
|
278
|
+
(0, vitest_1.expect)(utils_1.Field.integer(42.9)).toEqual({ type: "Integer", value: 42 });
|
|
279
|
+
});
|
|
280
|
+
(0, vitest_1.it)("builds float field", () => {
|
|
281
|
+
(0, vitest_1.expect)(utils_1.Field.float(3.14)).toEqual({ type: "Float", value: 3.14 });
|
|
282
|
+
});
|
|
283
|
+
(0, vitest_1.it)("builds boolean field", () => {
|
|
284
|
+
(0, vitest_1.expect)(utils_1.Field.boolean(true)).toEqual({ type: "Boolean", value: true });
|
|
285
|
+
(0, vitest_1.expect)(utils_1.Field.boolean(false)).toEqual({ type: "Boolean", value: false });
|
|
286
|
+
});
|
|
287
|
+
(0, vitest_1.it)("builds UUID field", () => {
|
|
288
|
+
const uuid = "550e8400-e29b-41d4-a716-446655440000";
|
|
289
|
+
(0, vitest_1.expect)(utils_1.Field.uuid(uuid)).toEqual({ type: "UUID", value: uuid });
|
|
290
|
+
});
|
|
291
|
+
(0, vitest_1.it)("builds decimal field", () => {
|
|
292
|
+
(0, vitest_1.expect)(utils_1.Field.decimal("123.456")).toEqual({
|
|
293
|
+
type: "Decimal",
|
|
294
|
+
value: "123.456",
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
(0, vitest_1.it)("builds dateTime field from Date", () => {
|
|
298
|
+
const dt = new Date("2024-01-15T10:30:00Z");
|
|
299
|
+
const result = utils_1.Field.dateTime(dt);
|
|
300
|
+
(0, vitest_1.expect)(result.type).toBe("DateTime");
|
|
301
|
+
(0, vitest_1.expect)(result.value).toBe(dt.toISOString());
|
|
302
|
+
});
|
|
303
|
+
(0, vitest_1.it)("builds dateTime field from string", () => {
|
|
304
|
+
const result = utils_1.Field.dateTime("2024-01-15T10:30:00Z");
|
|
305
|
+
(0, vitest_1.expect)(result).toEqual({ type: "DateTime", value: "2024-01-15T10:30:00Z" });
|
|
306
|
+
});
|
|
307
|
+
(0, vitest_1.it)("builds duration field", () => {
|
|
308
|
+
(0, vitest_1.expect)(utils_1.Field.duration(3600000)).toEqual({
|
|
309
|
+
type: "Duration",
|
|
310
|
+
value: 3600000,
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
(0, vitest_1.it)("builds number field", () => {
|
|
314
|
+
(0, vitest_1.expect)(utils_1.Field.number(42)).toEqual({ type: "Number", value: 42 });
|
|
315
|
+
(0, vitest_1.expect)(utils_1.Field.number(3.14)).toEqual({ type: "Number", value: 3.14 });
|
|
316
|
+
});
|
|
317
|
+
(0, vitest_1.it)("builds array field", () => {
|
|
318
|
+
(0, vitest_1.expect)(utils_1.Field.array([1, 2, 3])).toEqual({ type: "Array", value: [1, 2, 3] });
|
|
319
|
+
});
|
|
320
|
+
(0, vitest_1.it)("builds set field", () => {
|
|
321
|
+
(0, vitest_1.expect)(utils_1.Field.set(["a", "b", "c"])).toEqual({
|
|
322
|
+
type: "Set",
|
|
323
|
+
value: ["a", "b", "c"],
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
(0, vitest_1.it)("builds vector field", () => {
|
|
327
|
+
(0, vitest_1.expect)(utils_1.Field.vector([0.1, 0.2, 0.3])).toEqual({
|
|
328
|
+
type: "Vector",
|
|
329
|
+
value: [0.1, 0.2, 0.3],
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
(0, vitest_1.it)("builds object field", () => {
|
|
333
|
+
(0, vitest_1.expect)(utils_1.Field.object({ key: "value" })).toEqual({
|
|
334
|
+
type: "Object",
|
|
335
|
+
value: { key: "value" },
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
(0, vitest_1.it)("builds bytes field from Uint8Array", () => {
|
|
339
|
+
const bytes = new Uint8Array([104, 101, 108, 108, 111]);
|
|
340
|
+
const result = utils_1.Field.bytes(bytes);
|
|
341
|
+
(0, vitest_1.expect)(result.type).toBe("Bytes");
|
|
342
|
+
(0, vitest_1.expect)(typeof result.value).toBe("string"); // Base64 encoded
|
|
343
|
+
});
|
|
344
|
+
(0, vitest_1.it)("builds bytes field from base64 string", () => {
|
|
345
|
+
const result = utils_1.Field.bytes("aGVsbG8=");
|
|
346
|
+
(0, vitest_1.expect)(result).toEqual({ type: "Bytes", value: "aGVsbG8=" });
|
|
347
|
+
});
|
|
348
|
+
(0, vitest_1.it)("builds binary field from Uint8Array", () => {
|
|
349
|
+
const bytes = new Uint8Array([1, 2, 3, 4]);
|
|
350
|
+
const result = utils_1.Field.binary(bytes);
|
|
351
|
+
(0, vitest_1.expect)(result.type).toBe("Binary");
|
|
352
|
+
(0, vitest_1.expect)(typeof result.value).toBe("string"); // Base64 encoded
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
// ============================================================================
|
|
356
|
+
// extractRecord Tests
|
|
357
|
+
// ============================================================================
|
|
358
|
+
(0, vitest_1.describe)("extractRecord", () => {
|
|
359
|
+
(0, vitest_1.it)("extracts all fields from a record", () => {
|
|
360
|
+
const record = {
|
|
361
|
+
id: "user_123",
|
|
362
|
+
name: { type: "String", value: "John Doe" },
|
|
363
|
+
age: { type: "Integer", value: 30 },
|
|
364
|
+
active: { type: "Boolean", value: true },
|
|
365
|
+
};
|
|
366
|
+
const result = (0, utils_1.extractRecord)(record);
|
|
367
|
+
(0, vitest_1.expect)(result).toEqual({
|
|
368
|
+
id: "user_123",
|
|
369
|
+
name: "John Doe",
|
|
370
|
+
age: 30,
|
|
371
|
+
active: true,
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
(0, vitest_1.it)("preserves id field as-is", () => {
|
|
375
|
+
const record = {
|
|
376
|
+
id: "user_123",
|
|
377
|
+
name: { type: "String", value: "John" },
|
|
378
|
+
};
|
|
379
|
+
const result = (0, utils_1.extractRecord)(record);
|
|
380
|
+
(0, vitest_1.expect)(result.id).toBe("user_123");
|
|
381
|
+
});
|
|
382
|
+
(0, vitest_1.it)("handles nested objects", () => {
|
|
383
|
+
const record = {
|
|
384
|
+
user: { type: "Object", value: { name: "John", role: "admin" } },
|
|
385
|
+
tags: { type: "Array", value: ["python", "rust"] },
|
|
386
|
+
};
|
|
387
|
+
const result = (0, utils_1.extractRecord)(record);
|
|
388
|
+
(0, vitest_1.expect)(result.user).toEqual({ name: "John", role: "admin" });
|
|
389
|
+
(0, vitest_1.expect)(result.tags).toEqual(["python", "rust"]);
|
|
390
|
+
});
|
|
391
|
+
(0, vitest_1.it)("handles empty record", () => {
|
|
392
|
+
(0, vitest_1.expect)((0, utils_1.extractRecord)({})).toEqual({});
|
|
393
|
+
});
|
|
394
|
+
(0, vitest_1.it)("handles null/undefined input", () => {
|
|
395
|
+
(0, vitest_1.expect)((0, utils_1.extractRecord)(null)).toBeNull();
|
|
396
|
+
(0, vitest_1.expect)((0, utils_1.extractRecord)(undefined)).toBeUndefined();
|
|
397
|
+
});
|
|
398
|
+
(0, vitest_1.it)("handles non-object input", () => {
|
|
399
|
+
(0, vitest_1.expect)((0, utils_1.extractRecord)("string")).toBe("string");
|
|
400
|
+
(0, vitest_1.expect)((0, utils_1.extractRecord)(123)).toBe(123);
|
|
401
|
+
});
|
|
402
|
+
(0, vitest_1.it)("handles null field values", () => {
|
|
403
|
+
const record = {
|
|
404
|
+
name: { type: "String", value: "John" },
|
|
405
|
+
optional: { type: "Null", value: null },
|
|
406
|
+
};
|
|
407
|
+
const result = (0, utils_1.extractRecord)(record);
|
|
408
|
+
(0, vitest_1.expect)(result.name).toBe("John");
|
|
409
|
+
(0, vitest_1.expect)(result.optional).toBeNull();
|
|
410
|
+
});
|
|
411
|
+
});
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekodb/ekodb-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Official TypeScript/JavaScript client for ekoDB",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc",
|
|
9
|
-
"prepare": "npm run build"
|
|
9
|
+
"prepare": "npm run build",
|
|
10
|
+
"test": "vitest run",
|
|
11
|
+
"test:watch": "vitest"
|
|
10
12
|
},
|
|
11
13
|
"keywords": [
|
|
12
14
|
"ekodb",
|
|
@@ -17,9 +19,10 @@
|
|
|
17
19
|
"author": "ekoDB",
|
|
18
20
|
"license": "MIT",
|
|
19
21
|
"devDependencies": {
|
|
20
|
-
"@types/node": "^
|
|
22
|
+
"@types/node": "^25.0.3",
|
|
21
23
|
"@types/ws": "^8.5.10",
|
|
22
|
-
"typescript": "^5.3.0"
|
|
24
|
+
"typescript": "^5.3.0",
|
|
25
|
+
"vitest": "^3.2.0"
|
|
23
26
|
},
|
|
24
27
|
"dependencies": {
|
|
25
28
|
"@msgpack/msgpack": "^3.0.0",
|
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.
|
|
455
|
+
data.ttl = ttl;
|
|
453
456
|
}
|
|
454
457
|
return this.makeRequest<Record>("POST", `/api/insert/${collection}`, data);
|
|
455
458
|
}
|
|
@@ -491,7 +494,7 @@ export class EkoDBClient {
|
|
|
491
494
|
/**
|
|
492
495
|
* Find a document by ID
|
|
493
496
|
*/
|
|
494
|
-
async
|
|
497
|
+
async findById(collection: string, id: string): Promise<Record> {
|
|
495
498
|
return this.makeRequest<Record>("GET", `/api/find/${collection}/${id}`);
|
|
496
499
|
}
|
|
497
500
|
|
|
@@ -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
|
-
|
|
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