@ekodb/ekodb-client 0.7.0 → 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 +1 -1
- package/dist/client.js +1 -1
- package/dist/query-builder.test.d.ts +4 -0
- package/dist/query-builder.test.js +318 -0
- package/dist/utils.d.ts +3 -3
- package/dist/utils.js +3 -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 +1 -1
- package/src/query-builder.test.ts +404 -0
- package/src/utils.test.ts +506 -0
- package/src/utils.ts +3 -3
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for ekoDB TypeScript client QueryBuilder
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from "vitest";
|
|
6
|
+
import { QueryBuilder, SortOrder } from "./query-builder";
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Basic Tests
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
describe("QueryBuilder basics", () => {
|
|
13
|
+
it("creates empty query builder", () => {
|
|
14
|
+
const qb = new QueryBuilder();
|
|
15
|
+
expect(qb).toBeInstanceOf(QueryBuilder);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("builds empty query", () => {
|
|
19
|
+
const query = new QueryBuilder().build();
|
|
20
|
+
expect(query).toEqual({});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Equality Operators Tests
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
describe("QueryBuilder equality operators", () => {
|
|
29
|
+
it("builds eq filter", () => {
|
|
30
|
+
const query = new QueryBuilder().eq("status", "active").build();
|
|
31
|
+
|
|
32
|
+
expect(query.filter).toEqual({
|
|
33
|
+
type: "Condition",
|
|
34
|
+
content: {
|
|
35
|
+
field: "status",
|
|
36
|
+
operator: "Eq",
|
|
37
|
+
value: "active",
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("builds ne filter", () => {
|
|
43
|
+
const query = new QueryBuilder().ne("status", "deleted").build();
|
|
44
|
+
|
|
45
|
+
expect(query.filter.content.operator).toBe("Ne");
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// ============================================================================
|
|
50
|
+
// Comparison Operators Tests
|
|
51
|
+
// ============================================================================
|
|
52
|
+
|
|
53
|
+
describe("QueryBuilder comparison operators", () => {
|
|
54
|
+
it("builds gt filter", () => {
|
|
55
|
+
const query = new QueryBuilder().gt("age", 18).build();
|
|
56
|
+
|
|
57
|
+
expect(query.filter.content.operator).toBe("Gt");
|
|
58
|
+
expect(query.filter.content.value).toBe(18);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("builds gte filter", () => {
|
|
62
|
+
const query = new QueryBuilder().gte("score", 80).build();
|
|
63
|
+
|
|
64
|
+
expect(query.filter.content.operator).toBe("Gte");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("builds lt filter", () => {
|
|
68
|
+
const query = new QueryBuilder().lt("price", 100.5).build();
|
|
69
|
+
|
|
70
|
+
expect(query.filter.content.operator).toBe("Lt");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("builds lte filter", () => {
|
|
74
|
+
const query = new QueryBuilder().lte("count", 1000).build();
|
|
75
|
+
|
|
76
|
+
expect(query.filter.content.operator).toBe("Lte");
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// ============================================================================
|
|
81
|
+
// Array Operators Tests
|
|
82
|
+
// ============================================================================
|
|
83
|
+
|
|
84
|
+
describe("QueryBuilder array operators", () => {
|
|
85
|
+
it("builds in filter", () => {
|
|
86
|
+
const query = new QueryBuilder()
|
|
87
|
+
.in("status", ["active", "pending"])
|
|
88
|
+
.build();
|
|
89
|
+
|
|
90
|
+
expect(query.filter.content.operator).toBe("In");
|
|
91
|
+
expect(query.filter.content.value).toEqual(["active", "pending"]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("builds nin filter", () => {
|
|
95
|
+
const query = new QueryBuilder()
|
|
96
|
+
.nin("role", ["blocked", "deleted"])
|
|
97
|
+
.build();
|
|
98
|
+
|
|
99
|
+
expect(query.filter.content.operator).toBe("NotIn");
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// String Operators Tests
|
|
105
|
+
// ============================================================================
|
|
106
|
+
|
|
107
|
+
describe("QueryBuilder string operators", () => {
|
|
108
|
+
it("builds contains filter", () => {
|
|
109
|
+
const query = new QueryBuilder().contains("email", "@example.com").build();
|
|
110
|
+
|
|
111
|
+
expect(query.filter.content.operator).toBe("Contains");
|
|
112
|
+
expect(query.filter.content.value).toBe("@example.com");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("builds startsWith filter", () => {
|
|
116
|
+
const query = new QueryBuilder().startsWith("name", "John").build();
|
|
117
|
+
|
|
118
|
+
expect(query.filter.content.operator).toBe("StartsWith");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("builds endsWith filter", () => {
|
|
122
|
+
const query = new QueryBuilder().endsWith("filename", ".pdf").build();
|
|
123
|
+
|
|
124
|
+
expect(query.filter.content.operator).toBe("EndsWith");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("builds regex filter", () => {
|
|
128
|
+
const query = new QueryBuilder().regex("phone", "^\\+1").build();
|
|
129
|
+
|
|
130
|
+
expect(query.filter.content.operator).toBe("Regex");
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// ============================================================================
|
|
135
|
+
// Logical Operators Tests
|
|
136
|
+
// ============================================================================
|
|
137
|
+
|
|
138
|
+
describe("QueryBuilder logical operators", () => {
|
|
139
|
+
it("builds and filter", () => {
|
|
140
|
+
const conditions = [
|
|
141
|
+
{
|
|
142
|
+
type: "Condition",
|
|
143
|
+
content: { field: "status", operator: "Eq", value: "active" },
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
type: "Condition",
|
|
147
|
+
content: { field: "age", operator: "Gt", value: 18 },
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
const query = new QueryBuilder().and(conditions).build();
|
|
152
|
+
|
|
153
|
+
expect(query.filter.type).toBe("Logical");
|
|
154
|
+
expect(query.filter.content.operator).toBe("And");
|
|
155
|
+
expect(query.filter.content.expressions).toHaveLength(2);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("builds or filter", () => {
|
|
159
|
+
const conditions = [
|
|
160
|
+
{
|
|
161
|
+
type: "Condition",
|
|
162
|
+
content: { field: "role", operator: "Eq", value: "admin" },
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
type: "Condition",
|
|
166
|
+
content: { field: "role", operator: "Eq", value: "super_admin" },
|
|
167
|
+
},
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
const query = new QueryBuilder().or(conditions).build();
|
|
171
|
+
|
|
172
|
+
expect(query.filter.content.operator).toBe("Or");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("builds not filter", () => {
|
|
176
|
+
const condition = {
|
|
177
|
+
type: "Condition",
|
|
178
|
+
content: { field: "deleted", operator: "Eq", value: true },
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const query = new QueryBuilder().not(condition).build();
|
|
182
|
+
|
|
183
|
+
expect(query.filter.content.operator).toBe("Not");
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// ============================================================================
|
|
188
|
+
// Multiple Filters (Auto AND) Tests
|
|
189
|
+
// ============================================================================
|
|
190
|
+
|
|
191
|
+
describe("QueryBuilder multiple filters", () => {
|
|
192
|
+
it("combines multiple filters with AND logic", () => {
|
|
193
|
+
const query = new QueryBuilder()
|
|
194
|
+
.eq("status", "active")
|
|
195
|
+
.gt("age", 18)
|
|
196
|
+
.contains("email", "@company.com")
|
|
197
|
+
.build();
|
|
198
|
+
|
|
199
|
+
expect(query.filter.type).toBe("Logical");
|
|
200
|
+
expect(query.filter.content.operator).toBe("And");
|
|
201
|
+
expect(query.filter.content.expressions).toHaveLength(3);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// ============================================================================
|
|
206
|
+
// Sorting Tests
|
|
207
|
+
// ============================================================================
|
|
208
|
+
|
|
209
|
+
describe("QueryBuilder sorting", () => {
|
|
210
|
+
it("builds ascending sort", () => {
|
|
211
|
+
const query = new QueryBuilder().sortAsc("name").build();
|
|
212
|
+
|
|
213
|
+
expect(query.sort).toHaveLength(1);
|
|
214
|
+
expect(query.sort![0].field).toBe("name");
|
|
215
|
+
expect(query.sort![0].ascending).toBe(true);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("builds descending sort", () => {
|
|
219
|
+
const query = new QueryBuilder().sortDesc("created_at").build();
|
|
220
|
+
|
|
221
|
+
expect(query.sort![0].ascending).toBe(false);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("builds multiple sorts", () => {
|
|
225
|
+
const query = new QueryBuilder()
|
|
226
|
+
.sortDesc("created_at")
|
|
227
|
+
.sortAsc("name")
|
|
228
|
+
.build();
|
|
229
|
+
|
|
230
|
+
expect(query.sort).toHaveLength(2);
|
|
231
|
+
expect(query.sort![0].field).toBe("created_at");
|
|
232
|
+
expect(query.sort![0].ascending).toBe(false);
|
|
233
|
+
expect(query.sort![1].field).toBe("name");
|
|
234
|
+
expect(query.sort![1].ascending).toBe(true);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// ============================================================================
|
|
239
|
+
// Pagination Tests
|
|
240
|
+
// ============================================================================
|
|
241
|
+
|
|
242
|
+
describe("QueryBuilder pagination", () => {
|
|
243
|
+
it("builds limit", () => {
|
|
244
|
+
const query = new QueryBuilder().limit(10).build();
|
|
245
|
+
|
|
246
|
+
expect(query.limit).toBe(10);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("builds skip", () => {
|
|
250
|
+
const query = new QueryBuilder().skip(20).build();
|
|
251
|
+
|
|
252
|
+
expect(query.skip).toBe(20);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("builds page (convenience method)", () => {
|
|
256
|
+
// Page 2 with 20 items per page = skip 40
|
|
257
|
+
const query = new QueryBuilder().page(2, 20).build();
|
|
258
|
+
|
|
259
|
+
expect(query.limit).toBe(20);
|
|
260
|
+
expect(query.skip).toBe(40);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("builds page 0", () => {
|
|
264
|
+
const query = new QueryBuilder().page(0, 10).build();
|
|
265
|
+
|
|
266
|
+
expect(query.skip).toBe(0);
|
|
267
|
+
expect(query.limit).toBe(10);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// ============================================================================
|
|
272
|
+
// Join Tests
|
|
273
|
+
// ============================================================================
|
|
274
|
+
|
|
275
|
+
describe("QueryBuilder join", () => {
|
|
276
|
+
it("builds join configuration", () => {
|
|
277
|
+
const joinConfig = {
|
|
278
|
+
collections: ["users"],
|
|
279
|
+
local_field: "user_id",
|
|
280
|
+
foreign_field: "id",
|
|
281
|
+
as_field: "user",
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const query = new QueryBuilder().join(joinConfig).build();
|
|
285
|
+
|
|
286
|
+
expect(query.join).toEqual(joinConfig);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// ============================================================================
|
|
291
|
+
// Bypass Flags Tests
|
|
292
|
+
// ============================================================================
|
|
293
|
+
|
|
294
|
+
describe("QueryBuilder bypass flags", () => {
|
|
295
|
+
it("builds bypass_cache true", () => {
|
|
296
|
+
const query = new QueryBuilder().bypassCache(true).build();
|
|
297
|
+
|
|
298
|
+
expect(query.bypass_cache).toBe(true);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("builds bypass_cache false (not included)", () => {
|
|
302
|
+
const query = new QueryBuilder().bypassCache(false).build();
|
|
303
|
+
|
|
304
|
+
expect(query.bypass_cache).toBeUndefined();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("builds bypass_cache default (true)", () => {
|
|
308
|
+
const query = new QueryBuilder().bypassCache().build();
|
|
309
|
+
|
|
310
|
+
expect(query.bypass_cache).toBe(true);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("builds bypass_ripple", () => {
|
|
314
|
+
const query = new QueryBuilder().bypassRipple(true).build();
|
|
315
|
+
|
|
316
|
+
expect(query.bypass_ripple).toBe(true);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// ============================================================================
|
|
321
|
+
// Chaining Tests
|
|
322
|
+
// ============================================================================
|
|
323
|
+
|
|
324
|
+
describe("QueryBuilder chaining", () => {
|
|
325
|
+
it("supports full method chaining", () => {
|
|
326
|
+
const query = new QueryBuilder()
|
|
327
|
+
.eq("status", "active")
|
|
328
|
+
.gt("age", 18)
|
|
329
|
+
.sortDesc("created_at")
|
|
330
|
+
.sortAsc("name")
|
|
331
|
+
.limit(10)
|
|
332
|
+
.skip(20)
|
|
333
|
+
.bypassCache(true)
|
|
334
|
+
.build();
|
|
335
|
+
|
|
336
|
+
// Check filter exists
|
|
337
|
+
expect(query.filter).toBeDefined();
|
|
338
|
+
|
|
339
|
+
// Check sort exists
|
|
340
|
+
expect(query.sort).toHaveLength(2);
|
|
341
|
+
|
|
342
|
+
// Check pagination
|
|
343
|
+
expect(query.limit).toBe(10);
|
|
344
|
+
expect(query.skip).toBe(20);
|
|
345
|
+
|
|
346
|
+
// Check bypass flag
|
|
347
|
+
expect(query.bypass_cache).toBe(true);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it("returns this for method chaining", () => {
|
|
351
|
+
const qb = new QueryBuilder();
|
|
352
|
+
|
|
353
|
+
expect(qb.eq("a", 1)).toBe(qb);
|
|
354
|
+
expect(qb.ne("b", 2)).toBe(qb);
|
|
355
|
+
expect(qb.gt("c", 3)).toBe(qb);
|
|
356
|
+
expect(qb.gte("d", 4)).toBe(qb);
|
|
357
|
+
expect(qb.lt("e", 5)).toBe(qb);
|
|
358
|
+
expect(qb.lte("f", 6)).toBe(qb);
|
|
359
|
+
expect(qb.in("g", [7])).toBe(qb);
|
|
360
|
+
expect(qb.nin("h", [8])).toBe(qb);
|
|
361
|
+
expect(qb.contains("i", "j")).toBe(qb);
|
|
362
|
+
expect(qb.startsWith("k", "l")).toBe(qb);
|
|
363
|
+
expect(qb.endsWith("m", "n")).toBe(qb);
|
|
364
|
+
expect(qb.regex("o", "p")).toBe(qb);
|
|
365
|
+
expect(qb.sortAsc("q")).toBe(qb);
|
|
366
|
+
expect(qb.sortDesc("r")).toBe(qb);
|
|
367
|
+
expect(qb.limit(1)).toBe(qb);
|
|
368
|
+
expect(qb.skip(0)).toBe(qb);
|
|
369
|
+
expect(qb.bypassCache()).toBe(qb);
|
|
370
|
+
expect(qb.bypassRipple()).toBe(qb);
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// ============================================================================
|
|
375
|
+
// Raw Filter Tests
|
|
376
|
+
// ============================================================================
|
|
377
|
+
|
|
378
|
+
describe("QueryBuilder rawFilter", () => {
|
|
379
|
+
it("adds raw filter expression", () => {
|
|
380
|
+
const rawFilter = {
|
|
381
|
+
type: "Condition",
|
|
382
|
+
content: {
|
|
383
|
+
field: "custom",
|
|
384
|
+
operator: "CustomOp",
|
|
385
|
+
value: "custom_value",
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const query = new QueryBuilder().rawFilter(rawFilter).build();
|
|
390
|
+
|
|
391
|
+
expect(query.filter).toEqual(rawFilter);
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// ============================================================================
|
|
396
|
+
// SortOrder Enum Tests
|
|
397
|
+
// ============================================================================
|
|
398
|
+
|
|
399
|
+
describe("SortOrder enum", () => {
|
|
400
|
+
it("has correct values", () => {
|
|
401
|
+
expect(SortOrder.Asc).toBe("asc");
|
|
402
|
+
expect(SortOrder.Desc).toBe("desc");
|
|
403
|
+
});
|
|
404
|
+
});
|