@reldens/storage 0.88.0 → 0.89.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 (63) hide show
  1. package/.claude/driver-methods-complete-analysis.md +1978 -0
  2. package/.claude/test-architecture.md +973 -0
  3. package/CLAUDE.md +121 -1
  4. package/bin/reldens-storage.js +1 -1
  5. package/lib/base-driver.js +8 -0
  6. package/lib/entities-generator.js +1 -3
  7. package/lib/entity-templates/mikro-orm-model.template +2 -1
  8. package/lib/generators/models-generation.js +126 -5
  9. package/lib/mikro-orm/mikro-orm-data-server.js +24 -10
  10. package/lib/mikro-orm/mikro-orm-driver.js +184 -116
  11. package/lib/objection-js/objection-js-driver.js +111 -19
  12. package/lib/prisma/prisma-data-server.js +3 -2
  13. package/lib/prisma/prisma-driver.js +12 -10
  14. package/lib/prisma/prisma-filter-processor.js +193 -146
  15. package/lib/prisma/prisma-metadata-loader.js +0 -4
  16. package/lib/prisma/prisma-relation-resolver.js +292 -267
  17. package/lib/prisma/prisma-type-caster.js +280 -260
  18. package/package.json +15 -6
  19. package/tests/.env.test.example +7 -0
  20. package/tests/fixtures/categories-fixtures.js +164 -0
  21. package/tests/fixtures/expected-entities-mikro-orm/entities/test-categories-entity.js +70 -0
  22. package/tests/fixtures/expected-entities-mikro-orm/entities/test-products-entity.js +95 -0
  23. package/tests/fixtures/expected-entities-mikro-orm/entities/test-reviews-entity.js +84 -0
  24. package/tests/fixtures/expected-entities-mikro-orm/entities-config.js +17 -0
  25. package/tests/fixtures/expected-entities-mikro-orm/entities-translations.js +13 -0
  26. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/registered-models-mikro-orm.js +23 -0
  27. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-categories-model.js +57 -0
  28. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-products-model.js +79 -0
  29. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-reviews-model.js +70 -0
  30. package/tests/fixtures/expected-entities-objection-js/entities/test-categories-entity.js +70 -0
  31. package/tests/fixtures/expected-entities-objection-js/entities/test-products-entity.js +95 -0
  32. package/tests/fixtures/expected-entities-objection-js/entities/test-reviews-entity.js +84 -0
  33. package/tests/fixtures/expected-entities-objection-js/entities-config.js +17 -0
  34. package/tests/fixtures/expected-entities-objection-js/entities-translations.js +13 -0
  35. package/tests/fixtures/expected-entities-objection-js/models/objection-js/registered-models-objection-js.js +23 -0
  36. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-categories-model.js +33 -0
  37. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-products-model.js +42 -0
  38. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-reviews-model.js +33 -0
  39. package/tests/fixtures/expected-entities-prisma/entities/test-categories-entity.js +70 -0
  40. package/tests/fixtures/expected-entities-prisma/entities/test-products-entity.js +95 -0
  41. package/tests/fixtures/expected-entities-prisma/entities/test-reviews-entity.js +84 -0
  42. package/tests/fixtures/expected-entities-prisma/entities-config.js +17 -0
  43. package/tests/fixtures/expected-entities-prisma/entities-translations.js +13 -0
  44. package/tests/fixtures/expected-entities-prisma/models/prisma/registered-models-prisma.js +23 -0
  45. package/tests/fixtures/expected-entities-prisma/models/prisma/test-categories-model.js +43 -0
  46. package/tests/fixtures/expected-entities-prisma/models/prisma/test-products-model.js +50 -0
  47. package/tests/fixtures/expected-entities-prisma/models/prisma/test-reviews-model.js +46 -0
  48. package/tests/fixtures/products-fixtures.js +119 -0
  49. package/tests/fixtures/reviews-fixtures.js +42 -0
  50. package/tests/fixtures/sql/test-schema.sql +59 -0
  51. package/tests/integration/test-drivers.js +257 -0
  52. package/tests/integration/test-nested-filters.js +408 -0
  53. package/tests/integration/test-relations.js +349 -0
  54. package/tests/run-tests.js +213 -0
  55. package/tests/unit/test-drivers.js +89 -0
  56. package/tests/unit/test-entities-generator.js +533 -0
  57. package/tests/unit/test-entity-manager.js +128 -0
  58. package/tests/unit/test-type-mapper.js +232 -0
  59. package/tests/utils/custom-reporter.js +73 -0
  60. package/tests/utils/driver-registry.js +144 -0
  61. package/tests/utils/prisma-subprocess-worker.js +150 -0
  62. package/tests/utils/test-helpers.js +726 -0
  63. package/tests/utils/test-runner.js +63 -0
@@ -0,0 +1,408 @@
1
+ /**
2
+ *
3
+ * Reldens - Nested Filters Integration Test
4
+ * Tests complex filter syntax across all three storage drivers with an actual database
5
+ *
6
+ */
7
+
8
+ const { TestRunner, assert } = require('../utils/test-runner');
9
+ const { TestHelpers } = require('../utils/test-helpers');
10
+ const { CategoriesFixtures } = require('../fixtures/categories-fixtures');
11
+ const { ProductsFixtures } = require('../fixtures/products-fixtures');
12
+
13
+ class NestedFiltersTest
14
+ {
15
+
16
+ constructor(dataServer, repos, driverName)
17
+ {
18
+ this.dataServer = dataServer;
19
+ this.categoriesRepo = repos.testCategories;
20
+ this.productsRepo = repos.testProducts;
21
+ this.driverName = driverName;
22
+ this.runner = new TestRunner();
23
+ }
24
+
25
+ async run()
26
+ {
27
+ this.runner.suite('Nested Filters - Driver: '+this.driverName);
28
+ await this.testAndOperator();
29
+ await this.testOrOperator();
30
+ await this.testNestedAndOr();
31
+ await this.testNotOperator();
32
+ await this.testInOperator();
33
+ await this.testLikeOperator();
34
+ await this.testComparisonOperators();
35
+ await this.testComplexNestedScenarios();
36
+ await this.testEdgeCases();
37
+ return this.runner.getResults();
38
+ }
39
+
40
+ async testAndOperator()
41
+ {
42
+ this.runner.group('AND Operator');
43
+ await TestHelpers.cleanDatabase(this.dataServer);
44
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
45
+ CategoriesFixtures.category_filters_1,
46
+ CategoriesFixtures.category_filters_2,
47
+ CategoriesFixtures.category_filters_3
48
+ ]);
49
+ await this.runner.test('should filter with simple AND condition', async () => {
50
+ let results = await this.categoriesRepo.load({
51
+ AND: [
52
+ {is_active: 1},
53
+ {display_order: 1}
54
+ ]
55
+ });
56
+ assert.ok(results);
57
+ assert.strictEqual(results.length, 1);
58
+ assert.strictEqual(results[0].display_order, 1);
59
+ assert.strictEqual(results[0].is_active, 1);
60
+ });
61
+ await this.runner.test('should filter with AND containing multiple conditions', async () => {
62
+ let results = await this.categoriesRepo.load({
63
+ AND: [
64
+ {is_active: 1},
65
+ {display_order: {operator: 'gte', value: 1}}
66
+ ]
67
+ });
68
+ assert.ok(results);
69
+ assert.strictEqual(results.length, 2);
70
+ });
71
+ await this.runner.test('should return empty when AND conditions do not match', async () => {
72
+ let results = await this.categoriesRepo.load({
73
+ AND: [
74
+ {is_active: 1},
75
+ {slug: 'nonexistent'}
76
+ ]
77
+ });
78
+ assert.ok(results);
79
+ assert.strictEqual(results.length, 0);
80
+ });
81
+ }
82
+
83
+ async testOrOperator()
84
+ {
85
+ this.runner.group('OR Operator');
86
+ await TestHelpers.cleanDatabase(this.dataServer);
87
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
88
+ CategoriesFixtures.category_filters_1,
89
+ CategoriesFixtures.category_filters_2,
90
+ CategoriesFixtures.category_filters_3
91
+ ]);
92
+ await this.runner.test('should filter with simple OR condition', async () => {
93
+ let results = await this.categoriesRepo.load({
94
+ OR: [
95
+ {slug: 'filters-test-1'},
96
+ {slug: 'filters-test-2'}
97
+ ]
98
+ });
99
+ assert.ok(results);
100
+ assert.strictEqual(results.length, 2);
101
+ });
102
+ await this.runner.test('should filter with OR containing operator conditions', async () => {
103
+ let results = await this.categoriesRepo.load({
104
+ OR: [
105
+ {display_order: {operator: 'lt', value: 2}},
106
+ {is_active: 0}
107
+ ]
108
+ });
109
+ assert.ok(results);
110
+ assert.strictEqual(results.length, 2);
111
+ });
112
+ await this.runner.test('should return all matching records with OR', async () => {
113
+ let results = await this.categoriesRepo.load({
114
+ OR: [
115
+ {is_active: 1},
116
+ {is_active: 0}
117
+ ]
118
+ });
119
+ assert.ok(results);
120
+ assert.strictEqual(results.length, 3);
121
+ });
122
+ }
123
+
124
+ async testNestedAndOr()
125
+ {
126
+ this.runner.group('Nested AND/OR');
127
+ await TestHelpers.cleanDatabase(this.dataServer);
128
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
129
+ CategoriesFixtures.category_filters_1,
130
+ CategoriesFixtures.category_filters_2,
131
+ CategoriesFixtures.category_filters_3
132
+ ]);
133
+ await this.runner.test('should filter with OR inside AND', async () => {
134
+ let results = await this.categoriesRepo.load({
135
+ AND: [
136
+ {is_active: 1},
137
+ {
138
+ OR: [
139
+ {display_order: 1},
140
+ {display_order: 2}
141
+ ]
142
+ }
143
+ ]
144
+ });
145
+ assert.ok(results);
146
+ assert.strictEqual(results.length, 2);
147
+ });
148
+ await this.runner.test('should filter with AND inside OR', async () => {
149
+ let results = await this.categoriesRepo.load({
150
+ OR: [
151
+ {
152
+ AND: [
153
+ {is_active: 1},
154
+ {display_order: 1}
155
+ ]
156
+ },
157
+ {is_active: 0}
158
+ ]
159
+ });
160
+ assert.ok(results);
161
+ assert.strictEqual(results.length, 2);
162
+ });
163
+ await this.runner.test('should handle deep nesting of AND/OR', async () => {
164
+ let results = await this.categoriesRepo.load({
165
+ AND: [
166
+ {
167
+ OR: [
168
+ {display_order: 1},
169
+ {display_order: 2}
170
+ ]
171
+ },
172
+ {
173
+ OR: [
174
+ {is_active: 1},
175
+ {is_active: 0}
176
+ ]
177
+ }
178
+ ]
179
+ });
180
+ assert.ok(results);
181
+ assert.strictEqual(results.length, 2);
182
+ });
183
+ }
184
+
185
+ async testNotOperator()
186
+ {
187
+ this.runner.group('NOT Operator');
188
+ await TestHelpers.cleanDatabase(this.dataServer);
189
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
190
+ CategoriesFixtures.category_filters_1,
191
+ CategoriesFixtures.category_filters_2,
192
+ CategoriesFixtures.category_filters_3
193
+ ]);
194
+ await this.runner.test('should filter with NOT operator', async () => {
195
+ let results = await this.categoriesRepo.load({
196
+ is_active: {operator: 'not', value: 0}
197
+ });
198
+ assert.ok(results);
199
+ assert.strictEqual(results.length, 2);
200
+ for(let category of results){
201
+ assert.strictEqual(category.is_active, 1);
202
+ }
203
+ });
204
+ await this.runner.test('should filter with NOT inside AND', async () => {
205
+ let results = await this.categoriesRepo.load({
206
+ AND: [
207
+ {is_active: {operator: 'not', value: 0}},
208
+ {display_order: 1}
209
+ ]
210
+ });
211
+ assert.ok(results);
212
+ assert.strictEqual(results.length, 1);
213
+ });
214
+ }
215
+
216
+ async testInOperator()
217
+ {
218
+ this.runner.group('IN Operator');
219
+ await TestHelpers.cleanDatabase(this.dataServer);
220
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
221
+ CategoriesFixtures.category_filters_1,
222
+ CategoriesFixtures.category_filters_2,
223
+ CategoriesFixtures.category_filters_3
224
+ ]);
225
+ await this.runner.test('should filter with IN operator', async () => {
226
+ let results = await this.categoriesRepo.load({
227
+ display_order: {operator: 'in', value: [1, 2]}
228
+ });
229
+ assert.ok(results);
230
+ assert.strictEqual(results.length, 2);
231
+ });
232
+ await this.runner.test('should filter with IN operator on string field', async () => {
233
+ let results = await this.categoriesRepo.load({
234
+ slug: {operator: 'in', value: ['filters-test-1', 'filters-test-2']}
235
+ });
236
+ assert.ok(results);
237
+ assert.strictEqual(results.length, 2);
238
+ });
239
+ await this.runner.test('should return empty with IN operator when no match', async () => {
240
+ let results = await this.categoriesRepo.load({
241
+ display_order: {operator: 'in', value: [99, 100]}
242
+ });
243
+ assert.ok(results);
244
+ assert.strictEqual(results.length, 0);
245
+ });
246
+ }
247
+
248
+ async testLikeOperator()
249
+ {
250
+ this.runner.group('LIKE Operator');
251
+ await TestHelpers.cleanDatabase(this.dataServer);
252
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
253
+ CategoriesFixtures.category_filters_1,
254
+ CategoriesFixtures.category_filters_2
255
+ ]);
256
+ await this.runner.test('should filter with LIKE operator', async () => {
257
+ let results = await this.categoriesRepo.load({
258
+ name: {operator: 'like', value: '%Filters%'}
259
+ });
260
+ assert.ok(results);
261
+ assert.ok(results.length > 0);
262
+ assert.ok(results[0].name.includes('Filters'));
263
+ });
264
+ await this.runner.test('should filter with LIKE and wildcards', async () => {
265
+ let results = await this.categoriesRepo.load({
266
+ slug: {operator: 'like', value: '%filters%'}
267
+ });
268
+ assert.ok(results);
269
+ assert.ok(results.length > 0);
270
+ });
271
+ await this.runner.test('should work with LIKE inside AND', async () => {
272
+ let results = await this.categoriesRepo.load({
273
+ AND: [
274
+ {is_active: 1},
275
+ {name: {operator: 'like', value: '%Filters%'}}
276
+ ]
277
+ });
278
+ assert.ok(results);
279
+ assert.ok(results.length > 0);
280
+ });
281
+ }
282
+
283
+ async testComparisonOperators()
284
+ {
285
+ this.runner.group('Comparison Operators');
286
+ await TestHelpers.cleanDatabase(this.dataServer);
287
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
288
+ CategoriesFixtures.category_filters_1,
289
+ CategoriesFixtures.category_filters_2,
290
+ CategoriesFixtures.category_filters_3
291
+ ]);
292
+ await this.runner.test('should filter with gt operator', async () => {
293
+ let results = await this.categoriesRepo.load({
294
+ display_order: {operator: 'gt', value: 1}
295
+ });
296
+ assert.ok(results);
297
+ assert.strictEqual(results.length, 2);
298
+ });
299
+ await this.runner.test('should filter with gte operator', async () => {
300
+ let results = await this.categoriesRepo.load({
301
+ display_order: {operator: 'gte', value: 2}
302
+ });
303
+ assert.ok(results);
304
+ assert.strictEqual(results.length, 2);
305
+ });
306
+ await this.runner.test('should filter with lt operator', async () => {
307
+ let results = await this.categoriesRepo.load({
308
+ display_order: {operator: 'lt', value: 3}
309
+ });
310
+ assert.ok(results);
311
+ assert.strictEqual(results.length, 2);
312
+ });
313
+ await this.runner.test('should filter with lte operator', async () => {
314
+ let results = await this.categoriesRepo.load({
315
+ display_order: {operator: 'lte', value: 2}
316
+ });
317
+ assert.ok(results);
318
+ assert.strictEqual(results.length, 2);
319
+ });
320
+ }
321
+
322
+ async testComplexNestedScenarios()
323
+ {
324
+ this.runner.group('Complex Nested Scenarios');
325
+ await TestHelpers.cleanDatabase(this.dataServer);
326
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
327
+ CategoriesFixtures.category_filters_1
328
+ ]);
329
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_products', [
330
+ ProductsFixtures.product_filters_1,
331
+ ProductsFixtures.product_filters_2
332
+ ]);
333
+ await this.runner.test('should handle complex filter with multiple operators', async () => {
334
+ let results = await this.productsRepo.load({
335
+ AND: [
336
+ {status: 'published'},
337
+ {
338
+ OR: [
339
+ {price: {operator: 'lt', value: 1000}},
340
+ {stock_quantity: {operator: 'gte', value: 50}}
341
+ ]
342
+ }
343
+ ]
344
+ });
345
+ assert.ok(results);
346
+ assert.ok(results.length > 0);
347
+ });
348
+ await this.runner.test('should handle filter with IN inside OR', async () => {
349
+ let results = await this.productsRepo.load({
350
+ OR: [
351
+ {status: {operator: 'in', value: ['published', 'draft']}},
352
+ {stock_quantity: {operator: 'gt', value: 0}}
353
+ ]
354
+ });
355
+ assert.ok(results);
356
+ assert.strictEqual(results.length, 2);
357
+ });
358
+ await this.runner.test('should handle nested AND/OR with LIKE and comparison operators', async () => {
359
+ let results = await this.productsRepo.load({
360
+ AND: [
361
+ {
362
+ OR: [
363
+ {name: {operator: 'like', value: '%Phone%'}},
364
+ {name: {operator: 'like', value: '%Product%'}}
365
+ ]
366
+ },
367
+ {price: {operator: 'gte', value: 0}}
368
+ ]
369
+ });
370
+ assert.ok(results);
371
+ assert.ok(results.length > 0);
372
+ });
373
+ }
374
+
375
+ async testEdgeCases()
376
+ {
377
+ this.runner.group('Edge Cases');
378
+ await TestHelpers.cleanDatabase(this.dataServer);
379
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
380
+ CategoriesFixtures.category_filters_1
381
+ ]);
382
+ await this.runner.test('should handle empty AND array', async () => {
383
+ let results = await this.categoriesRepo.load({AND: []});
384
+ assert.ok(results);
385
+ });
386
+ await this.runner.test('should handle empty OR array', async () => {
387
+ let results = await this.categoriesRepo.load({OR: []});
388
+ assert.ok(results);
389
+ });
390
+ await this.runner.test('should handle single condition in AND', async () => {
391
+ let results = await this.categoriesRepo.load({
392
+ AND: [{is_active: 1}]
393
+ });
394
+ assert.ok(results);
395
+ assert.strictEqual(results.length, 1);
396
+ });
397
+ await this.runner.test('should handle single condition in OR', async () => {
398
+ let results = await this.categoriesRepo.load({
399
+ OR: [{is_active: 1}]
400
+ });
401
+ assert.ok(results);
402
+ assert.strictEqual(results.length, 1);
403
+ });
404
+ }
405
+
406
+ }
407
+
408
+ module.exports = NestedFiltersTest;