@onchaindb/sdk 0.4.5 → 1.0.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.
Files changed (97) hide show
  1. package/.claude/settings.local.json +10 -2
  2. package/README.md +422 -355
  3. package/dist/batch.d.ts +1 -10
  4. package/dist/batch.d.ts.map +1 -1
  5. package/dist/batch.js +4 -26
  6. package/dist/batch.js.map +1 -1
  7. package/dist/client.d.ts +29 -43
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +198 -323
  10. package/dist/client.js.map +1 -1
  11. package/dist/database.d.ts +14 -131
  12. package/dist/database.d.ts.map +1 -1
  13. package/dist/database.js +35 -131
  14. package/dist/database.js.map +1 -1
  15. package/dist/index.d.ts +6 -9
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1 -15
  18. package/dist/index.js.map +1 -1
  19. package/dist/query-sdk/ConditionBuilder.d.ts +3 -11
  20. package/dist/query-sdk/ConditionBuilder.d.ts.map +1 -1
  21. package/dist/query-sdk/ConditionBuilder.js +10 -48
  22. package/dist/query-sdk/ConditionBuilder.js.map +1 -1
  23. package/dist/query-sdk/NestedBuilders.d.ts +33 -30
  24. package/dist/query-sdk/NestedBuilders.d.ts.map +1 -1
  25. package/dist/query-sdk/NestedBuilders.js +46 -43
  26. package/dist/query-sdk/NestedBuilders.js.map +1 -1
  27. package/dist/query-sdk/QueryBuilder.d.ts +4 -2
  28. package/dist/query-sdk/QueryBuilder.d.ts.map +1 -1
  29. package/dist/query-sdk/QueryBuilder.js +47 -169
  30. package/dist/query-sdk/QueryBuilder.js.map +1 -1
  31. package/dist/query-sdk/QueryResult.d.ts +0 -38
  32. package/dist/query-sdk/QueryResult.d.ts.map +1 -1
  33. package/dist/query-sdk/QueryResult.js +1 -227
  34. package/dist/query-sdk/QueryResult.js.map +1 -1
  35. package/dist/query-sdk/index.d.ts +1 -1
  36. package/dist/query-sdk/index.d.ts.map +1 -1
  37. package/dist/query-sdk/index.js.map +1 -1
  38. package/dist/query-sdk/operators.d.ts +32 -28
  39. package/dist/query-sdk/operators.d.ts.map +1 -1
  40. package/dist/query-sdk/operators.js +45 -155
  41. package/dist/query-sdk/operators.js.map +1 -1
  42. package/dist/types.d.ts +153 -1
  43. package/dist/types.d.ts.map +1 -1
  44. package/dist/types.js.map +1 -1
  45. package/jest.config.js +4 -0
  46. package/package.json +1 -1
  47. package/skills.md +0 -1
  48. package/src/client.ts +242 -745
  49. package/src/database.ts +70 -493
  50. package/src/index.ts +40 -193
  51. package/src/query-sdk/ConditionBuilder.ts +37 -89
  52. package/src/query-sdk/NestedBuilders.ts +90 -92
  53. package/src/query-sdk/QueryBuilder.ts +59 -218
  54. package/src/query-sdk/QueryResult.ts +4 -330
  55. package/src/query-sdk/README.md +214 -583
  56. package/src/query-sdk/index.ts +1 -1
  57. package/src/query-sdk/operators.ts +91 -200
  58. package/src/query-sdk/tests/FieldConditionBuilder.test.ts +70 -71
  59. package/src/query-sdk/tests/LogicalOperator.test.ts +43 -82
  60. package/src/query-sdk/tests/NestedBuilders.test.ts +229 -309
  61. package/src/query-sdk/tests/QueryBuilder.test.ts +5 -5
  62. package/src/query-sdk/tests/QueryResult.test.ts +41 -435
  63. package/src/query-sdk/tests/comprehensive.test.ts +4 -185
  64. package/src/tests/client-requests.test.ts +280 -0
  65. package/src/tests/client-validation.test.ts +80 -0
  66. package/src/types.ts +229 -8
  67. package/src/batch.ts +0 -257
  68. package/src/query-sdk/dist/ConditionBuilder.d.ts +0 -22
  69. package/src/query-sdk/dist/ConditionBuilder.js +0 -90
  70. package/src/query-sdk/dist/FieldConditionBuilder.d.ts +0 -1
  71. package/src/query-sdk/dist/FieldConditionBuilder.js +0 -6
  72. package/src/query-sdk/dist/NestedBuilders.d.ts +0 -43
  73. package/src/query-sdk/dist/NestedBuilders.js +0 -144
  74. package/src/query-sdk/dist/OnChainDB.d.ts +0 -19
  75. package/src/query-sdk/dist/OnChainDB.js +0 -123
  76. package/src/query-sdk/dist/QueryBuilder.d.ts +0 -70
  77. package/src/query-sdk/dist/QueryBuilder.js +0 -295
  78. package/src/query-sdk/dist/QueryResult.d.ts +0 -52
  79. package/src/query-sdk/dist/QueryResult.js +0 -293
  80. package/src/query-sdk/dist/SelectionBuilder.d.ts +0 -20
  81. package/src/query-sdk/dist/SelectionBuilder.js +0 -80
  82. package/src/query-sdk/dist/adapters/HttpClientAdapter.d.ts +0 -27
  83. package/src/query-sdk/dist/adapters/HttpClientAdapter.js +0 -170
  84. package/src/query-sdk/dist/index.d.ts +0 -36
  85. package/src/query-sdk/dist/index.js +0 -27
  86. package/src/query-sdk/dist/operators.d.ts +0 -56
  87. package/src/query-sdk/dist/operators.js +0 -289
  88. package/src/query-sdk/dist/tests/setup.d.ts +0 -15
  89. package/src/query-sdk/dist/tests/setup.js +0 -46
  90. package/src/query-sdk/jest.config.js +0 -25
  91. package/src/query-sdk/package.json +0 -46
  92. package/src/query-sdk/tests/aggregations.test.ts +0 -653
  93. package/src/query-sdk/tests/integration.test.ts +0 -608
  94. package/src/query-sdk/tests/operators.test.ts +0 -327
  95. package/src/query-sdk/tests/unit.test.ts +0 -794
  96. package/src/query-sdk/tsconfig.json +0 -26
  97. package/src/query-sdk/yarn.lock +0 -3092
@@ -1,794 +0,0 @@
1
- // Comprehensive unit tests for TypeScript SDK
2
- import {
3
- FieldConditionBuilder,
4
- LogicalOperator,
5
- ConditionBuilder,
6
- NestedConditionBuilder,
7
- NestedFieldConditionBuilder,
8
- QueryBuilder,
9
- QueryResult,
10
- createQueryResult
11
- } from '../index';
12
-
13
- // Test framework helpers
14
- interface TestCase {
15
- name: string;
16
- test: () => void;
17
- }
18
-
19
- class TestRunner {
20
- private tests: TestCase[] = [];
21
- private passed = 0;
22
- private failed = 0;
23
-
24
- add(name: string, test: () => void) {
25
- this.tests.push({ name, test });
26
- }
27
-
28
- run() {
29
- console.log(`🧪 Running ${this.tests.length} unit tests...\n`);
30
-
31
- for (const testCase of this.tests) {
32
- try {
33
- testCase.test();
34
- console.log(`✅ ${testCase.name}`);
35
- this.passed++;
36
- } catch (error) {
37
- console.error(`❌ ${testCase.name}: ${error instanceof Error ? error.message : error}`);
38
- this.failed++;
39
- }
40
- }
41
-
42
- console.log(`\n📊 Test Results: ${this.passed} passed, ${this.failed} failed`);
43
- if (this.failed > 0) {
44
- process.exit(1);
45
- }
46
- }
47
- }
48
-
49
- // Assertion helpers
50
- function assertEqual<T>(actual: T, expected: T, message?: string) {
51
- const actualStr = JSON.stringify(actual, null, 2);
52
- const expectedStr = JSON.stringify(expected, null, 2);
53
- if (actualStr !== expectedStr) {
54
- throw new Error(`${message || 'Values not equal'}\nExpected: ${expectedStr}\nActual: ${actualStr}`);
55
- }
56
- }
57
-
58
- function assertTrue(condition: boolean, message?: string) {
59
- if (!condition) {
60
- throw new Error(message || 'Condition is false');
61
- }
62
- }
63
-
64
- function assertFalse(condition: boolean, message?: string) {
65
- if (condition) {
66
- throw new Error(message || 'Condition is true');
67
- }
68
- }
69
-
70
- function assertThrows(fn: () => void, message?: string) {
71
- try {
72
- fn();
73
- throw new Error(message || 'Expected function to throw');
74
- } catch (error) {
75
- // Expected - function threw as required
76
- }
77
- }
78
-
79
- // Initialize test runner
80
- const runner = new TestRunner();
81
-
82
- // ===== FIELD CONDITION BUILDER TESTS =====
83
-
84
- runner.add('FieldConditionBuilder - Basic operators', () => {
85
- const builder = new FieldConditionBuilder('testField');
86
-
87
- assertEqual(builder.equals('value').operator, 'is');
88
- assertEqual(builder.notEquals('value').operator, 'isNot');
89
- assertEqual(builder.greaterThan(10).operator, 'greaterThan');
90
- assertEqual(builder.lessThan(10).operator, 'lessThan');
91
- assertEqual(builder.greaterThanOrEqual(10).operator, 'greaterThanOrEqual');
92
- assertEqual(builder.lessThanOrEqual(10).operator, 'lessThanOrEqual');
93
- });
94
-
95
- runner.add('FieldConditionBuilder - String operators', () => {
96
- const builder = new FieldConditionBuilder('text');
97
-
98
- assertEqual(builder.contains('sub').operator, 'includes');
99
- assertEqual(builder.startsWith('pre').operator, 'startsWith');
100
- assertEqual(builder.endsWith('suf').operator, 'endsWith');
101
- assertEqual(builder.regExpMatches('.*test.*').operator, 'regExpMatches');
102
- assertEqual(builder.includesCaseInsensitive('TEST').operator, 'includesCaseInsensitive');
103
- });
104
-
105
- runner.add('FieldConditionBuilder - Array operators', () => {
106
- const builder = new FieldConditionBuilder('items');
107
-
108
- const inCondition = builder.in([1, 2, 3]);
109
- assertEqual(inCondition.operator, 'in');
110
- assertEqual(inCondition.value, [1, 2, 3]);
111
-
112
- const notInCondition = builder.notIn(['a', 'b']);
113
- assertEqual(notInCondition.operator, 'notIn');
114
- assertEqual(notInCondition.value, ['a', 'b']);
115
- });
116
-
117
- runner.add('FieldConditionBuilder - Null/existence operators', () => {
118
- const builder = new FieldConditionBuilder('optional');
119
-
120
- assertEqual(builder.exists().value, true);
121
- assertEqual(builder.notExists().value, false);
122
- assertEqual(builder.isNull().value, true);
123
- assertEqual(builder.isNotNull().value, false);
124
- });
125
-
126
- runner.add('FieldConditionBuilder - Numeric operators', () => {
127
- const builder = new FieldConditionBuilder('score');
128
-
129
- const betweenCondition = builder.between(10, 20);
130
- assertEqual(betweenCondition.operator, 'between');
131
- assertEqual(betweenCondition.value, { min: 10, max: 20 });
132
-
133
- // Test additional real operators
134
- const regexCondition = builder.regExpMatches('^test.*');
135
- assertEqual(regexCondition.operator, 'regExpMatches');
136
- });
137
-
138
- runner.add('FieldConditionBuilder - String operators', () => {
139
- const builder = new FieldConditionBuilder('text');
140
-
141
- assertEqual(builder.contains('test').operator, 'includes');
142
- assertEqual(builder.startsWith('prefix').operator, 'startsWith');
143
- assertEqual(builder.endsWith('suffix').operator, 'endsWith');
144
- assertEqual(builder.regExpMatches('^test.*').operator, 'regExpMatches');
145
- assertEqual(builder.includesCaseInsensitive('Test').operator, 'includesCaseInsensitive');
146
- assertEqual(builder.startsWithCaseInsensitive('Test').operator, 'startsWithCaseInsensitive');
147
- assertEqual(builder.endsWithCaseInsensitive('Test').operator, 'endsWithCaseInsensitive');
148
- });
149
-
150
- runner.add('FieldConditionBuilder - IP address operators', () => {
151
- const builder = new FieldConditionBuilder('ipAddress');
152
-
153
- assertEqual(builder.isLocalIp().operator, 'isLocalIp');
154
- assertEqual(builder.isExternalIp().operator, 'isExternalIp');
155
- assertEqual(builder.inCountry('US').operator, 'inCountry');
156
- assertEqual(builder.cidr('192.168.1.0/24').operator, 'CIDR');
157
- assertEqual(builder.b64('dGVzdA==').operator, 'b64');
158
- assertEqual(builder.inDataset('malware_ips').operator, 'inDataset');
159
- });
160
-
161
- runner.add('FieldConditionBuilder - Misc operators', () => {
162
- const builder = new FieldConditionBuilder('data');
163
-
164
- const b64Condition = builder.b64('dGVzdA==');
165
- assertEqual(b64Condition.operator, 'b64');
166
- assertEqual(b64Condition.value, 'dGVzdA==');
167
-
168
- const countryCondition = builder.inCountry('US');
169
- assertEqual(countryCondition.operator, 'inCountry');
170
- assertEqual(countryCondition.value, 'US');
171
-
172
- const datasetCondition = builder.inDataset('malware_ips');
173
- assertEqual(datasetCondition.operator, 'inDataset');
174
- assertEqual(datasetCondition.value, 'malware_ips');
175
-
176
- const cidrCondition = builder.cidr('192.168.1.0/24');
177
- assertEqual(cidrCondition.operator, 'CIDR');
178
- assertEqual(cidrCondition.value, '192.168.1.0/24');
179
- });
180
-
181
- runner.add('FieldConditionBuilder - Boolean operators', () => {
182
- const builder = new FieldConditionBuilder('isActive');
183
-
184
- const trueCondition = builder.isTrue();
185
- assertEqual(trueCondition.operator, 'is');
186
- assertEqual(trueCondition.value, true);
187
-
188
- const falseCondition = builder.isFalse();
189
- assertEqual(falseCondition.operator, 'is');
190
- assertEqual(falseCondition.value, false);
191
- });
192
-
193
- runner.add('FieldConditionBuilder - Number operators', () => {
194
- const builder = new FieldConditionBuilder('amount');
195
-
196
- assertEqual(builder.greaterThan(100).operator, 'greaterThan');
197
- assertEqual(builder.lessThan(500).operator, 'lessThan');
198
- assertEqual(builder.greaterThanOrEqual(100).operator, 'greaterThanOrEqual');
199
- assertEqual(builder.lessThanOrEqual(500).operator, 'lessThanOrEqual');
200
- assertEqual(builder.between(100, 500).operator, 'betweenOp');
201
- });
202
-
203
- runner.add('FieldConditionBuilder - Base operators', () => {
204
- const builder = new FieldConditionBuilder('value');
205
-
206
- assertEqual(builder.equals('test').operator, 'is');
207
- assertEqual(builder.notEquals('test').operator, 'isNot');
208
- assertEqual(builder.in(['a', 'b', 'c']).operator, 'in');
209
- assertEqual(builder.notIn(['x', 'y', 'z']).operator, 'notIn');
210
- assertEqual(builder.isNull().operator, 'isNull');
211
- assertEqual(builder.isNotNull().operator, 'isNull');
212
- assertEqual(builder.exists().operator, 'exists');
213
- assertEqual(builder.notExists().operator, 'exists');
214
- // Only test boolean convenience methods that use base operators
215
- assertEqual(builder.isTrue().operator, 'is');
216
- assertEqual(builder.isFalse().operator, 'is');
217
- });
218
-
219
- runner.add('FieldConditionBuilder - Boolean convenience methods', () => {
220
- const builder = new FieldConditionBuilder('flag');
221
-
222
- const trueCondition = builder.isTrue();
223
- assertEqual(trueCondition.operator, 'is');
224
- assertEqual(trueCondition.value, true);
225
-
226
- const falseCondition = builder.isFalse();
227
- assertEqual(falseCondition.operator, 'is');
228
- assertEqual(falseCondition.value, false);
229
- });
230
-
231
- // ===== LOGICAL OPERATOR TESTS =====
232
-
233
- runner.add('LogicalOperator - Single condition', () => {
234
- const condition = new FieldConditionBuilder('name').equals('John');
235
- const logicalOp = LogicalOperator.Condition(condition);
236
-
237
- assertEqual(logicalOp.type, 'condition');
238
- assertEqual(logicalOp.condition?.field, 'name');
239
- assertEqual(logicalOp.condition?.operator, 'is');
240
- assertEqual(logicalOp.condition?.value, 'John');
241
- });
242
-
243
- runner.add('LogicalOperator - AND operation', () => {
244
- const condition1 = LogicalOperator.Condition(new FieldConditionBuilder('name').equals('John'));
245
- const condition2 = LogicalOperator.Condition(new FieldConditionBuilder('age').greaterThan(25));
246
-
247
- const andOp = LogicalOperator.And([condition1, condition2]);
248
- assertEqual(andOp.type, 'and');
249
- assertEqual(andOp.conditions.length, 2);
250
- });
251
-
252
- runner.add('LogicalOperator - OR operation', () => {
253
- const condition1 = LogicalOperator.Condition(new FieldConditionBuilder('status').equals('active'));
254
- const condition2 = LogicalOperator.Condition(new FieldConditionBuilder('status').equals('pending'));
255
-
256
- const orOp = LogicalOperator.Or([condition1, condition2]);
257
- assertEqual(orOp.type, 'or');
258
- assertEqual(orOp.conditions.length, 2);
259
- });
260
-
261
- runner.add('LogicalOperator - NOT operation', () => {
262
- const condition = LogicalOperator.Condition(new FieldConditionBuilder('deleted').isTrue());
263
- const notOp = LogicalOperator.Not([condition]);
264
-
265
- assertEqual(notOp.type, 'not');
266
- assertEqual(notOp.conditions.length, 1);
267
- });
268
-
269
- // ===== DOT NOTATION TESTS =====
270
-
271
- runner.add('createNestedQuery - Simple field', () => {
272
- const result = createNestedQuery('name', 'is', 'John');
273
- assertEqual(result, { name: { is: 'John' } });
274
- });
275
-
276
- runner.add('createNestedQuery - Nested field', () => {
277
- const result = createNestedQuery('user.profile.name', 'is', 'John');
278
- const expected = {
279
- user: {
280
- profile: {
281
- name: {
282
- is: 'John'
283
- }
284
- }
285
- }
286
- };
287
- assertEqual(result, expected);
288
- });
289
-
290
- runner.add('createNestedQuery - Deep nesting', () => {
291
- const result = createNestedQuery('a.b.c.d.e', 'contains', 'test');
292
- const expected = {
293
- a: {
294
- b: {
295
- c: {
296
- d: {
297
- e: {
298
- contains: 'test'
299
- }
300
- }
301
- }
302
- }
303
- }
304
- };
305
- assertEqual(result, expected);
306
- });
307
-
308
- runner.add('LogicalOperator.toComposable - Dot notation integration', () => {
309
- const condition = new FieldConditionBuilder('user.profile.bio').equals('Developer');
310
- const logicalOp = LogicalOperator.Condition(condition);
311
- const composable = logicalOp.toComposable();
312
-
313
- const expected = {
314
- user: {
315
- profile: {
316
- bio: {
317
- is: 'Developer'
318
- }
319
- }
320
- }
321
- };
322
- assertEqual(composable, expected);
323
- });
324
-
325
- // ===== NESTED BUILDER TESTS =====
326
-
327
- runner.add('NestedConditionBuilder - Basic usage', () => {
328
- const nestedBuilder = new NestedConditionBuilder('user');
329
- const fieldBuilder = nestedBuilder.field('name');
330
-
331
- assertTrue(fieldBuilder instanceof NestedFieldConditionBuilder);
332
- });
333
-
334
- runner.add('NestedFieldConditionBuilder - Path construction', () => {
335
- const nestedBuilder = new NestedConditionBuilder('user');
336
- const condition = nestedBuilder.field('profile').field('bio').equals('Developer');
337
-
338
- assertEqual(condition.field, 'user.profile.bio');
339
- assertEqual(condition.operator, 'is');
340
- assertEqual(condition.value, 'Developer');
341
- });
342
-
343
- runner.add('NestedFieldConditionBuilder - All operators available', () => {
344
- const nestedBuilder = new NestedConditionBuilder('user');
345
- const fieldBuilder = nestedBuilder.field('data');
346
-
347
- // Test a few key operators to ensure they work
348
- assertEqual(fieldBuilder.contains('test').operator, 'includes');
349
- assertEqual(fieldBuilder.greaterThan(10).operator, 'greaterThan');
350
- assertEqual(fieldBuilder.isNull().operator, 'isNull');
351
- assertEqual(fieldBuilder.dateToday().operator, 'dateToday');
352
- });
353
-
354
- runner.add('NestedConditionBuilder - Logical groups', () => {
355
- const nestedBuilder = new NestedConditionBuilder('user');
356
-
357
- const andGroup = nestedBuilder.andGroup(builder => [
358
- LogicalOperator.Condition(builder.field('name').equals('John')),
359
- LogicalOperator.Condition(builder.field('age').greaterThan(25))
360
- ]);
361
-
362
- assertEqual(andGroup.type, 'and');
363
- assertEqual(andGroup.conditions.length, 2);
364
- });
365
-
366
- // ===== CONDITION BUILDER TESTS =====
367
-
368
- runner.add('ConditionBuilder - Basic functionality', () => {
369
- const builder = new ConditionBuilder();
370
-
371
- assertTrue(builder.isEmpty());
372
- assertEqual(builder.getConditionCount(), 0);
373
- });
374
-
375
- runner.add('ConditionBuilder - Adding conditions', () => {
376
- const builder = new ConditionBuilder();
377
- const condition = LogicalOperator.Condition(new FieldConditionBuilder('name').equals('John'));
378
-
379
- builder.addCondition(condition);
380
- assertEqual(builder.getConditionCount(), 1);
381
- assertFalse(builder.isEmpty());
382
- });
383
-
384
- runner.add('ConditionBuilder - Logical groups', () => {
385
- const builder = new ConditionBuilder();
386
-
387
- const andGroup = builder.andGroup(b => [
388
- LogicalOperator.Condition(new FieldConditionBuilder('name').equals('John')),
389
- LogicalOperator.Condition(new FieldConditionBuilder('age').greaterThan(25))
390
- ]);
391
-
392
- assertEqual(andGroup.type, 'and');
393
- assertEqual(andGroup.conditions.length, 2);
394
- });
395
-
396
- runner.add('ConditionBuilder - Nested conditions', () => {
397
- const builder = new ConditionBuilder();
398
-
399
- const nestedCondition = builder.nested('user', nested =>
400
- LogicalOperator.Condition(nested.field('name').equals('John'))
401
- );
402
-
403
- assertEqual(nestedCondition.type, 'condition');
404
- });
405
-
406
- runner.add('ConditionBuilder - Clear functionality', () => {
407
- const builder = new ConditionBuilder();
408
- builder.addCondition(LogicalOperator.Condition(new FieldConditionBuilder('test').equals('value')));
409
-
410
- assertEqual(builder.getConditionCount(), 1);
411
- builder.clear();
412
- assertEqual(builder.getConditionCount(), 0);
413
- assertTrue(builder.isEmpty());
414
- });
415
-
416
- // ===== QUERY BUILDER TESTS =====
417
-
418
- runner.add('QueryBuilder - Basic construction', () => {
419
- const builder = new QueryBuilder();
420
- assertFalse(builder.isValid()); // No conditions yet
421
- });
422
-
423
- runner.add('QueryBuilder - Method chaining', () => {
424
- const mockClient = {
425
- async post() { return { data: { records: [], total: 0 } }; }
426
- };
427
-
428
- const builder = new QueryBuilder()
429
- .withHttpClient(mockClient)
430
- .withServerUrl('http://localhost:3000')
431
- .collection('users')
432
- .limit(10)
433
- .offset(0);
434
-
435
- const rawQuery = builder.buildRawQuery();
436
- assertEqual(rawQuery.limit, 10);
437
- assertEqual(rawQuery.offset, 0);
438
- });
439
-
440
- runner.add('QueryBuilder - Where field conditions', () => {
441
- const builder = new QueryBuilder();
442
- const result = builder.whereField('name').equals('John');
443
-
444
- assertTrue(result === builder); // Returns self for chaining
445
- assertTrue(builder.isValid()); // Now has conditions
446
- });
447
-
448
- runner.add('QueryBuilder - Complex find conditions', () => {
449
- const builder = new QueryBuilder();
450
-
451
- builder.find(conditionBuilder =>
452
- conditionBuilder.andGroup(b => [
453
- LogicalOperator.Condition(new FieldConditionBuilder('name').equals('John')),
454
- LogicalOperator.Condition(new FieldConditionBuilder('age').greaterThan(25))
455
- ])
456
- );
457
-
458
- assertTrue(builder.isValid());
459
- const rawQuery = builder.buildRawQuery();
460
- assertTrue(typeof rawQuery.find === 'object');
461
- });
462
-
463
- runner.add('QueryBuilder - Selection methods', () => {
464
- const builder = new QueryBuilder();
465
-
466
- // Test selectFields
467
- builder.selectFields(['name', 'email']);
468
- assertTrue(builder.isValid());
469
-
470
- // Test selectAll
471
- const builder2 = new QueryBuilder();
472
- builder2.selectAll();
473
- assertTrue(builder2.isValid());
474
- });
475
-
476
- runner.add('QueryBuilder - Clone functionality', () => {
477
- const original = new QueryBuilder()
478
- .collection('users')
479
- .limit(10)
480
- .whereField('name').equals('John');
481
-
482
- const cloned = original.clone();
483
-
484
- // Verify they're separate instances
485
- assertTrue(cloned !== original);
486
-
487
- // Verify cloned data
488
- assertEqual(cloned.buildRawQuery().limit, 10);
489
- });
490
-
491
- // ===== QUERY RESULT TESTS =====
492
-
493
- runner.add('QueryResult - Basic functionality', () => {
494
- const data = [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }];
495
- const result = createQueryResult(data);
496
-
497
- assertEqual(result.len(), 2);
498
- assertEqual(result.length(), 2);
499
- assertFalse(result.isEmpty());
500
- assertEqual(result.first()?.name, 'John');
501
- assertEqual(result.last()?.name, 'Jane');
502
- assertEqual(result.get(0)?.id, 1);
503
- });
504
-
505
- runner.add('QueryResult - Array input validation', () => {
506
- // Test with single item
507
- const single = createQueryResult({ id: 1, name: 'Test' } as any);
508
- assertEqual(single.len(), 1);
509
-
510
- // Test with empty array
511
- const empty = createQueryResult([]);
512
- assertTrue(empty.isEmpty());
513
- assertEqual(empty.len(), 0);
514
- });
515
-
516
- runner.add('QueryResult - Iteration methods', () => {
517
- const data = [
518
- { id: 1, name: 'John', active: true },
519
- { id: 2, name: 'Jane', active: false },
520
- { id: 3, name: 'Bob', active: true }
521
- ];
522
- const result = createQueryResult(data);
523
-
524
- // Test any/some
525
- assertTrue(result.any(item => item.active));
526
- assertTrue(result.any(item => item.name === 'Jane'));
527
-
528
- // Test all/every
529
- assertFalse(result.all(item => item.active));
530
- assertTrue(result.all(item => typeof item.id === 'number'));
531
-
532
- // Test find
533
- const found = result.find(item => item.name === 'Jane');
534
- assertEqual(found?.id, 2);
535
-
536
- // Test filter
537
- const active = result.filter(item => item.active);
538
- assertEqual(active.length, 2);
539
-
540
- // Test map
541
- const names = result.map(item => item.name);
542
- assertEqual(names, ['John', 'Jane', 'Bob']);
543
- });
544
-
545
- runner.add('QueryResult - Pluck methods', () => {
546
- const data = [
547
- { id: 1, name: 'John', score: 95.5 },
548
- { id: 2, name: 'Jane', score: 87 },
549
- { id: 3, name: 'Bob', score: null }
550
- ];
551
- const result = createQueryResult(data);
552
-
553
- // Test pluck
554
- const names = result.pluck('name');
555
- assertEqual(names, ['John', 'Jane', 'Bob']);
556
-
557
- // Test pluckStrings
558
- const stringNames = result.pluckStrings('name');
559
- assertEqual(stringNames, ['John', 'Jane', 'Bob']);
560
-
561
- // Test pluckNumbers
562
- const scores = result.pluckNumbers('score');
563
- assertEqual(scores, [95.5, 87, null]);
564
- });
565
-
566
- runner.add('QueryResult - Numeric summary', () => {
567
- const data = [
568
- { score: 85 },
569
- { score: 92 },
570
- { score: 78 },
571
- { score: 95 }
572
- ];
573
- const result = createQueryResult(data);
574
-
575
- const summary = result.summarizeNumeric('score');
576
-
577
- assertTrue(summary !== null);
578
- assertEqual(summary!.count, 4);
579
- assertEqual(summary!.sum, 350);
580
- assertEqual(summary!.mean, 87.5);
581
- assertEqual(summary!.min, 78);
582
- assertEqual(summary!.max, 95);
583
-
584
- // Test with no numeric data
585
- const textData = [{ name: 'John' }, { name: 'Jane' }];
586
- const textResult = createQueryResult(textData);
587
- const textSummary = textResult.summarizeNumeric('name');
588
- assertEqual(textSummary, null);
589
- });
590
-
591
- runner.add('QueryResult - Sorting', () => {
592
- const data = [
593
- { name: 'Charlie', age: 30 },
594
- { name: 'Alice', age: 25 },
595
- { name: 'Bob', age: 35 }
596
- ];
597
- const result = createQueryResult(data);
598
-
599
- // Test single field sort
600
- const byName = result.sortBy('name');
601
- assertEqual(byName[0].name, 'Alice');
602
- assertEqual(byName[1].name, 'Bob');
603
- assertEqual(byName[2].name, 'Charlie');
604
-
605
- // Test descending sort
606
- const byAgeDesc = result.sortBy('age', false);
607
- assertEqual(byAgeDesc[0].age, 35);
608
- assertEqual(byAgeDesc[2].age, 25);
609
-
610
- // Test multi-field sort
611
- const data2 = [
612
- { dept: 'B', name: 'Charlie' },
613
- { dept: 'A', name: 'Bob' },
614
- { dept: 'A', name: 'Alice' }
615
- ];
616
- const result2 = createQueryResult(data2);
617
- const sorted = result2.sortByMultiple([
618
- { field: 'dept', ascending: true },
619
- { field: 'name', ascending: true }
620
- ]);
621
- assertEqual(sorted[0].name, 'Alice');
622
- assertEqual(sorted[1].name, 'Bob');
623
- assertEqual(sorted[2].name, 'Charlie');
624
- });
625
-
626
- runner.add('QueryResult - Pagination and chunking', () => {
627
- const data = Array.from({ length: 10 }, (_, i) => ({ id: i + 1 }));
628
- const result = createQueryResult(data);
629
-
630
- // Test pagination
631
- const page1 = result.paginate(1, 3);
632
- assertEqual(page1.length, 3);
633
- assertEqual(page1[0].id, 1);
634
-
635
- const page2 = result.paginate(2, 3);
636
- assertEqual(page2[0].id, 4);
637
-
638
- // Test chunking
639
- const chunks = result.chunk(3);
640
- assertEqual(chunks.length, 4); // 10 items, chunks of 3 = 4 chunks
641
- assertEqual(chunks[0].length, 3);
642
- assertEqual(chunks[3].length, 1); // Last chunk has remainder
643
- });
644
-
645
- runner.add('QueryResult - Distinct and unique', () => {
646
- const data = [
647
- { category: 'A', value: 1 },
648
- { category: 'B', value: 2 },
649
- { category: 'A', value: 3 },
650
- { category: 'C', value: 4 }
651
- ];
652
- const result = createQueryResult(data);
653
-
654
- const distinctByCategory = result.distinctBy('category');
655
- assertEqual(distinctByCategory.length, 3);
656
-
657
- // Test unique (for primitive arrays)
658
- const numbers = createQueryResult([1, 2, 2, 3, 3, 4]);
659
- const uniqueNumbers = numbers.unique();
660
- assertEqual(uniqueNumbers.length, 4);
661
- });
662
-
663
- runner.add('QueryResult - Statistical functions', () => {
664
- const data = [
665
- { category: 'A', active: true },
666
- { category: 'B', active: false },
667
- { category: 'A', active: true },
668
- { category: 'C', active: true }
669
- ];
670
- const result = createQueryResult(data);
671
-
672
- // Test count with predicate
673
- const activeCount = result.count(item => item.active);
674
- assertEqual(activeCount, 3);
675
-
676
- // Test countBy
677
- const categoryCounts = result.countBy('category');
678
- assertEqual(categoryCounts['A'], 2);
679
- assertEqual(categoryCounts['B'], 1);
680
- assertEqual(categoryCounts['C'], 1);
681
- });
682
-
683
- runner.add('QueryResult - Joins', () => {
684
- const users = createQueryResult([
685
- { id: 1, name: 'John', deptId: 10 },
686
- { id: 2, name: 'Jane', deptId: 20 }
687
- ]);
688
-
689
- const departments = createQueryResult([
690
- { id: 10, name: 'Engineering' },
691
- { id: 20, name: 'Design' }
692
- ]);
693
-
694
- // Test inner join
695
- const innerJoined = users.innerJoin(departments, 'deptId', 'id');
696
- assertEqual(innerJoined.length, 2);
697
- assertEqual(innerJoined[0].name, 'Engineering'); // dept name
698
-
699
- // Test left join
700
- const leftJoined = users.leftJoin(departments, 'deptId', 'id');
701
- assertEqual(leftJoined.length, 2);
702
- });
703
-
704
- runner.add('QueryResult - Export functions', () => {
705
- const data = [
706
- { id: 1, name: 'John', active: true },
707
- { id: 2, name: 'Jane', active: false }
708
- ];
709
- const result = createQueryResult(data);
710
-
711
- // Test CSV export
712
- const csv = result.toCsv();
713
- const lines = csv.split('\n');
714
- assertEqual(lines.length, 3); // header + 2 rows
715
- assertTrue(lines[0].includes('id'));
716
- assertTrue(lines[0].includes('name'));
717
- assertTrue(lines[0].includes('active'));
718
-
719
- // Test JSON export
720
- const json = result.toJSON();
721
- const parsed = JSON.parse(json);
722
- assertEqual(parsed.length, 2);
723
- assertEqual(parsed[0].name, 'John');
724
-
725
- // Test array conversion
726
- const array = result.toArray();
727
- assertEqual(array.length, 2);
728
- assertTrue(Array.isArray(array));
729
- });
730
-
731
- runner.add('QueryResult - Nested field access', () => {
732
- const data = [
733
- { user: { profile: { name: 'John', age: 30 } } },
734
- { user: { profile: { name: 'Jane', age: 25 } } }
735
- ];
736
- const result = createQueryResult(data);
737
-
738
- // Test nested pluck
739
- const names = result.pluck('user.profile.name');
740
- assertEqual(names, ['John', 'Jane']);
741
-
742
- // Test nested sort
743
- const sorted = result.sortBy('user.profile.age');
744
- assertEqual(sorted[0].user.profile.name, 'Jane');
745
- assertEqual(sorted[1].user.profile.name, 'John');
746
-
747
- // Test nested groupBy
748
- const grouped = result.groupBy('user.profile.name');
749
- assertTrue('John' in grouped);
750
- assertTrue('Jane' in grouped);
751
- });
752
-
753
- // ===== ERROR HANDLING AND EDGE CASES =====
754
-
755
- runner.add('Error handling - Invalid conditions', () => {
756
- assertThrows(() => {
757
- const builder = new ConditionBuilder();
758
- builder.buildSingle(); // Should throw - no conditions
759
- });
760
- });
761
-
762
- runner.add('Error handling - Missing required data', () => {
763
- assertThrows(() => {
764
- const logicalOp = new LogicalOperator('condition' as any, [], undefined);
765
- logicalOp.toComposable(); // Should throw - no condition provided
766
- });
767
- });
768
-
769
- runner.add('Edge cases - Empty and null data', () => {
770
- const empty = createQueryResult([]);
771
-
772
- assertEqual(empty.len(), 0);
773
- assertTrue(empty.isEmpty());
774
- assertEqual(empty.first(), undefined);
775
- assertEqual(empty.last(), undefined);
776
-
777
- const summary = empty.summarizeNumeric('nonexistent');
778
- assertEqual(summary, null);
779
- });
780
-
781
- runner.add('Edge cases - Nested field access with missing data', () => {
782
- const data = [
783
- { user: { name: 'John' } }, // Missing profile.age
784
- { user: { profile: { age: 25 } } } // Missing name
785
- ];
786
- const result = createQueryResult(data);
787
-
788
- const ages = result.pluck('user.profile.age');
789
- assertEqual(ages[0], undefined);
790
- assertEqual(ages[1], 25);
791
- });
792
-
793
- // Run all tests
794
- runner.run();