@onchaindb/sdk 0.4.0 → 0.4.2
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/.DS_Store +0 -0
- package/.claude/settings.local.json +8 -0
- package/.gitignore +5 -0
- package/.idea/.gitignore +5 -0
- package/.idea/compiler.xml +6 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/jsLinters/eslint.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/prettier.xml +7 -0
- package/.idea/sdk.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/.idea/workspace.xml +257 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +11 -3
- package/dist/client.js.map +1 -1
- package/dist/database.d.ts +0 -20
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +0 -40
- package/dist/database.js.map +1 -1
- package/dist/query-sdk/tests/setup.d.ts +16 -0
- package/dist/query-sdk/tests/setup.d.ts.map +1 -0
- package/dist/query-sdk/tests/setup.js +49 -0
- package/dist/query-sdk/tests/setup.js.map +1 -0
- package/examples/basic-usage.ts +136 -0
- package/examples/blob-upload-example.ts +140 -0
- package/examples/collection-schema-example.ts +304 -0
- package/examples/server-side-joins.ts +201 -0
- package/examples/tweet-self-joins-example.ts +352 -0
- package/package-lock.json +3823 -0
- package/package.json +1 -1
- package/skills.md +1096 -0
- package/src/.env +1 -0
- package/src/batch.d.ts +121 -0
- package/src/batch.js +205 -0
- package/src/batch.ts +257 -0
- package/src/client.ts +1856 -0
- package/src/database.d.ts +268 -0
- package/src/database.js +294 -0
- package/src/database.ts +695 -0
- package/src/index.d.ts +160 -0
- package/src/index.js +186 -0
- package/src/index.ts +253 -0
- package/src/query-sdk/ConditionBuilder.ts +103 -0
- package/src/query-sdk/FieldConditionBuilder.ts +2 -0
- package/src/query-sdk/NestedBuilders.ts +186 -0
- package/src/query-sdk/OnChainDB.ts +294 -0
- package/src/query-sdk/QueryBuilder.ts +1191 -0
- package/src/query-sdk/QueryResult.ts +375 -0
- package/src/query-sdk/README.md +866 -0
- package/src/query-sdk/SelectionBuilder.ts +94 -0
- package/src/query-sdk/adapters/HttpClientAdapter.ts +249 -0
- package/src/query-sdk/dist/ConditionBuilder.d.ts +22 -0
- package/src/query-sdk/dist/ConditionBuilder.js +90 -0
- package/src/query-sdk/dist/FieldConditionBuilder.d.ts +1 -0
- package/src/query-sdk/dist/FieldConditionBuilder.js +6 -0
- package/src/query-sdk/dist/NestedBuilders.d.ts +43 -0
- package/src/query-sdk/dist/NestedBuilders.js +144 -0
- package/src/query-sdk/dist/OnChainDB.d.ts +19 -0
- package/src/query-sdk/dist/OnChainDB.js +123 -0
- package/src/query-sdk/dist/QueryBuilder.d.ts +70 -0
- package/src/query-sdk/dist/QueryBuilder.js +295 -0
- package/src/query-sdk/dist/QueryResult.d.ts +52 -0
- package/src/query-sdk/dist/QueryResult.js +293 -0
- package/src/query-sdk/dist/SelectionBuilder.d.ts +20 -0
- package/src/query-sdk/dist/SelectionBuilder.js +80 -0
- package/src/query-sdk/dist/adapters/HttpClientAdapter.d.ts +27 -0
- package/src/query-sdk/dist/adapters/HttpClientAdapter.js +170 -0
- package/src/query-sdk/dist/index.d.ts +36 -0
- package/src/query-sdk/dist/index.js +27 -0
- package/src/query-sdk/dist/operators.d.ts +56 -0
- package/src/query-sdk/dist/operators.js +289 -0
- package/src/query-sdk/dist/tests/setup.d.ts +15 -0
- package/src/query-sdk/dist/tests/setup.js +46 -0
- package/src/query-sdk/index.ts +59 -0
- package/src/query-sdk/jest.config.js +25 -0
- package/src/query-sdk/operators.ts +335 -0
- package/src/query-sdk/package.json +46 -0
- package/src/query-sdk/tests/FieldConditionBuilder.test.ts +84 -0
- package/src/query-sdk/tests/LogicalOperator.test.ts +85 -0
- package/src/query-sdk/tests/NestedBuilders.test.ts +321 -0
- package/src/query-sdk/tests/QueryBuilder.test.ts +348 -0
- package/src/query-sdk/tests/QueryResult.test.ts +464 -0
- package/src/query-sdk/tests/aggregations.test.ts +653 -0
- package/src/query-sdk/tests/comprehensive.test.ts +279 -0
- package/src/query-sdk/tests/integration.test.ts +608 -0
- package/src/query-sdk/tests/operators.test.ts +327 -0
- package/src/query-sdk/tests/setup.ts +59 -0
- package/src/query-sdk/tests/unit.test.ts +794 -0
- package/src/query-sdk/tsconfig.json +26 -0
- package/src/query-sdk/yarn.lock +3092 -0
- package/src/types.d.ts +131 -0
- package/src/types.js +46 -0
- package/src/types.ts +534 -0
- package/src/x402/index.ts +12 -0
- package/src/x402/types.ts +250 -0
- package/src/x402/utils.ts +332 -0
- package/tsconfig.json +20 -0
- package/yarn.lock +2309 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { createQueryResult, QueryResult } from '../QueryResult';
|
|
2
|
+
|
|
3
|
+
describe('QueryResult', () => {
|
|
4
|
+
const testData = [
|
|
5
|
+
{ id: 1, name: 'Alice', age: 30, department: 'Engineering', salary: 75000, active: true },
|
|
6
|
+
{ id: 2, name: 'Bob', age: 25, department: 'Design', salary: 65000, active: true },
|
|
7
|
+
{ id: 3, name: 'Carol', age: 35, department: 'Engineering', salary: 85000, active: false },
|
|
8
|
+
{ id: 4, name: 'David', age: 28, department: 'Marketing', salary: 55000, active: true },
|
|
9
|
+
{ id: 5, name: 'Eve', age: 32, department: 'Engineering', salary: 95000, active: true }
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
let result: QueryResult<any>;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
result = createQueryResult(testData);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('Basic operations', () => {
|
|
19
|
+
test('should return correct length', () => {
|
|
20
|
+
expect(result.len()).toBe(5);
|
|
21
|
+
expect(result.length()).toBe(5);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('should identify empty results', () => {
|
|
25
|
+
const emptyResult = createQueryResult([]);
|
|
26
|
+
expect(emptyResult.isEmpty()).toBe(true);
|
|
27
|
+
expect(result.isEmpty()).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('should return first and last items', () => {
|
|
31
|
+
expect(result.first()?.name).toBe('Alice');
|
|
32
|
+
expect(result.last()?.name).toBe('Eve');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('should get item by index', () => {
|
|
36
|
+
expect(result.get(0)?.name).toBe('Alice');
|
|
37
|
+
expect(result.get(2)?.name).toBe('Carol');
|
|
38
|
+
expect(result.get(10)).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('should handle single item input', () => {
|
|
42
|
+
const singleResult = createQueryResult({ id: 1, name: 'Test' } as any);
|
|
43
|
+
expect(singleResult.len()).toBe(1);
|
|
44
|
+
expect(singleResult.first()?.name).toBe('Test');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('Iteration methods', () => {
|
|
49
|
+
test('any should work correctly', () => {
|
|
50
|
+
expect(result.any(item => item.age > 30)).toBe(true);
|
|
51
|
+
expect(result.any(item => item.age > 100)).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('all should work correctly', () => {
|
|
55
|
+
expect(result.all(item => item.id > 0)).toBe(true);
|
|
56
|
+
expect(result.all(item => item.active)).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('find should return correct item', () => {
|
|
60
|
+
const found = result.find(item => item.name === 'Carol');
|
|
61
|
+
expect(found?.id).toBe(3);
|
|
62
|
+
expect(result.find(item => item.name === 'Unknown')).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('filter should return correct items', () => {
|
|
66
|
+
const activeUsers = result.filter(item => item.active);
|
|
67
|
+
expect(activeUsers).toHaveLength(4);
|
|
68
|
+
|
|
69
|
+
const engineers = result.filter(item => item.department === 'Engineering');
|
|
70
|
+
expect(engineers).toHaveLength(3);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('map should transform items correctly', () => {
|
|
74
|
+
const names = result.map(item => item.name);
|
|
75
|
+
expect(names).toEqual(['Alice', 'Bob', 'Carol', 'David', 'Eve']);
|
|
76
|
+
|
|
77
|
+
const ages = result.map(item => item.age);
|
|
78
|
+
expect(ages).toEqual([30, 25, 35, 28, 32]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('forEach should iterate over all items', () => {
|
|
82
|
+
const mockCallback = jest.fn();
|
|
83
|
+
result.forEach(mockCallback);
|
|
84
|
+
expect(mockCallback).toHaveBeenCalledTimes(5);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('reduce should accumulate correctly', () => {
|
|
88
|
+
const totalAge = result.reduce((sum, item) => sum + item.age, 0);
|
|
89
|
+
expect(totalAge).toBe(150);
|
|
90
|
+
|
|
91
|
+
const nameList = result.reduce((names, item) => names + item.name + ',', '');
|
|
92
|
+
expect(nameList).toBe('Alice,Bob,Carol,David,Eve,');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('Data extraction', () => {
|
|
97
|
+
test('pluck should extract field values', () => {
|
|
98
|
+
const names = result.pluck('name');
|
|
99
|
+
expect(names).toEqual(['Alice', 'Bob', 'Carol', 'David', 'Eve']);
|
|
100
|
+
|
|
101
|
+
const ages = result.pluck('age');
|
|
102
|
+
expect(ages).toEqual([30, 25, 35, 28, 32]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('pluckStrings should extract and convert to strings', () => {
|
|
106
|
+
const ageStrings = result.pluckStrings('age');
|
|
107
|
+
expect(ageStrings).toEqual(['30', '25', '35', '28', '32']);
|
|
108
|
+
|
|
109
|
+
const names = result.pluckStrings('name');
|
|
110
|
+
expect(names).toEqual(['Alice', 'Bob', 'Carol', 'David', 'Eve']);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('pluckNumbers should extract and convert to numbers', () => {
|
|
114
|
+
const ages = result.pluckNumbers('age');
|
|
115
|
+
expect(ages).toEqual([30, 25, 35, 28, 32]);
|
|
116
|
+
|
|
117
|
+
const salaries = result.pluckNumbers('salary');
|
|
118
|
+
expect(salaries).toEqual([75000, 65000, 85000, 55000, 95000]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('pluckNumbers should handle non-numeric values', () => {
|
|
122
|
+
const testDataWithNulls = [
|
|
123
|
+
{ id: 1, score: 85 },
|
|
124
|
+
{ id: 2, score: null },
|
|
125
|
+
{ id: 3, score: '90' },
|
|
126
|
+
{ id: 4, score: 'invalid' }
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
const resultWithNulls = createQueryResult(testDataWithNulls);
|
|
130
|
+
const scores = resultWithNulls.pluckNumbers('score');
|
|
131
|
+
expect(scores).toEqual([85, null, 90, null]);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('Numeric summary', () => {
|
|
136
|
+
test('should calculate numeric summary correctly', () => {
|
|
137
|
+
const ageSummary = result.summarizeNumeric('age');
|
|
138
|
+
|
|
139
|
+
expect(ageSummary).not.toBeNull();
|
|
140
|
+
expect(ageSummary!.count).toBe(5);
|
|
141
|
+
expect(ageSummary!.sum).toBe(150);
|
|
142
|
+
expect(ageSummary!.mean).toBe(30);
|
|
143
|
+
expect(ageSummary!.min).toBe(25);
|
|
144
|
+
expect(ageSummary!.max).toBe(35);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('should calculate median correctly', () => {
|
|
148
|
+
const ageSummary = result.summarizeNumeric('age');
|
|
149
|
+
expect(ageSummary!.median).toBe(30); // [25, 28, 30, 32, 35]
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('should calculate standard deviation correctly', () => {
|
|
153
|
+
const ageSummary = result.summarizeNumeric('age');
|
|
154
|
+
expect(ageSummary!.standardDeviation).toBeCloseTo(3.74, 2);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('should return null for non-numeric fields', () => {
|
|
158
|
+
const nameSummary = result.summarizeNumeric('name');
|
|
159
|
+
expect(nameSummary).toBeNull();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('should handle empty data', () => {
|
|
163
|
+
const emptyResult = createQueryResult([]);
|
|
164
|
+
const summary = emptyResult.summarizeNumeric('age');
|
|
165
|
+
expect(summary).toBeNull();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('Grouping and aggregation', () => {
|
|
170
|
+
test('groupBy should group items correctly', () => {
|
|
171
|
+
const byDepartment = result.groupBy('department');
|
|
172
|
+
|
|
173
|
+
expect(byDepartment['Engineering']).toHaveLength(3);
|
|
174
|
+
expect(byDepartment['Design']).toHaveLength(1);
|
|
175
|
+
expect(byDepartment['Marketing']).toHaveLength(1);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('countBy should count values correctly', () => {
|
|
179
|
+
const departmentCounts = result.countBy('department');
|
|
180
|
+
|
|
181
|
+
expect(departmentCounts['Engineering']).toBe(3);
|
|
182
|
+
expect(departmentCounts['Design']).toBe(1);
|
|
183
|
+
expect(departmentCounts['Marketing']).toBe(1);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('count with predicate should work correctly', () => {
|
|
187
|
+
const activeCount = result.count(item => item.active);
|
|
188
|
+
expect(activeCount).toBe(4);
|
|
189
|
+
|
|
190
|
+
const engineerCount = result.count(item => item.department === 'Engineering');
|
|
191
|
+
expect(engineerCount).toBe(3);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('count without predicate should return total count', () => {
|
|
195
|
+
expect(result.count()).toBe(5);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('Sorting', () => {
|
|
200
|
+
test('sortBy should sort correctly', () => {
|
|
201
|
+
const sortedByAge = result.sortBy('age');
|
|
202
|
+
const ages = sortedByAge.map(item => item.age);
|
|
203
|
+
expect(ages).toEqual([25, 28, 30, 32, 35]);
|
|
204
|
+
|
|
205
|
+
const sortedByName = result.sortBy('name');
|
|
206
|
+
const names = sortedByName.map(item => item.name);
|
|
207
|
+
expect(names).toEqual(['Alice', 'Bob', 'Carol', 'David', 'Eve']);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('sortBy should handle descending order', () => {
|
|
211
|
+
const sortedByAgeDesc = result.sortBy('age', false);
|
|
212
|
+
const ages = sortedByAgeDesc.map(item => item.age);
|
|
213
|
+
expect(ages).toEqual([35, 32, 30, 28, 25]);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('sortByMultiple should handle multiple sort fields', () => {
|
|
217
|
+
const sorted = result.sortByMultiple([
|
|
218
|
+
{ field: 'department', ascending: true },
|
|
219
|
+
{ field: 'salary', ascending: false }
|
|
220
|
+
]);
|
|
221
|
+
|
|
222
|
+
// Should sort by department first, then by salary descending
|
|
223
|
+
expect(sorted[0].department).toBe('Design');
|
|
224
|
+
expect(sorted[1].department).toBe('Engineering');
|
|
225
|
+
expect(sorted[1].salary).toBe(95000); // Highest paid engineer first
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('sortBy should handle null values', () => {
|
|
229
|
+
const dataWithNulls = [
|
|
230
|
+
{ id: 1, score: 10 },
|
|
231
|
+
{ id: 2, score: null },
|
|
232
|
+
{ id: 3, score: 5 }
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
const resultWithNulls = createQueryResult(dataWithNulls);
|
|
236
|
+
const sorted = resultWithNulls.sortBy('score');
|
|
237
|
+
|
|
238
|
+
// Null values should sort first in ascending order
|
|
239
|
+
expect(sorted[0].score).toBeNull();
|
|
240
|
+
expect(sorted[1].score).toBe(5);
|
|
241
|
+
expect(sorted[2].score).toBe(10);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe('Pagination and chunking', () => {
|
|
246
|
+
test('paginate should return correct page', () => {
|
|
247
|
+
const page1 = result.paginate(1, 2);
|
|
248
|
+
expect(page1).toHaveLength(2);
|
|
249
|
+
expect(page1[0].name).toBe('Alice');
|
|
250
|
+
expect(page1[1].name).toBe('Bob');
|
|
251
|
+
|
|
252
|
+
const page2 = result.paginate(2, 2);
|
|
253
|
+
expect(page2).toHaveLength(2);
|
|
254
|
+
expect(page2[0].name).toBe('Carol');
|
|
255
|
+
expect(page2[1].name).toBe('David');
|
|
256
|
+
|
|
257
|
+
const page3 = result.paginate(3, 2);
|
|
258
|
+
expect(page3).toHaveLength(1);
|
|
259
|
+
expect(page3[0].name).toBe('Eve');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('chunk should split data into chunks', () => {
|
|
263
|
+
const chunks = result.chunk(2);
|
|
264
|
+
expect(chunks).toHaveLength(3);
|
|
265
|
+
expect(chunks[0]).toHaveLength(2);
|
|
266
|
+
expect(chunks[1]).toHaveLength(2);
|
|
267
|
+
expect(chunks[2]).toHaveLength(1);
|
|
268
|
+
|
|
269
|
+
expect(chunks[0][0].name).toBe('Alice');
|
|
270
|
+
expect(chunks[2][0].name).toBe('Eve');
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe('Distinct and unique', () => {
|
|
275
|
+
test('distinctBy should return unique values', () => {
|
|
276
|
+
const uniqueDepartments = result.distinctBy('department');
|
|
277
|
+
const departments = uniqueDepartments.map(item => item.department);
|
|
278
|
+
expect(departments).toEqual(['Engineering', 'Design', 'Marketing']);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test('unique should remove duplicates', () => {
|
|
282
|
+
const duplicateData = [1, 2, 2, 3, 3, 3, 4];
|
|
283
|
+
const duplicateResult = createQueryResult(duplicateData);
|
|
284
|
+
const unique = duplicateResult.unique();
|
|
285
|
+
expect(unique).toEqual([1, 2, 3, 4]);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe('Joining', () => {
|
|
290
|
+
const departments = createQueryResult([
|
|
291
|
+
{ id: 'Engineering', budget: 100000, manager: 'John' },
|
|
292
|
+
{ id: 'Design', budget: 75000, manager: 'Jane' },
|
|
293
|
+
{ id: 'Marketing', budget: 50000, manager: 'Mike' }
|
|
294
|
+
]);
|
|
295
|
+
|
|
296
|
+
test('innerJoin should join matching records', () => {
|
|
297
|
+
const joined = result.innerJoin(departments, 'department', 'id');
|
|
298
|
+
|
|
299
|
+
expect(joined).toHaveLength(5);
|
|
300
|
+
expect(joined[0].budget).toBe(100000); // Alice from Engineering
|
|
301
|
+
expect(joined[1].budget).toBe(75000); // Bob from Design
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('leftJoin should include all left records', () => {
|
|
305
|
+
const leftJoined = result.leftJoin(departments, 'department', 'id');
|
|
306
|
+
|
|
307
|
+
expect(leftJoined).toHaveLength(5);
|
|
308
|
+
// All original records should be present
|
|
309
|
+
expect(leftJoined.map(r => r.name)).toEqual(['Alice', 'Bob', 'Carol', 'David', 'Eve']);
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
describe('Export functions', () => {
|
|
314
|
+
test('toArray should return array copy', () => {
|
|
315
|
+
const array = result.toArray();
|
|
316
|
+
expect(Array.isArray(array)).toBe(true);
|
|
317
|
+
expect(array).toHaveLength(5);
|
|
318
|
+
expect(array[0]).toEqual(testData[0]);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test('toJSON should return JSON string', () => {
|
|
322
|
+
const json = result.toJSON();
|
|
323
|
+
const parsed = JSON.parse(json);
|
|
324
|
+
expect(parsed).toEqual(testData);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test('toCsv should generate CSV format', () => {
|
|
328
|
+
const csv = result.toCsv();
|
|
329
|
+
const lines = csv.split('\n');
|
|
330
|
+
|
|
331
|
+
expect(lines.length).toBe(6); // Header + 5 data rows
|
|
332
|
+
expect(lines[0]).toContain('id,name,age,department,salary,active');
|
|
333
|
+
expect(lines[1]).toContain('1,Alice,30,Engineering,75000,true');
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test('toCsv should handle custom delimiter', () => {
|
|
337
|
+
const tsv = result.toCsv('\t');
|
|
338
|
+
expect(tsv).toContain('\t');
|
|
339
|
+
expect(tsv.split('\n')[0]).toContain('id\tname\tage');
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test('toCsv should escape values with delimiters', () => {
|
|
343
|
+
const dataWithCommas = [
|
|
344
|
+
{ name: 'Smith, John', description: 'A person, who works' }
|
|
345
|
+
];
|
|
346
|
+
const resultWithCommas = createQueryResult(dataWithCommas);
|
|
347
|
+
const csv = resultWithCommas.toCsv();
|
|
348
|
+
|
|
349
|
+
expect(csv).toContain('"Smith, John"');
|
|
350
|
+
expect(csv).toContain('"A person, who works"');
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
describe('Nested field access', () => {
|
|
355
|
+
const nestedData = [
|
|
356
|
+
{
|
|
357
|
+
id: 1,
|
|
358
|
+
user: {
|
|
359
|
+
profile: {
|
|
360
|
+
name: 'Alice',
|
|
361
|
+
age: 30
|
|
362
|
+
},
|
|
363
|
+
settings: {
|
|
364
|
+
theme: 'dark'
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
id: 2,
|
|
370
|
+
user: {
|
|
371
|
+
profile: {
|
|
372
|
+
name: 'Bob',
|
|
373
|
+
age: 25
|
|
374
|
+
},
|
|
375
|
+
settings: {
|
|
376
|
+
theme: 'light'
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
];
|
|
381
|
+
|
|
382
|
+
test('should handle nested field plucking', () => {
|
|
383
|
+
const nestedResult = createQueryResult(nestedData);
|
|
384
|
+
|
|
385
|
+
const names = nestedResult.pluck('user.profile.name');
|
|
386
|
+
expect(names).toEqual(['Alice', 'Bob']);
|
|
387
|
+
|
|
388
|
+
const ages = nestedResult.pluck('user.profile.age');
|
|
389
|
+
expect(ages).toEqual([30, 25]);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test('should handle nested field sorting', () => {
|
|
393
|
+
const nestedResult = createQueryResult(nestedData);
|
|
394
|
+
const sorted = nestedResult.sortBy('user.profile.age');
|
|
395
|
+
|
|
396
|
+
expect(sorted[0].user.profile.name).toBe('Bob');
|
|
397
|
+
expect(sorted[1].user.profile.name).toBe('Alice');
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test('should handle nested field grouping', () => {
|
|
401
|
+
const nestedResult = createQueryResult(nestedData);
|
|
402
|
+
const grouped = nestedResult.groupBy('user.settings.theme');
|
|
403
|
+
|
|
404
|
+
expect(grouped['dark']).toHaveLength(1);
|
|
405
|
+
expect(grouped['light']).toHaveLength(1);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
test('should handle missing nested fields gracefully', () => {
|
|
409
|
+
const dataWithMissing = [
|
|
410
|
+
{ id: 1, user: { profile: { name: 'Alice' } } },
|
|
411
|
+
{ id: 2, user: { name: 'Bob' } }, // Missing profile
|
|
412
|
+
{ id: 3 } // Missing user entirely
|
|
413
|
+
];
|
|
414
|
+
|
|
415
|
+
const resultWithMissing = createQueryResult(dataWithMissing);
|
|
416
|
+
const names = resultWithMissing.pluck('user.profile.name');
|
|
417
|
+
|
|
418
|
+
expect(names).toEqual(['Alice', undefined, undefined]);
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
describe('Type casting and transformation', () => {
|
|
423
|
+
test('cast should change type', () => {
|
|
424
|
+
const casted = result.cast<{ id: number; name: string }>();
|
|
425
|
+
expect(casted).toBeInstanceOf(QueryResult);
|
|
426
|
+
expect(casted.len()).toBe(5);
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
describe('Debugging and inspection', () => {
|
|
431
|
+
test('inspect should not throw error', () => {
|
|
432
|
+
// Mock console.log to prevent output during tests
|
|
433
|
+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
434
|
+
|
|
435
|
+
result.inspect();
|
|
436
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
437
|
+
|
|
438
|
+
result.inspect(2); // Limited output
|
|
439
|
+
expect(consoleSpy).toHaveBeenCalledTimes(2);
|
|
440
|
+
|
|
441
|
+
consoleSpy.mockRestore();
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
describe('Edge cases', () => {
|
|
446
|
+
test('should handle empty arrays gracefully', () => {
|
|
447
|
+
const emptyResult = createQueryResult([]);
|
|
448
|
+
|
|
449
|
+
expect(emptyResult.len()).toBe(0);
|
|
450
|
+
expect(emptyResult.isEmpty()).toBe(true);
|
|
451
|
+
expect(emptyResult.first()).toBeUndefined();
|
|
452
|
+
expect(emptyResult.summarizeNumeric('age')).toBeNull();
|
|
453
|
+
expect(emptyResult.groupBy('department')).toEqual({});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
test('should handle invalid field names', () => {
|
|
457
|
+
const invalidSummary = result.summarizeNumeric('nonexistent');
|
|
458
|
+
expect(invalidSummary).toBeNull();
|
|
459
|
+
|
|
460
|
+
const invalidPluck = result.pluck('nonexistent');
|
|
461
|
+
expect(invalidPluck.every(val => val === undefined)).toBe(true);
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
});
|