@objectql/server 4.0.0 → 4.0.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/CHANGELOG.md +33 -3
- package/README.md +6 -3
- package/dist/adapters/graphql.js +9 -5
- package/dist/adapters/graphql.js.map +1 -1
- package/dist/adapters/rest.js +11 -1
- package/dist/adapters/rest.js.map +1 -1
- package/dist/server.js +22 -15
- package/dist/server.js.map +1 -1
- package/package.json +3 -3
- package/src/adapters/graphql.ts +9 -5
- package/src/adapters/rest.ts +12 -1
- package/src/server.ts +20 -15
- package/test/graphql.test.ts +5 -3
- package/test/node.test.ts +7 -3
- package/test/rest-advanced.test.ts +17 -8
- package/test/rest.test.ts +100 -28
- package/tsconfig.tsbuildinfo +1 -1
package/test/rest.test.ts
CHANGED
|
@@ -27,37 +27,97 @@ class MockDriver implements Driver {
|
|
|
27
27
|
async find(objectName: string, query: any) {
|
|
28
28
|
let items = this.data[objectName] || [];
|
|
29
29
|
|
|
30
|
-
// Apply filters if provided
|
|
30
|
+
// Apply filters if provided (supports FilterNode array format)
|
|
31
31
|
if (query && query.filters) {
|
|
32
|
-
|
|
33
|
-
if (typeof filters === 'object') {
|
|
34
|
-
const filterKeys = Object.keys(filters);
|
|
35
|
-
if (filterKeys.length > 0) {
|
|
36
|
-
items = items.filter(item => {
|
|
37
|
-
for (const [key, value] of Object.entries(filters)) {
|
|
38
|
-
if (item[key] !== value) {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return true;
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
}
|
|
32
|
+
items = items.filter(item => this.matchesFilter(item, query.filters));
|
|
46
33
|
}
|
|
47
34
|
|
|
48
|
-
// Apply skip and limit if provided
|
|
35
|
+
// Apply skip and top/limit if provided (QueryAST uses 'top' for limit)
|
|
49
36
|
if (query) {
|
|
50
37
|
if (query.skip) {
|
|
51
38
|
items = items.slice(query.skip);
|
|
52
39
|
}
|
|
53
|
-
if (query.limit) {
|
|
54
|
-
items = items.slice(0, query.limit);
|
|
40
|
+
if (query.top || query.limit) {
|
|
41
|
+
items = items.slice(0, query.top || query.limit);
|
|
55
42
|
}
|
|
56
43
|
}
|
|
57
44
|
|
|
58
45
|
return items;
|
|
59
46
|
}
|
|
60
|
-
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Matches an item against a filter condition
|
|
50
|
+
* @param item - The data item to test
|
|
51
|
+
* @param filter - The filter in FilterNode array format or simple object format
|
|
52
|
+
* FilterNode format: [field, op, value] for single condition
|
|
53
|
+
* Complex filters: [[field, op, value], 'and', [field2, op2, value2]]
|
|
54
|
+
* Simple format: { field: value }
|
|
55
|
+
* @returns true if item matches the filter
|
|
56
|
+
*/
|
|
57
|
+
private matchesFilter(item: any, filter: any): boolean {
|
|
58
|
+
if (!filter) return true;
|
|
59
|
+
|
|
60
|
+
// Handle FilterNode array format: [[field, op, value]] or [[field, op, value], 'and', [field2, op2, value2]]
|
|
61
|
+
if (Array.isArray(filter)) {
|
|
62
|
+
// Single condition: [field, op, value]
|
|
63
|
+
if (filter.length === 3 && typeof filter[0] === 'string') {
|
|
64
|
+
const [field, op, value] = filter;
|
|
65
|
+
return this.evaluateCondition(item, field, op, value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Multiple conditions with logical operators
|
|
69
|
+
let result = true;
|
|
70
|
+
let currentOp = 'and';
|
|
71
|
+
for (let i = 0; i < filter.length; i++) {
|
|
72
|
+
const element = filter[i];
|
|
73
|
+
if (typeof element === 'string') {
|
|
74
|
+
currentOp = element; // 'and' or 'or'
|
|
75
|
+
} else if (Array.isArray(element)) {
|
|
76
|
+
const conditionResult = this.matchesFilter(item, element);
|
|
77
|
+
if (currentOp === 'and') {
|
|
78
|
+
result = result && conditionResult;
|
|
79
|
+
} else if (currentOp === 'or') {
|
|
80
|
+
result = result || conditionResult;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Handle simple object format: { field: value }
|
|
88
|
+
if (typeof filter === 'object') {
|
|
89
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
90
|
+
if (item[key] !== value) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Evaluates a single filter condition
|
|
102
|
+
* @param item - The data item to test
|
|
103
|
+
* @param field - The field name
|
|
104
|
+
* @param op - The operator: '=', '!=', '>', '>=', '<', '<=', 'in'
|
|
105
|
+
* @param value - The value to compare against
|
|
106
|
+
* @returns true if the condition is satisfied
|
|
107
|
+
*/
|
|
108
|
+
private evaluateCondition(item: any, field: string, op: string, value: any): boolean {
|
|
109
|
+
const fieldValue = item[field];
|
|
110
|
+
switch (op) {
|
|
111
|
+
case '=': return fieldValue === value;
|
|
112
|
+
case '!=': return fieldValue !== value;
|
|
113
|
+
case '>': return fieldValue > value;
|
|
114
|
+
case '>=': return fieldValue >= value;
|
|
115
|
+
case '<': return fieldValue < value;
|
|
116
|
+
case '<=': return fieldValue <= value;
|
|
117
|
+
case 'in': return Array.isArray(value) ? value.includes(fieldValue) : false;
|
|
118
|
+
default: return true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
61
121
|
async findOne(objectName: string, id: string | number, query?: any, options?: any) {
|
|
62
122
|
const items = this.data[objectName] || [];
|
|
63
123
|
if (id !== undefined && id !== null) {
|
|
@@ -97,7 +157,13 @@ class MockDriver implements Driver {
|
|
|
97
157
|
}
|
|
98
158
|
|
|
99
159
|
async count(objectName: string, query: any) {
|
|
100
|
-
|
|
160
|
+
// Count should apply filters but not skip/limit
|
|
161
|
+
const countQuery = { ...query };
|
|
162
|
+
delete countQuery.skip;
|
|
163
|
+
delete countQuery.top;
|
|
164
|
+
delete countQuery.limit;
|
|
165
|
+
const items = await this.find(objectName, countQuery);
|
|
166
|
+
return items.length;
|
|
101
167
|
}
|
|
102
168
|
|
|
103
169
|
async createMany(objectName: string, data: any[]) {
|
|
@@ -201,6 +267,8 @@ describe('REST API Adapter', () => {
|
|
|
201
267
|
}
|
|
202
268
|
}
|
|
203
269
|
});
|
|
270
|
+
|
|
271
|
+
await app.init();
|
|
204
272
|
|
|
205
273
|
// Create handler and server once for all tests
|
|
206
274
|
handler = createRESTHandler(app);
|
|
@@ -223,8 +291,9 @@ describe('REST API Adapter', () => {
|
|
|
223
291
|
.set('Accept', 'application/json');
|
|
224
292
|
|
|
225
293
|
expect(response.status).toBe(200);
|
|
226
|
-
expect(response.body.
|
|
227
|
-
expect(response.body
|
|
294
|
+
expect(response.body.data).toBeDefined();
|
|
295
|
+
expect(response.body.data.name).toBe('Alice');
|
|
296
|
+
expect(response.body.data['@type']).toBe('user');
|
|
228
297
|
});
|
|
229
298
|
|
|
230
299
|
it('should handle POST /api/data/:object - Create record', async () => {
|
|
@@ -234,9 +303,10 @@ describe('REST API Adapter', () => {
|
|
|
234
303
|
.set('Accept', 'application/json');
|
|
235
304
|
|
|
236
305
|
expect(response.status).toBe(201);
|
|
237
|
-
expect(response.body.
|
|
238
|
-
expect(response.body.
|
|
239
|
-
expect(response.body
|
|
306
|
+
expect(response.body.data).toBeDefined();
|
|
307
|
+
expect(response.body.data.name).toBe('Charlie');
|
|
308
|
+
expect(response.body.data._id).toBeDefined();
|
|
309
|
+
expect(response.body.data['@type']).toBe('user');
|
|
240
310
|
});
|
|
241
311
|
|
|
242
312
|
it('should handle PUT /api/data/:object/:id - Update record', async () => {
|
|
@@ -254,8 +324,9 @@ describe('REST API Adapter', () => {
|
|
|
254
324
|
.set('Accept', 'application/json');
|
|
255
325
|
|
|
256
326
|
expect(response.status).toBe(200);
|
|
257
|
-
expect(response.body.
|
|
258
|
-
expect(response.body
|
|
327
|
+
expect(response.body.data).toBeDefined();
|
|
328
|
+
expect(response.body.data.deleted).toBe(true);
|
|
329
|
+
expect(response.body.data['@type']).toBe('user');
|
|
259
330
|
});
|
|
260
331
|
|
|
261
332
|
it('should return 404 for non-existent object', async () => {
|
|
@@ -442,7 +513,8 @@ describe('REST API Adapter', () => {
|
|
|
442
513
|
|
|
443
514
|
// Should still work as single create
|
|
444
515
|
expect(response.status).toBe(201);
|
|
445
|
-
expect(response.body.
|
|
516
|
+
expect(response.body.data).toBeDefined();
|
|
517
|
+
expect(response.body.data._id).toBeDefined();
|
|
446
518
|
});
|
|
447
519
|
});
|
|
448
520
|
});
|