@reldens/storage 0.87.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 (65) hide show
  1. package/.claude/driver-methods-complete-analysis.md +1978 -0
  2. package/.claude/test-architecture.md +973 -0
  3. package/CLAUDE.md +141 -7
  4. package/README.md +14 -2
  5. package/bin/reldens-storage.js +1 -1
  6. package/lib/base-driver.js +8 -0
  7. package/lib/entities-generator.js +1 -3
  8. package/lib/entity-templates/mikro-orm-model.template +2 -1
  9. package/lib/generators/models-generation.js +126 -5
  10. package/lib/mikro-orm/mikro-orm-data-server.js +24 -10
  11. package/lib/mikro-orm/mikro-orm-driver.js +184 -116
  12. package/lib/objection-js/objection-js-driver.js +111 -19
  13. package/lib/prisma/prisma-client-loader.js +4 -0
  14. package/lib/prisma/prisma-data-server.js +3 -2
  15. package/lib/prisma/prisma-driver.js +12 -10
  16. package/lib/prisma/prisma-filter-processor.js +193 -146
  17. package/lib/prisma/prisma-metadata-loader.js +0 -4
  18. package/lib/prisma/prisma-relation-resolver.js +292 -267
  19. package/lib/prisma/prisma-type-caster.js +280 -260
  20. package/package.json +15 -6
  21. package/tests/.env.test.example +7 -0
  22. package/tests/fixtures/categories-fixtures.js +164 -0
  23. package/tests/fixtures/expected-entities-mikro-orm/entities/test-categories-entity.js +70 -0
  24. package/tests/fixtures/expected-entities-mikro-orm/entities/test-products-entity.js +95 -0
  25. package/tests/fixtures/expected-entities-mikro-orm/entities/test-reviews-entity.js +84 -0
  26. package/tests/fixtures/expected-entities-mikro-orm/entities-config.js +17 -0
  27. package/tests/fixtures/expected-entities-mikro-orm/entities-translations.js +13 -0
  28. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/registered-models-mikro-orm.js +23 -0
  29. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-categories-model.js +57 -0
  30. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-products-model.js +79 -0
  31. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-reviews-model.js +70 -0
  32. package/tests/fixtures/expected-entities-objection-js/entities/test-categories-entity.js +70 -0
  33. package/tests/fixtures/expected-entities-objection-js/entities/test-products-entity.js +95 -0
  34. package/tests/fixtures/expected-entities-objection-js/entities/test-reviews-entity.js +84 -0
  35. package/tests/fixtures/expected-entities-objection-js/entities-config.js +17 -0
  36. package/tests/fixtures/expected-entities-objection-js/entities-translations.js +13 -0
  37. package/tests/fixtures/expected-entities-objection-js/models/objection-js/registered-models-objection-js.js +23 -0
  38. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-categories-model.js +33 -0
  39. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-products-model.js +42 -0
  40. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-reviews-model.js +33 -0
  41. package/tests/fixtures/expected-entities-prisma/entities/test-categories-entity.js +70 -0
  42. package/tests/fixtures/expected-entities-prisma/entities/test-products-entity.js +95 -0
  43. package/tests/fixtures/expected-entities-prisma/entities/test-reviews-entity.js +84 -0
  44. package/tests/fixtures/expected-entities-prisma/entities-config.js +17 -0
  45. package/tests/fixtures/expected-entities-prisma/entities-translations.js +13 -0
  46. package/tests/fixtures/expected-entities-prisma/models/prisma/registered-models-prisma.js +23 -0
  47. package/tests/fixtures/expected-entities-prisma/models/prisma/test-categories-model.js +43 -0
  48. package/tests/fixtures/expected-entities-prisma/models/prisma/test-products-model.js +50 -0
  49. package/tests/fixtures/expected-entities-prisma/models/prisma/test-reviews-model.js +46 -0
  50. package/tests/fixtures/products-fixtures.js +119 -0
  51. package/tests/fixtures/reviews-fixtures.js +42 -0
  52. package/tests/fixtures/sql/test-schema.sql +59 -0
  53. package/tests/integration/test-drivers.js +257 -0
  54. package/tests/integration/test-nested-filters.js +408 -0
  55. package/tests/integration/test-relations.js +349 -0
  56. package/tests/run-tests.js +213 -0
  57. package/tests/unit/test-drivers.js +89 -0
  58. package/tests/unit/test-entities-generator.js +533 -0
  59. package/tests/unit/test-entity-manager.js +128 -0
  60. package/tests/unit/test-type-mapper.js +232 -0
  61. package/tests/utils/custom-reporter.js +73 -0
  62. package/tests/utils/driver-registry.js +144 -0
  63. package/tests/utils/prisma-subprocess-worker.js +150 -0
  64. package/tests/utils/test-helpers.js +726 -0
  65. package/tests/utils/test-runner.js +63 -0
@@ -0,0 +1,1978 @@
1
+ # Driver Methods Complete Analysis
2
+
3
+ **Date:** 2026-01-28
4
+ **Purpose:** Complete understanding of driver abstraction layer and ALL public methods across the 3 drivers
5
+
6
+ ---
7
+
8
+ ## 1. FUNDAMENTAL PURPOSE: The Abstraction Layer
9
+
10
+ ### What Problem Does This Solve?
11
+
12
+ **The @reldens/storage package provides a UNIFIED API across three different ORM libraries:**
13
+ - ObjectionJS (built on Knex.js)
14
+ - MikroORM
15
+ - Prisma
16
+
17
+ ### The Core Concept
18
+
19
+ **Write once, run anywhere:**
20
+ ```javascript
21
+ // Application code - SAME for all drivers
22
+ let category = await categoriesRepo.create({name: 'Electronics', slug: 'electronics'});
23
+ let products = await productsRepo.loadWithRelations({category_id: category.id}, ['related_reviews']);
24
+ let count = await categoriesRepo.count({is_active: 1});
25
+ ```
26
+
27
+ **No matter which driver is configured (objection-js, mikro-orm, or prisma), the code above works identically.**
28
+
29
+ ### Why This Matters
30
+
31
+ **Without abstraction:**
32
+ ```javascript
33
+ // ObjectionJS
34
+ let category = await Category.query().insert({name: 'Electronics'});
35
+ let products = await Product.query().where('category_id', category.id).withGraphFetched('related_reviews');
36
+
37
+ // MikroORM
38
+ let category = await em.create(Category, {name: 'Electronics'});
39
+ await em.flush();
40
+ let products = await em.find(Product, {category_id: category.id}, {populate: ['related_reviews']});
41
+
42
+ // Prisma
43
+ let category = await prisma.category.create({data: {name: 'Electronics'}});
44
+ let products = await prisma.product.findMany({where: {category_id: category.id}, include: {related_reviews: true}});
45
+ ```
46
+
47
+ **Each ORM has different:**
48
+ - Method names
49
+ - Parameter structures
50
+ - Query syntax
51
+ - Relation loading syntax
52
+ - Filter operators
53
+
54
+ **With abstraction:**
55
+ ```javascript
56
+ // ONE codebase works with ALL ORMs
57
+ await repo.create(params);
58
+ await repo.loadWithRelations(filters, relations);
59
+ await repo.count(filters);
60
+ ```
61
+
62
+ ### The Contract
63
+
64
+ **Every driver MUST implement:**
65
+ 1. **Same method signatures** - Same method names, same parameters
66
+ 2. **Same behavior** - Same inputs produce same outputs
67
+ 3. **Same return format** - Results structure is identical
68
+ 4. **Driver-agnostic code** - Application code doesn't know/care which driver is used
69
+
70
+ **The BaseDriver class defines this contract** (lib/base-driver.js)
71
+
72
+ ---
73
+
74
+ ## 2. PUBLIC METHODS - The Complete API
75
+
76
+ All methods defined in BaseDriver that ALL drivers must implement:
77
+
78
+ ### CREATE Operations
79
+ - `create(params)` - Create single record
80
+ - `createWithRelations(params, relations)` - Create record with nested relations
81
+
82
+ ### READ Operations (No Relations)
83
+ - `loadAll()` - Load all records (no filters, respects limit/offset/sort)
84
+ - `load(filters)` - Load records by filters (respects limit/offset/sort)
85
+ - `loadBy(field, fieldValue, operator)` - Load records by single field
86
+ - `loadById(id)` - Load single record by ID
87
+ - `loadByIds(ids)` - Load multiple records by IDs array
88
+ - `loadOne(filters)` - Load first record matching filters
89
+ - `loadOneBy(field, fieldValue, operator)` - Load first record by single field
90
+
91
+ ### READ Operations (With Relations)
92
+ - `loadAllWithRelations(relations)` - Load all records with relations
93
+ - `loadWithRelations(filters, relations)` - Load records by filters with relations
94
+ - `loadByWithRelations(field, fieldValue, relations, operator)` - Load by field with relations
95
+ - `loadByIdWithRelations(id, relations)` - Load by ID with relations
96
+ - `loadOneWithRelations(filters, relations)` - Load first record with relations
97
+ - `loadOneByWithRelations(field, fieldValue, relations, operator)` - Load first by field with relations
98
+
99
+ ### UPDATE Operations
100
+ - `update(filters, updatePatch)` - Update records by filters
101
+ - `updateBy(field, fieldValue, updatePatch, operator)` - Update by single field
102
+ - `updateById(id, params)` - Update single record by ID
103
+ - `upsert(params, filters)` - Insert if not exists, update if exists
104
+
105
+ ### DELETE Operations
106
+ - `delete(filters)` - Delete records by filters
107
+ - `deleteById(id)` - Delete single record by ID
108
+
109
+ ### COUNT Operations
110
+ - `count(filters)` - Count records by filters
111
+ - `countWithRelations(filters, relations)` - Count records with relation filters
112
+
113
+ ### UTILITY Operations
114
+ - `rawQuery(content)` - Execute raw SQL query
115
+ - `executeCustomQuery(methodName, methodOptions)` - Execute custom model method
116
+ - `isJsonField(fieldName)` - Check if field is JSON type
117
+ - `parseRelationsString(relationsString)` - Parse relation string to array
118
+
119
+ ### PROPERTY Accessors
120
+ - `databaseName()` - Get database name
121
+ - `id()` - Get entity ID
122
+ - `name()` - Get entity name
123
+ - `tableName()` - Get table name
124
+ - `property(propertyName)` - Get model property
125
+
126
+ ### CONFIGURATION Properties
127
+ - `this.limit` - Result limit (0 = no limit)
128
+ - `this.offset` - Result offset
129
+ - `this.sortBy` - Sort field name
130
+ - `this.sortDirection` - Sort direction ('ASC' or 'DESC')
131
+ - `this.select` - Fields to select (array)
132
+
133
+ ---
134
+
135
+ ## 3. METHOD-BY-METHOD ANALYSIS
136
+
137
+ ### 3.1 create(params)
138
+
139
+ **Purpose:** Create a single record
140
+
141
+ **Parameters:**
142
+ - `params` (object) - Record data
143
+
144
+ **Expected Behavior:**
145
+ - Insert record into database
146
+ - Return created record with ID
147
+ - Throw error if constraints violated (UNIQUE, FK, NOT NULL)
148
+
149
+ **Return:** Created record object with all fields including auto-generated ID
150
+
151
+ #### ObjectionJS Implementation (objection-js-driver.js:38-41)
152
+
153
+ ```javascript
154
+ create(params)
155
+ {
156
+ return this.queryBuilder().insert(params);
157
+ }
158
+ ```
159
+
160
+ **How it works:**
161
+ - Calls Objection's `insert()` method
162
+ - Passes params directly to Knex
163
+ - Returns promise that resolves to created record
164
+
165
+ **Behavior:**
166
+ - ✅ Accepts explicit IDs for autoincrement fields
167
+ - ✅ Returns created record with ID
168
+ - ✅ Throws on constraint violations
169
+
170
+ #### MikroORM Implementation (mikro-orm-driver.js:85-92)
171
+
172
+ ```javascript
173
+ async create(params)
174
+ {
175
+ let transformed = this.transformFkToRelations(params);
176
+ let newInstance = await this.orm.em.create(this.rawModel, transformed);
177
+ await this.orm.em.upsert(newInstance);
178
+ await this.orm.em.flush();
179
+ return newInstance;
180
+ }
181
+ ```
182
+
183
+ **How it works:**
184
+ 1. Transforms FK fields to relation objects via `transformFkToRelations()`
185
+ - Converts `category_id: 1` to `related_categories: Reference<Category>(1)`
186
+ 2. Creates entity instance via `em.create()`
187
+ 3. Upserts to identity map via `em.upsert()`
188
+ 4. Flushes to database via `em.flush()`
189
+ 5. Returns entity instance
190
+
191
+ **transformFkToRelations() (lines 497-512):**
192
+ ```javascript
193
+ transformFkToRelations(params)
194
+ {
195
+ let transformed = {...params};
196
+ for(let fkColumn of Object.keys(this.fkMappings)){
197
+ if(!sc.hasOwn(params, fkColumn) || null === params[fkColumn]){
198
+ continue;
199
+ }
200
+ let mapping = this.fkMappings[fkColumn];
201
+ transformed[mapping.relationKey] = this.orm.em.getReference(mapping.entityName, params[fkColumn]);
202
+ delete transformed[fkColumn];
203
+ }
204
+ return transformed;
205
+ }
206
+ ```
207
+
208
+ **Behavior:**
209
+ - ✅ Accepts explicit IDs for autoincrement fields
210
+ - ✅ Returns created record with ID
211
+ - ✅ Throws on constraint violations
212
+ - ⚠️ Transforms FK columns to MikroORM References
213
+
214
+ #### Prisma Implementation (prisma-driver.js:225-234)
215
+
216
+ ```javascript
217
+ async create(params)
218
+ {
219
+ let preparedData = this.prepareData(params, {convertRelations: true, isCreate: true});
220
+ this.ensureRequiredFields(preparedData);
221
+ return this.executeWithNormalization(
222
+ () => this.model.create({data: preparedData}),
223
+ 'Create error: ',
224
+ false
225
+ );
226
+ }
227
+ ```
228
+
229
+ **How it works:**
230
+ 1. Prepares data via `prepareData()` with `isCreate: true`
231
+ - Removes autoincrement ID field (line 170-172)
232
+ - Converts FK fields to relation connect syntax
233
+ - Casts types
234
+ 2. Validates required fields via `ensureRequiredFields()`
235
+ 3. Calls Prisma's `model.create()`
236
+ 4. Normalizes return data (converts BigInt, etc)
237
+
238
+ **prepareData() for create (lines 150-189):**
239
+ ```javascript
240
+ prepareData(params, options = {})
241
+ {
242
+ let isCreate = sc.get(options, 'isCreate', false);
243
+ let convertRelations = sc.get(options, 'convertRelations', false);
244
+ if(convertRelations){
245
+ let converted = this.relationResolver.convertForeignKeysToRelations(params, isCreate);
246
+ return this.castDataFields(converted, skipObjects, isCreate);
247
+ }
248
+ return this.castDataFields(params, skipObjects, isCreate);
249
+ }
250
+
251
+ castDataFields(params, skipObjects = false, isCreate = false)
252
+ {
253
+ let data = {};
254
+ for(let key of Object.keys(params)){
255
+ let value = params[key];
256
+ if(isCreate && key === this.idFieldName && this.typeCaster.isNullOrEmpty(value)){
257
+ continue; // Skip ID field in create
258
+ }
259
+ // ... type casting logic
260
+ }
261
+ return data;
262
+ }
263
+ ```
264
+
265
+ **Behavior:**
266
+ - ❌ REJECTS explicit IDs for autoincrement fields (removed in prepareData)
267
+ - ✅ Returns created record with auto-generated ID
268
+ - ✅ Throws on constraint violations
269
+ - ⚠️ Converts FK columns to Prisma connect syntax
270
+
271
+ **Consistency Analysis:**
272
+ - ✅ All return created record with ID
273
+ - ✅ All throw on constraint violations
274
+ - ❌ **INCONSISTENT**: ObjectionJS and MikroORM accept explicit IDs, Prisma rejects them
275
+ - ⚠️ All three transform FK fields differently internally
276
+
277
+ **Tests Impact:**
278
+ - Basic CREATE tests work when NOT passing explicit IDs
279
+ - Prisma fails when fixtures contain explicit IDs
280
+
281
+ ---
282
+
283
+ ### 3.2 createWithRelations(params, relations)
284
+
285
+ **Purpose:** Create a record along with nested related records in a single operation
286
+
287
+ **Parameters:**
288
+ - `params` (object) - Parent record data with nested relation data
289
+ - `relations` (array) - Relation names to create (e.g., ['related_products'])
290
+
291
+ **Expected Behavior:**
292
+ - Create parent record
293
+ - Create child records
294
+ - Link child records to parent via FK
295
+ - Return parent record with relations populated
296
+
297
+ **Return:** Created parent record with nested relations
298
+
299
+ #### ObjectionJS Implementation (objection-js-driver.js:43-46)
300
+
301
+ ```javascript
302
+ createWithRelations(params, relations)
303
+ {
304
+ return this.queryBuilder().insertGraphAndFetch(params);
305
+ }
306
+ ```
307
+
308
+ **How it works:**
309
+ - Uses Objection's `insertGraphAndFetch()` method
310
+ - Recursively inserts parent and children
311
+ - Automatically handles FK relationships
312
+ - Returns complete graph with all IDs
313
+
314
+ **Example:**
315
+ ```javascript
316
+ await categoriesRepo.createWithRelations({
317
+ name: 'Electronics',
318
+ related_products: [
319
+ {name: 'Laptop', sku: 'LAP-001'}
320
+ ]
321
+ }, ['related_products']);
322
+ ```
323
+
324
+ **Behavior:**
325
+ - ✅ Supports nested CREATE (creates new children)
326
+ - ✅ Works with explicit IDs
327
+ - ✅ Works without explicit IDs
328
+ - ✅ Returns parent with populated relations
329
+
330
+ #### MikroORM Implementation (mikro-orm-driver.js:94-102)
331
+
332
+ ```javascript
333
+ async createWithRelations(params, relations)
334
+ {
335
+ if(!sc.isArray(relations) || 0 === relations.length){
336
+ relations = this.getAllRelations();
337
+ }
338
+ let newInstance = await this.create(params);
339
+ await this.createNested(newInstance, params, relations);
340
+ return await this.appendRelationsToCollection(newInstance, relations);
341
+ }
342
+ ```
343
+
344
+ **How it works:**
345
+ 1. Create parent via `this.create(params)` (ignores relation fields)
346
+ 2. Create nested children via `createNested()`
347
+ 3. Populate relations via `appendRelationsToCollection()`
348
+
349
+ **createNested() (lines 441-470):**
350
+ ```javascript
351
+ async createNested(newInstance, params, relations)
352
+ {
353
+ for(let relationName of Object.keys(properties)){
354
+ let prop = properties[relationName];
355
+ if(isOneToMany && sc.hasOwn(params, relationName) && sc.isArray(params[relationName])){
356
+ await this.createMany(params, relationName, relationDriver, newInstance, prop);
357
+ }
358
+ }
359
+ }
360
+ ```
361
+
362
+ **createMany() (lines 483-495):**
363
+ ```javascript
364
+ async createMany(params, relationName, relationDriver, newInstance, prop)
365
+ {
366
+ let nestedArray = [];
367
+ for(let objectData of params[relationName]){
368
+ if(prop.joinColumn){
369
+ objectData[prop.joinColumn] = newInstance.id; // Set FK to parent ID
370
+ }
371
+ let nestedObject = await relationDriver.create(objectData);
372
+ await this.orm.em.flush();
373
+ nestedArray.push(nestedObject);
374
+ }
375
+ newInstance[relationName] = nestedArray;
376
+ }
377
+ ```
378
+
379
+ **Behavior:**
380
+ - ✅ Supports nested CREATE (creates new children)
381
+ - ✅ Works with explicit IDs
382
+ - ⚠️ **BREAKS with delete operator** on properties (JIT compiler issue)
383
+ - ✅ Returns parent with populated relations
384
+
385
+ #### Prisma Implementation (prisma-driver.js:236-264)
386
+
387
+ ```javascript
388
+ async createWithRelations(params, relations)
389
+ {
390
+ if(!sc.isArray(relations) || 0 === relations.length){
391
+ relations = this.getAllRelations();
392
+ }
393
+ let preparedData = this.prepareData(params); // NOTE: no isCreate flag!
394
+ this.ensureRequiredFields(preparedData);
395
+ let createData = {data: preparedData};
396
+ if(0 < relations.length){
397
+ let includeData = this.relationResolver.buildIncludeObjectWithMapping(relations);
398
+ createData.include = includeData.include;
399
+ let relationData = this.relationResolver.buildCreateDataWithRelations(params, relations);
400
+ createData.data = {...createData.data, ...relationData};
401
+ return this.executeWithNormalization(
402
+ async () => this.relationResolver.transformRelationResults(
403
+ await this.model.create(createData),
404
+ includeData.include,
405
+ includeData.mapping
406
+ ),
407
+ 'Create with relations error: ',
408
+ false
409
+ );
410
+ }
411
+ return this.executeWithNormalization(
412
+ () => this.model.create(createData),
413
+ 'Create with relations error: ',
414
+ false
415
+ );
416
+ }
417
+ ```
418
+
419
+ **buildCreateDataWithRelations() (prisma-relation-resolver.js:234-263):**
420
+ ```javascript
421
+ buildCreateDataWithRelations(params, relations)
422
+ {
423
+ let createData = {};
424
+ for(let relation of relations){
425
+ let relationData = params[relation];
426
+ if(sc.isArray(relationData)){
427
+ createData[prismaName] = {
428
+ connect: relationData.map(item => ({id: this.typeCaster.castToIdType(item.id)}))
429
+ };
430
+ continue;
431
+ }
432
+ createData[prismaName] = {
433
+ connect: {id: this.typeCaster.castToIdType(relationData.id)}
434
+ };
435
+ }
436
+ return createData;
437
+ }
438
+ ```
439
+
440
+ **CRITICAL ISSUE:** Uses `connect` syntax, NOT `create` syntax!
441
+
442
+ **Prisma's nested CREATE syntax should be:**
443
+ ```javascript
444
+ {
445
+ data: {
446
+ name: 'Electronics',
447
+ related_products: {
448
+ create: [ // ← Should use "create"
449
+ {name: 'Laptop', sku: 'LAP-001'}
450
+ ]
451
+ }
452
+ }
453
+ }
454
+ ```
455
+
456
+ **What our code does:**
457
+ ```javascript
458
+ {
459
+ data: {
460
+ name: 'Electronics',
461
+ related_products: {
462
+ connect: [ // ← WRONG: Tries to link existing records
463
+ {id: undefined} // ← Fails because no ID
464
+ ]
465
+ }
466
+ }
467
+ }
468
+ ```
469
+
470
+ **Behavior:**
471
+ - ❌ **DOES NOT support nested CREATE**
472
+ - ❌ Only supports CONNECT (linking existing records)
473
+ - ❌ Requires child records to already exist with IDs
474
+ - ❌ Current implementation is broken
475
+
476
+ **Consistency Analysis:**
477
+ - ✅ ObjectionJS: Full nested CREATE support
478
+ - ⚠️ MikroORM: Full nested CREATE support BUT breaks with delete operator
479
+ - ❌ Prisma: NO nested CREATE support - only CONNECT
480
+ - **MAJOR INCONSISTENCY** - Method doesn't work the same across drivers
481
+
482
+ ---
483
+
484
+ ### 3.3 update(filters, updatePatch)
485
+
486
+ **Purpose:** Update multiple records matching filters
487
+
488
+ **Parameters:**
489
+ - `filters` (object) - Filter conditions
490
+ - `updatePatch` (object) - Fields to update
491
+
492
+ **Expected Behavior:**
493
+ - Find all records matching filters
494
+ - Update specified fields
495
+ - Return updated records (or result indicator)
496
+
497
+ **Return:** Updated records or result object
498
+
499
+ #### ObjectionJS Implementation (objection-js-driver.js:48-53)
500
+
501
+ ```javascript
502
+ update(filters, updatePatch)
503
+ {
504
+ let queryBuilder = this.queryBuilder(true, true, true);
505
+ this.appendFilters(queryBuilder, filters);
506
+ return queryBuilder.patch(updatePatch);
507
+ }
508
+ ```
509
+
510
+ **How it works:**
511
+ - Builds query with limit/offset/sort
512
+ - Appends filters via `appendFilters()`
513
+ - Calls Knex `patch()` method
514
+ - Returns number of affected rows
515
+
516
+ **Return type:** Number (count of updated rows)
517
+
518
+ #### MikroORM Implementation (mikro-orm-driver.js:119-123)
519
+
520
+ ```javascript
521
+ async update(filters, updatePatch)
522
+ {
523
+ let processedFilters = this.processFilters(filters);
524
+ return this.applyUpdateToEntities(processedFilters, updatePatch);
525
+ }
526
+
527
+ async applyUpdateToEntities(processedFilters, updatePatch)
528
+ {
529
+ let entities = await this.repository.find(processedFilters, this.queryBuilder(true, true, true));
530
+ if(0 === entities.length){
531
+ return false;
532
+ }
533
+ let transformed = this.transformFkToRelations(updatePatch);
534
+ for(let entity of entities){
535
+ Object.assign(entity, transformed);
536
+ await this.orm.em.upsert(entity);
537
+ await this.orm.em.flush();
538
+ }
539
+ return entities;
540
+ }
541
+ ```
542
+
543
+ **How it works:**
544
+ 1. Find entities matching filters
545
+ 2. Transform FK fields to relations
546
+ 3. Update each entity via Object.assign
547
+ 4. Upsert and flush each entity
548
+ 5. Return array of updated entities
549
+
550
+ **Return type:** Array of entity objects OR false
551
+
552
+ #### Prisma Implementation (prisma-driver.js:266-275)
553
+
554
+ ```javascript
555
+ async update(filters, updatePatch)
556
+ {
557
+ let preparedData = this.prepareData(updatePatch, {skipObjects: true});
558
+ let processedFilters = this.filterProcessor.processFilters(filters);
559
+ return this.executeWithNormalization(
560
+ () => this.model.updateMany({where: processedFilters, data: preparedData}),
561
+ 'Update error: ',
562
+ false
563
+ );
564
+ }
565
+ ```
566
+
567
+ **How it works:**
568
+ - Prepare data (skip object fields, convert relations)
569
+ - Process filters
570
+ - Call Prisma's `updateMany()`
571
+ - Normalize return (count of updated rows)
572
+
573
+ **Return type:** Object with `count` property OR false
574
+
575
+ **Consistency Analysis:**
576
+ - ❌ **RETURN TYPE INCONSISTENT**:
577
+ - ObjectionJS: Number (count)
578
+ - MikroORM: Array of entities OR false
579
+ - Prisma: Object {count: N} OR false
580
+ - ✅ All update multiple records
581
+ - ✅ All respect filters
582
+
583
+ **Tests Impact:**
584
+ - Tests check for truthy return value (`assert.ok(updated)`)
585
+ - Works for all drivers despite different return types
586
+ - MikroORM returns full entities (more data than needed)
587
+
588
+ ---
589
+
590
+ ### 3.4 updateById(id, params)
591
+
592
+ **Purpose:** Update single record by ID
593
+
594
+ **Parameters:**
595
+ - `id` - Record ID
596
+ - `params` (object) - Fields to update
597
+
598
+ **Expected Behavior:**
599
+ - Find record by ID
600
+ - Update specified fields
601
+ - Return updated record
602
+
603
+ **Return:** Updated record object
604
+
605
+ #### ObjectionJS Implementation (objection-js-driver.js:62-65)
606
+
607
+ ```javascript
608
+ updateById(id, params)
609
+ {
610
+ return this.queryBuilder().patchAndFetchById(id, params);
611
+ }
612
+ ```
613
+
614
+ **How it works:**
615
+ - Uses Objection's `patchAndFetchById()` method
616
+ - Updates and returns updated record in one operation
617
+
618
+ **Return type:** Updated record object
619
+
620
+ #### MikroORM Implementation (mikro-orm-driver.js:132-135)
621
+
622
+ ```javascript
623
+ updateById(id, params)
624
+ {
625
+ return this.update({id}, params);
626
+ }
627
+ ```
628
+
629
+ **How it works:**
630
+ - Delegates to `update()` with `{id}` filter
631
+ - Returns array of entities (from update())
632
+
633
+ **Return type:** Array with single entity OR false
634
+
635
+ #### Prisma Implementation (prisma-driver.js:288-301)
636
+
637
+ ```javascript
638
+ async updateById(id, params)
639
+ {
640
+ let castedId = this.typeCaster.castToIdType(id);
641
+ let found = await this.loadById(castedId);
642
+ if(!found){
643
+ return false;
644
+ }
645
+ let preparedData = this.prepareData(params, {convertRelations: true});
646
+ return this.executeWithNormalization(
647
+ () => this.model.update({where: {[this.idFieldName]: castedId}, data: preparedData}),
648
+ 'Update by ID error: ',
649
+ false
650
+ );
651
+ }
652
+ ```
653
+
654
+ **How it works:**
655
+ 1. Cast ID to correct type
656
+ 2. Check if record exists via `loadById()`
657
+ 3. Return false if not found
658
+ 4. Prepare data with relation conversion
659
+ 5. Call Prisma's `update()`
660
+ 6. Return updated record
661
+
662
+ **Return type:** Updated record object OR false
663
+
664
+ **Consistency Analysis:**
665
+ - ⚠️ **RETURN TYPE INCONSISTENT**:
666
+ - ObjectionJS: Record object
667
+ - MikroORM: Array [record] OR false
668
+ - Prisma: Record object OR false
669
+ - ✅ All update by ID
670
+ - ⚠️ MikroORM returns array instead of single object
671
+
672
+ **Tests Impact:**
673
+ - Tests expect single object: `assert.strictEqual(updated.name, 'Updated Name')`
674
+ - **MikroORM tests FAIL** because it returns array
675
+ - Need to check test logs for MikroORM updateById failures
676
+
677
+ ---
678
+
679
+ ### 3.5 updateBy(field, fieldValue, updatePatch, operator)
680
+
681
+ **Purpose:** Update multiple records matching a single field condition
682
+
683
+ **Parameters:**
684
+ - `field` (string) - Field name to filter by
685
+ - `fieldValue` - Value to match
686
+ - `updatePatch` (object) - Fields to update
687
+ - `operator` (string, optional) - Comparison operator (GT, LT, LIKE, etc.)
688
+
689
+ **Expected Behavior:**
690
+ - Find records where field matches value (with optional operator)
691
+ - Update specified fields
692
+ - Return updated records
693
+
694
+ **Return:** Updated records or result indicator
695
+
696
+ #### ObjectionJS Implementation (objection-js-driver.js:55-60)
697
+
698
+ ```javascript
699
+ updateBy(field, fieldValue, updatePatch, operator = null)
700
+ {
701
+ let queryBuilder = this.queryBuilder(true, true, true);
702
+ this.appendSingleFilter(queryBuilder, field, fieldValue, operator);
703
+ return queryBuilder.patch(updatePatch);
704
+ }
705
+ ```
706
+
707
+ **Return type:** Number (count of updated rows)
708
+
709
+ #### MikroORM Implementation (mikro-orm-driver.js:125-130)
710
+
711
+ ```javascript
712
+ async updateBy(field, fieldValue, updatePatch, operator = null)
713
+ {
714
+ let filter = this.createSingleFilter(field, fieldValue, operator);
715
+ let processedFilters = this.processFilters(filter);
716
+ return this.applyUpdateToEntities(processedFilters, updatePatch);
717
+ }
718
+ ```
719
+
720
+ **Return type:** Array of entities OR false
721
+
722
+ #### Prisma Implementation (prisma-driver.js:277-286)
723
+
724
+ ```javascript
725
+ async updateBy(field, fieldValue, updatePatch, operator = null)
726
+ {
727
+ let filter = this.filterProcessor.createSingleFilter(field, fieldValue, operator);
728
+ let preparedData = this.prepareData(updatePatch, {skipObjects: true});
729
+ return this.executeWithNormalization(
730
+ () => this.model.updateMany({where: filter, data: preparedData}),
731
+ 'Update by error: ',
732
+ false
733
+ );
734
+ }
735
+ ```
736
+
737
+ **Return type:** Object {count: N} OR false
738
+
739
+ **Consistency Analysis:**
740
+ - ❌ **RETURN TYPE INCONSISTENT** (same as update())
741
+ - ✅ All support operator parameter
742
+ - ✅ All update multiple records
743
+
744
+ ---
745
+
746
+ ### 3.6 upsert(params, filters)
747
+
748
+ **Purpose:** Insert if record doesn't exist, update if it does
749
+
750
+ **Parameters:**
751
+ - `params` (object) - Record data (with or without ID)
752
+ - `filters` (object, optional) - Alternate filters to find existing record
753
+
754
+ **Expected Behavior:**
755
+ - If params.id exists and record found: UPDATE by ID
756
+ - Else if filters provided and record found: UPDATE by filters
757
+ - Else: CREATE new record
758
+
759
+ **Return:** Updated or created record
760
+
761
+ #### ObjectionJS Implementation (objection-js-driver.js:67-82)
762
+
763
+ ```javascript
764
+ async upsert(params, filters)
765
+ {
766
+ if(params.id){
767
+ let existent = await this.loadById(params.id);
768
+ if(existent){
769
+ return this.updateById(params.id, params);
770
+ }
771
+ }
772
+ if(filters){
773
+ let existent = await this.loadOne(filters);
774
+ if(existent){
775
+ return this.updateById(existent.id, params);
776
+ }
777
+ }
778
+ return this.create(params);
779
+ }
780
+ ```
781
+
782
+ **Logic:**
783
+ 1. Check if record exists by ID
784
+ 2. Check if record exists by filters
785
+ 3. If found: update, else: create
786
+
787
+ **Return type:** Record object
788
+
789
+ #### MikroORM Implementation (mikro-orm-driver.js:137-152)
790
+
791
+ ```javascript
792
+ async upsert(params, filters)
793
+ {
794
+ if(params.id){
795
+ let patch = Object.assign({}, params);
796
+ delete patch.id;
797
+ return this.updateById(params.id, patch);
798
+ }
799
+ if(filters){
800
+ let existent = await this.loadOne(filters);
801
+ if(existent){
802
+ let transformed = this.transformFkToRelations(params);
803
+ return this.updateById(existent.id, transformed);
804
+ }
805
+ }
806
+ return this.create(params);
807
+ }
808
+ ```
809
+
810
+ **Logic:**
811
+ 1. If params.id: update by ID (removes ID from patch)
812
+ 2. Check if record exists by filters
813
+ 3. If found: update, else: create
814
+
815
+ **Return type:** Array [record] OR Record (from updateById or create)
816
+
817
+ #### Prisma Implementation (prisma-driver.js:303-334)
818
+
819
+ ```javascript
820
+ async upsert(params, filters)
821
+ {
822
+ let preparedData = this.prepareData(params, {convertRelations: true});
823
+ let idValue = params[this.idFieldName];
824
+ if(idValue){
825
+ let castedId = this.typeCaster.castToIdType(idValue);
826
+ let existing = await this.loadById(castedId);
827
+ if(existing){
828
+ let patch = Object.assign({}, preparedData);
829
+ delete patch[this.idFieldName];
830
+ return this.executeWithNormalization(
831
+ () => this.model.update({where: {[this.idFieldName]: castedId}, data: patch}),
832
+ 'Upsert update error: ',
833
+ false
834
+ );
835
+ }
836
+ }
837
+ if(filters){
838
+ let existing = await this.loadOne(filters);
839
+ if(existing){
840
+ return this.executeWithNormalization(
841
+ () => this.model.update({
842
+ where: {[this.idFieldName]: this.typeCaster.castToIdType(existing[this.idFieldName])},
843
+ data: preparedData
844
+ }),
845
+ 'Upsert update by filter error: ',
846
+ false
847
+ );
848
+ }
849
+ }
850
+ return await this.create(preparedData);
851
+ }
852
+ ```
853
+
854
+ **Logic:** Same as ObjectionJS
855
+
856
+ **Return type:** Record object OR false
857
+
858
+ **Consistency Analysis:**
859
+ - ✅ All have same logic flow
860
+ - ⚠️ MikroORM has inconsistent return (array from updateById)
861
+ - ✅ All support ID-based and filter-based detection
862
+
863
+ ---
864
+
865
+ ### 3.7 delete(filters)
866
+
867
+ **Purpose:** Delete multiple records matching filters
868
+
869
+ **Parameters:**
870
+ - `filters` (object) - Filter conditions
871
+
872
+ **Expected Behavior:**
873
+ - Find all records matching filters
874
+ - Delete them
875
+ - Return success indicator
876
+
877
+ **Return:** Boolean or count
878
+
879
+ #### ObjectionJS Implementation (objection-js-driver.js:84-88)
880
+
881
+ ```javascript
882
+ delete(filters)
883
+ {
884
+ let queryBuilder = this.queryBuilder(true, true, true);
885
+ return this.appendFilters(queryBuilder, filters).delete();
886
+ }
887
+ ```
888
+
889
+ **Return type:** Number (count of deleted rows)
890
+
891
+ #### MikroORM Implementation (mikro-orm-driver.js:154-166)
892
+
893
+ ```javascript
894
+ async delete(filters = {})
895
+ {
896
+ let processedFilters = this.processFilters(filters);
897
+ let entries = await this.repository.find(processedFilters);
898
+ if(!entries || 0 === entries.length){
899
+ return false;
900
+ }
901
+ for(let entry of entries){
902
+ await this.orm.em.remove(entry);
903
+ }
904
+ await this.orm.em.flush();
905
+ return true;
906
+ }
907
+ ```
908
+
909
+ **Return type:** Boolean (true or false)
910
+
911
+ #### Prisma Implementation (prisma-driver.js:336-344)
912
+
913
+ ```javascript
914
+ async delete(filters = {})
915
+ {
916
+ let processedFilters = this.filterProcessor.processFilters(filters);
917
+ return this.executeWithNormalization(
918
+ () => this.model.deleteMany({where: processedFilters}),
919
+ 'Delete error: ',
920
+ false
921
+ );
922
+ }
923
+ ```
924
+
925
+ **Return type:** Object {count: N} OR false
926
+
927
+ **Consistency Analysis:**
928
+ - ❌ **RETURN TYPE INCONSISTENT**:
929
+ - ObjectionJS: Number
930
+ - MikroORM: Boolean
931
+ - Prisma: Object {count: N} OR false
932
+ - ✅ All delete multiple records
933
+ - ✅ All support filters
934
+
935
+ ---
936
+
937
+ ### 3.8 deleteById(id)
938
+
939
+ **Purpose:** Delete single record by ID
940
+
941
+ **Parameters:**
942
+ - `id` - Record ID
943
+
944
+ **Expected Behavior:**
945
+ - Find record by ID
946
+ - Delete it
947
+ - Return success indicator
948
+
949
+ **Return:** Boolean or result object
950
+
951
+ #### ObjectionJS Implementation (objection-js-driver.js:90-93)
952
+
953
+ ```javascript
954
+ deleteById(id)
955
+ {
956
+ return this.queryBuilder().deleteById(id);
957
+ }
958
+ ```
959
+
960
+ **Return type:** Number (1 if deleted, 0 if not found)
961
+
962
+ #### MikroORM Implementation (mikro-orm-driver.js:168-177)
963
+
964
+ ```javascript
965
+ async deleteById(id)
966
+ {
967
+ let entity = await this.loadById(id);
968
+ if(!entity){
969
+ return false;
970
+ }
971
+ await this.orm.em.remove(entity);
972
+ await this.orm.em.flush();
973
+ return true;
974
+ }
975
+ ```
976
+
977
+ **Return type:** Boolean (true or false)
978
+
979
+ #### Prisma Implementation (prisma-driver.js:346-358)
980
+
981
+ ```javascript
982
+ async deleteById(id)
983
+ {
984
+ let castedId = this.typeCaster.castToIdType(id);
985
+ let found = await this.loadById(castedId);
986
+ if(!found){
987
+ return false;
988
+ }
989
+ return this.executeWithNormalization(
990
+ () => this.model.delete({where: {[this.idFieldName]: castedId}}),
991
+ 'Delete by ID error: ',
992
+ false
993
+ );
994
+ }
995
+ ```
996
+
997
+ **Return type:** Record object (deleted record) OR false
998
+
999
+ **Consistency Analysis:**
1000
+ - ❌ **RETURN TYPE INCONSISTENT**:
1001
+ - ObjectionJS: Number
1002
+ - MikroORM: Boolean
1003
+ - Prisma: Record object OR false
1004
+ - ✅ All delete by ID
1005
+ - ✅ All return falsy when not found
1006
+
1007
+ ---
1008
+
1009
+ ### 3.9 count(filters)
1010
+
1011
+ **Purpose:** Count records matching filters
1012
+
1013
+ **Parameters:**
1014
+ - `filters` (object) - Filter conditions
1015
+
1016
+ **Expected Behavior:**
1017
+ - Count records matching filters
1018
+ - Return count as number
1019
+
1020
+ **Return:** Number
1021
+
1022
+ #### ObjectionJS Implementation (objection-js-driver.js:95-101)
1023
+
1024
+ ```javascript
1025
+ async count(filters)
1026
+ {
1027
+ let queryBuilder = this.queryBuilder(true, true, true);
1028
+ this.appendFilters(queryBuilder, filters);
1029
+ let count = await queryBuilder.count().first();
1030
+ return count ? count['count(*)'] : 0;
1031
+ }
1032
+ ```
1033
+
1034
+ **Return type:** Number
1035
+
1036
+ #### MikroORM Implementation (mikro-orm-driver.js:179-183)
1037
+
1038
+ ```javascript
1039
+ async count(filters)
1040
+ {
1041
+ let processedFilters = this.processFilters(filters);
1042
+ return this.repository.count(processedFilters);
1043
+ }
1044
+ ```
1045
+
1046
+ **Return type:** Number
1047
+
1048
+ #### Prisma Implementation (prisma-driver.js:360-368)
1049
+
1050
+ ```javascript
1051
+ async count(filters)
1052
+ {
1053
+ let processedFilters = this.filterProcessor.processFilters(filters);
1054
+ return this.executeWithNormalization(
1055
+ () => this.model.count({where: processedFilters}),
1056
+ 'Count error: ',
1057
+ 0
1058
+ );
1059
+ }
1060
+ ```
1061
+
1062
+ **Return type:** Number
1063
+
1064
+ **Consistency Analysis:**
1065
+ - ✅ **ALL CONSISTENT** - Return number
1066
+ - ✅ All count by filters
1067
+ - ✅ All return 0 on error
1068
+
1069
+ ---
1070
+
1071
+ ### 3.10 countWithRelations(filters, relations)
1072
+
1073
+ **Purpose:** Count records with relation-based filters
1074
+
1075
+ **Parameters:**
1076
+ - `filters` (object) - Filter conditions
1077
+ - `relations` (array) - Relations to include in query
1078
+
1079
+ **Expected Behavior:**
1080
+ - Count records matching filters
1081
+ - Consider relation joins in count
1082
+ - Return count as number
1083
+
1084
+ **Return:** Number
1085
+
1086
+ #### ObjectionJS Implementation (objection-js-driver.js:103-109)
1087
+
1088
+ ```javascript
1089
+ async countWithRelations(filters, relations)
1090
+ {
1091
+ let queryBuilder = this.queryBuilder(true, true, true);
1092
+ this.appendFilters(queryBuilder, filters);
1093
+ let count = await this.appendRelationsToQuery(queryBuilder, relations).count().first();
1094
+ return count ? count['count(*)'] : 0;
1095
+ }
1096
+ ```
1097
+
1098
+ **How it works:**
1099
+ - Builds query with relations joined
1100
+ - Counts distinct parent records
1101
+
1102
+ **Return type:** Number
1103
+
1104
+ #### MikroORM Implementation (mikro-orm-driver.js:185-222)
1105
+
1106
+ ```javascript
1107
+ async countWithRelations(filters, relations)
1108
+ {
1109
+ if(!sc.isArray(relations) || 0 === relations.length){
1110
+ relations = this.getAllRelations();
1111
+ }
1112
+ let processedFilters = this.processFilters(filters);
1113
+ if(0 === relations.length || !this.rawModel.meta || !this.rawModel.meta.properties){
1114
+ return this.repository.count(processedFilters);
1115
+ }
1116
+ let queryBuilder = this.orm.em.createQueryBuilder(this.rawModel);
1117
+ let properties = this.rawModel.meta.properties;
1118
+ let aliasCounter = 0;
1119
+ for(let relationName of relations){
1120
+ let prop = properties[relationName];
1121
+ if(!prop || !prop.kind || (prop.kind !== 'm:1' && prop.kind !== '1:m')){
1122
+ continue;
1123
+ }
1124
+ // ... builds LEFT JOINs for relations
1125
+ }
1126
+ if(sc.isObject(processedFilters) && 0 < Object.keys(processedFilters).length){
1127
+ queryBuilder.where(processedFilters);
1128
+ }
1129
+ return queryBuilder.getCount();
1130
+ }
1131
+ ```
1132
+
1133
+ **How it works:**
1134
+ - Manually builds LEFT JOINs for each relation
1135
+ - Counts with relations joined
1136
+
1137
+ **Return type:** Number
1138
+
1139
+ #### Prisma Implementation (prisma-driver.js:370-385)
1140
+
1141
+ ```javascript
1142
+ async countWithRelations(filters, relations)
1143
+ {
1144
+ if(!sc.isArray(relations) || 0 === relations.length){
1145
+ relations = this.getAllRelations();
1146
+ }
1147
+ let processedFilters = this.filterProcessor.processFilters(filters);
1148
+ let query = {where: processedFilters};
1149
+ if(0 < relations.length){
1150
+ query.include = this.relationResolver.buildIncludeObjectWithMapping(relations).include;
1151
+ }
1152
+ return this.executeWithNormalization(
1153
+ () => this.model.count(query),
1154
+ 'Count with relations error: ',
1155
+ 0
1156
+ );
1157
+ }
1158
+ ```
1159
+
1160
+ **How it works:**
1161
+ - Builds include object for relations
1162
+ - Calls Prisma count with include
1163
+
1164
+ **Return type:** Number
1165
+
1166
+ **Consistency Analysis:**
1167
+ - ✅ **ALL CONSISTENT** - Return number
1168
+ - ✅ All count with relations joined
1169
+ - ⚠️ Prisma's include in count may not work correctly (Prisma doesn't filter by relations in count)
1170
+
1171
+ ---
1172
+
1173
+ ### 3.11 loadAll()
1174
+
1175
+ **Purpose:** Load ALL records from table (no filters)
1176
+
1177
+ **Parameters:** None
1178
+
1179
+ **Expected Behavior:**
1180
+ - Load all records
1181
+ - Respect limit/offset/sort properties
1182
+ - Return array of records
1183
+
1184
+ **Return:** Array of records
1185
+
1186
+ #### ObjectionJS Implementation (objection-js-driver.js:111-114)
1187
+
1188
+ ```javascript
1189
+ loadAll()
1190
+ {
1191
+ return this.queryBuilder();
1192
+ }
1193
+ ```
1194
+
1195
+ **Return type:** Promise<Array>
1196
+
1197
+ #### MikroORM Implementation (mikro-orm-driver.js:224-227)
1198
+
1199
+ ```javascript
1200
+ loadAll()
1201
+ {
1202
+ return this.repository.findAll();
1203
+ }
1204
+ ```
1205
+
1206
+ **Return type:** Promise<Array>
1207
+
1208
+ #### Prisma Implementation (prisma-driver.js:387-394)
1209
+
1210
+ ```javascript
1211
+ async loadAll()
1212
+ {
1213
+ return this.executeWithNormalization(
1214
+ () => this.model.findMany(this.queryBuilder.buildQueryOptions(this.getQueryOptions())),
1215
+ 'Load all error: ',
1216
+ []
1217
+ );
1218
+ }
1219
+ ```
1220
+
1221
+ **Return type:** Promise<Array>
1222
+
1223
+ **Consistency Analysis:**
1224
+ - ✅ **ALL CONSISTENT** - Return array
1225
+ - ✅ All load all records
1226
+ - ✅ All respect limit/offset/sort
1227
+
1228
+ ---
1229
+
1230
+ ### 3.12 loadAllWithRelations(relations)
1231
+
1232
+ **Purpose:** Load ALL records with relations
1233
+
1234
+ **Parameters:**
1235
+ - `relations` (array) - Relations to load
1236
+
1237
+ **Expected Behavior:**
1238
+ - Load all records
1239
+ - Populate specified relations
1240
+ - Respect limit/offset/sort
1241
+ - Return array with relations
1242
+
1243
+ **Return:** Array of records with relations populated
1244
+
1245
+ #### ObjectionJS Implementation (objection-js-driver.js:116-119)
1246
+
1247
+ ```javascript
1248
+ loadAllWithRelations(relations)
1249
+ {
1250
+ return this.appendRelationsToQuery(this.queryBuilder(), relations);
1251
+ }
1252
+ ```
1253
+
1254
+ **Return type:** Promise<Array>
1255
+
1256
+ #### MikroORM Implementation (mikro-orm-driver.js:229-236)
1257
+
1258
+ ```javascript
1259
+ async loadAllWithRelations(relations)
1260
+ {
1261
+ if(!sc.isArray(relations) || 0 === relations.length){
1262
+ relations = this.getAllRelations();
1263
+ }
1264
+ let entities = await this.loadAll();
1265
+ return await this.appendRelationsToCollection(entities, relations);
1266
+ }
1267
+ ```
1268
+
1269
+ **Return type:** Promise<Array>
1270
+
1271
+ #### Prisma Implementation (prisma-driver.js:396-405)
1272
+
1273
+ ```javascript
1274
+ async loadAllWithRelations(relations)
1275
+ {
1276
+ return this.executeQueryWithRelations(
1277
+ (q) => this.model.findMany(q),
1278
+ this.queryBuilder.buildQueryOptions(this.getQueryOptions()),
1279
+ relations,
1280
+ 'Load all with relations error: ',
1281
+ []
1282
+ );
1283
+ }
1284
+ ```
1285
+
1286
+ **Return type:** Promise<Array>
1287
+
1288
+ **Consistency Analysis:**
1289
+ - ✅ **ALL CONSISTENT** - Return array with relations
1290
+ - ✅ All load all records with relations
1291
+ - ✅ All respect limit/offset/sort
1292
+
1293
+ ---
1294
+
1295
+ ### 3.13 load(filters)
1296
+
1297
+ **Purpose:** Load records matching filters
1298
+
1299
+ **Parameters:**
1300
+ - `filters` (object) - Filter conditions
1301
+
1302
+ **Expected Behavior:**
1303
+ - Load records matching filters
1304
+ - Respect limit/offset/sort properties
1305
+ - Return array of records
1306
+
1307
+ **Return:** Array of records
1308
+
1309
+ #### ObjectionJS Implementation (objection-js-driver.js:121-126)
1310
+
1311
+ ```javascript
1312
+ load(filters)
1313
+ {
1314
+ let queryBuilder = this.queryBuilder(true, true, true);
1315
+ this.appendFilters(queryBuilder, filters);
1316
+ return queryBuilder;
1317
+ }
1318
+ ```
1319
+
1320
+ **Return type:** Promise<Array>
1321
+
1322
+ #### MikroORM Implementation (mikro-orm-driver.js:238-242)
1323
+
1324
+ ```javascript
1325
+ load(filters)
1326
+ {
1327
+ let processedFilters = this.processFilters(filters);
1328
+ return this.repository.find(processedFilters, this.queryBuilder(true, true, true));
1329
+ }
1330
+ ```
1331
+
1332
+ **Return type:** Promise<Array>
1333
+
1334
+ #### Prisma Implementation (prisma-driver.js:407-417)
1335
+
1336
+ ```javascript
1337
+ async load(filters)
1338
+ {
1339
+ let processedFilters = this.filterProcessor.processFilters(filters);
1340
+ let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions());
1341
+ query.where = processedFilters;
1342
+ return this.executeWithNormalization(
1343
+ () => this.model.findMany(query),
1344
+ 'Load error: ',
1345
+ []
1346
+ );
1347
+ }
1348
+ ```
1349
+
1350
+ **Return type:** Promise<Array>
1351
+
1352
+ **Consistency Analysis:**
1353
+ - ✅ **ALL CONSISTENT** - Return array
1354
+ - ✅ All load by filters
1355
+ - ✅ All respect limit/offset/sort
1356
+
1357
+ ---
1358
+
1359
+ ### 3.14 loadWithRelations(filters, relations)
1360
+
1361
+ **Purpose:** Load records matching filters with relations
1362
+
1363
+ **Parameters:**
1364
+ - `filters` (object) - Filter conditions
1365
+ - `relations` (array) - Relations to load
1366
+
1367
+ **Expected Behavior:**
1368
+ - Load records matching filters
1369
+ - Populate specified relations
1370
+ - Respect limit/offset/sort
1371
+ - Return array with relations
1372
+
1373
+ **Return:** Array of records with relations populated
1374
+
1375
+ #### ObjectionJS Implementation (objection-js-driver.js:128-133)
1376
+
1377
+ ```javascript
1378
+ loadWithRelations(filters, relations)
1379
+ {
1380
+ let queryBuilder = this.queryBuilder(true, true, true);
1381
+ this.appendFilters(queryBuilder, filters);
1382
+ return this.appendRelationsToQuery(queryBuilder, relations);
1383
+ }
1384
+ ```
1385
+
1386
+ **Return type:** Promise<Array>
1387
+
1388
+ #### MikroORM Implementation (mikro-orm-driver.js:244-252)
1389
+
1390
+ ```javascript
1391
+ async loadWithRelations(filters, relations)
1392
+ {
1393
+ if(!sc.isArray(relations) || 0 === relations.length){
1394
+ relations = this.getAllRelations();
1395
+ }
1396
+ let processedFilters = this.processFilters(filters);
1397
+ let entitiesCollection = await this.repository.find(processedFilters, this.queryBuilder(true, true, true));
1398
+ return await this.appendRelationsToCollection(entitiesCollection, relations);
1399
+ }
1400
+ ```
1401
+
1402
+ **Return type:** Promise<Array>
1403
+
1404
+ #### Prisma Implementation (prisma-driver.js:419-430)
1405
+
1406
+ ```javascript
1407
+ async loadWithRelations(filters, relations)
1408
+ {
1409
+ let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions());
1410
+ query.where = this.filterProcessor.processFilters(filters);
1411
+ return this.executeQueryWithRelations(
1412
+ (q) => this.model.findMany(q),
1413
+ query,
1414
+ relations,
1415
+ 'Load with relations error: ',
1416
+ []
1417
+ );
1418
+ }
1419
+ ```
1420
+
1421
+ **Return type:** Promise<Array>
1422
+
1423
+ **Consistency Analysis:**
1424
+ - ✅ **ALL CONSISTENT** - Return array with relations
1425
+ - ✅ All load by filters with relations
1426
+ - ✅ All respect limit/offset/sort
1427
+
1428
+ ---
1429
+
1430
+ ### 3.15 loadById(id)
1431
+
1432
+ **Purpose:** Load single record by ID
1433
+
1434
+ **Parameters:**
1435
+ - `id` - Record ID
1436
+
1437
+ **Expected Behavior:**
1438
+ - Find record by ID
1439
+ - Return record or null
1440
+
1441
+ **Return:** Record object or null
1442
+
1443
+ #### ObjectionJS Implementation (objection-js-driver.js:149-152)
1444
+
1445
+ ```javascript
1446
+ loadById(id)
1447
+ {
1448
+ return this.queryBuilder().findById(id);
1449
+ }
1450
+ ```
1451
+
1452
+ **Return type:** Promise<Record | null>
1453
+
1454
+ #### MikroORM Implementation (mikro-orm-driver.js:272-275)
1455
+
1456
+ ```javascript
1457
+ loadById(id)
1458
+ {
1459
+ return this.loadOneBy('id', id);
1460
+ }
1461
+ ```
1462
+
1463
+ **Return type:** Promise<Record | null>
1464
+
1465
+ #### Prisma Implementation (prisma-driver.js:457-465)
1466
+
1467
+ ```javascript
1468
+ async loadById(id)
1469
+ {
1470
+ let castedId = this.typeCaster.castToIdType(id);
1471
+ return this.executeWithNormalization(
1472
+ () => this.model.findUnique({where: {[this.idFieldName]: castedId}}),
1473
+ 'Load by ID error: ',
1474
+ null
1475
+ );
1476
+ }
1477
+ ```
1478
+
1479
+ **Return type:** Promise<Record | null>
1480
+
1481
+ **Consistency Analysis:**
1482
+ - ✅ **ALL CONSISTENT** - Return record or null
1483
+ - ✅ All load by ID
1484
+ - ✅ All return null when not found
1485
+
1486
+ ---
1487
+
1488
+ ### 3.16 loadByIdWithRelations(id, relations)
1489
+
1490
+ **Purpose:** Load single record by ID with relations
1491
+
1492
+ **Parameters:**
1493
+ - `id` - Record ID
1494
+ - `relations` (array) - Relations to load
1495
+
1496
+ **Expected Behavior:**
1497
+ - Find record by ID
1498
+ - Populate specified relations
1499
+ - Return record with relations or null
1500
+
1501
+ **Return:** Record object with relations or null
1502
+
1503
+ #### ObjectionJS Implementation (objection-js-driver.js:154-157)
1504
+
1505
+ ```javascript
1506
+ loadByIdWithRelations(id, relations)
1507
+ {
1508
+ return this.appendRelationsToQuery(this.queryBuilder().findById(id), relations);
1509
+ }
1510
+ ```
1511
+
1512
+ **Return type:** Promise<Record | null>
1513
+
1514
+ #### MikroORM Implementation (mikro-orm-driver.js:277-284)
1515
+
1516
+ ```javascript
1517
+ async loadByIdWithRelations(id, relations)
1518
+ {
1519
+ if(!sc.isArray(relations) || 0 === relations.length){
1520
+ relations = this.getAllRelations();
1521
+ }
1522
+ let entity = await this.loadOneBy('id', id);
1523
+ return await this.appendRelationsToCollection(entity, relations);
1524
+ }
1525
+ ```
1526
+
1527
+ **Return type:** Promise<Record | null>
1528
+
1529
+ #### Prisma Implementation (prisma-driver.js:467-476)
1530
+
1531
+ ```javascript
1532
+ async loadByIdWithRelations(id, relations)
1533
+ {
1534
+ return this.executeQueryWithRelations(
1535
+ (q) => this.model.findUnique(q),
1536
+ {where: {[this.idFieldName]: this.typeCaster.castToIdType(id)}},
1537
+ relations,
1538
+ 'Load by ID with relations error: ',
1539
+ null
1540
+ );
1541
+ }
1542
+ ```
1543
+
1544
+ **Return type:** Promise<Record | null>
1545
+
1546
+ **Consistency Analysis:**
1547
+ - ✅ **ALL CONSISTENT** - Return record with relations or null
1548
+ - ✅ All load by ID with relations
1549
+ - ✅ All return null when not found
1550
+
1551
+ ---
1552
+
1553
+ ### 3.17 loadOne(filters)
1554
+
1555
+ **Purpose:** Load first record matching filters
1556
+
1557
+ **Parameters:**
1558
+ - `filters` (object) - Filter conditions
1559
+
1560
+ **Expected Behavior:**
1561
+ - Find first record matching filters
1562
+ - Respect offset/sort (not limit)
1563
+ - Return single record or null
1564
+
1565
+ **Return:** Record object or null
1566
+
1567
+ #### ObjectionJS Implementation (objection-js-driver.js:164-169)
1568
+
1569
+ ```javascript
1570
+ loadOne(filters)
1571
+ {
1572
+ let queryBuilder = this.queryBuilder(false, true, true);
1573
+ this.appendFilters(queryBuilder, filters);
1574
+ return queryBuilder.limit(1).first();
1575
+ }
1576
+ ```
1577
+
1578
+ **Note:** Uses `useLimit = false` but then manually adds `.limit(1)`
1579
+
1580
+ **Return type:** Promise<Record | null>
1581
+
1582
+ #### MikroORM Implementation (mikro-orm-driver.js:292-296)
1583
+
1584
+ ```javascript
1585
+ loadOne(filters)
1586
+ {
1587
+ let processedFilters = this.processFilters(filters);
1588
+ return this.repository.findOne(processedFilters, this.queryBuilder(false, true, true));
1589
+ }
1590
+ ```
1591
+
1592
+ **Return type:** Promise<Record | null>
1593
+
1594
+ #### Prisma Implementation (prisma-driver.js:491-501)
1595
+
1596
+ ```javascript
1597
+ async loadOne(filters)
1598
+ {
1599
+ let processedFilters = this.filterProcessor.processFilters(filters);
1600
+ let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions(), false);
1601
+ query.where = processedFilters;
1602
+ return this.executeWithNormalization(
1603
+ () => this.model.findFirst(query),
1604
+ 'Load one error: ',
1605
+ null
1606
+ );
1607
+ }
1608
+ ```
1609
+
1610
+ **Return type:** Promise<Record | null>
1611
+
1612
+ **Consistency Analysis:**
1613
+ - ✅ **ALL CONSISTENT** - Return single record or null
1614
+ - ✅ All load first match
1615
+ - ✅ All respect offset/sort
1616
+
1617
+ ---
1618
+
1619
+ ### 3.18 rawQuery(content)
1620
+
1621
+ **Purpose:** Execute raw SQL query
1622
+
1623
+ **Parameters:**
1624
+ - `content` (string) - SQL query string
1625
+
1626
+ **Expected Behavior:**
1627
+ - Execute raw SQL
1628
+ - Return results array or false
1629
+
1630
+ **Return:** Array of results or false
1631
+
1632
+ **Note:** This is implemented in DataServer classes, not Driver classes
1633
+
1634
+ #### ObjectionJS Implementation (objection-js-data-server.js:83-87)
1635
+
1636
+ ```javascript
1637
+ async rawQuery(content)
1638
+ {
1639
+ let result = await this.knex.raw(content);
1640
+ return result[0] || false;
1641
+ }
1642
+ ```
1643
+
1644
+ **Uses:** Knex.raw()
1645
+
1646
+ **Return type:** Array or false
1647
+
1648
+ #### MikroORM Implementation (mikro-orm-data-server.js:97-108)
1649
+
1650
+ ```javascript
1651
+ async rawQuery(content)
1652
+ {
1653
+ try {
1654
+ let queryResult = await this.orm.em.execute(content);
1655
+ if(!sc.isArray(queryResult) || 0 === queryResult.length){
1656
+ return false;
1657
+ }
1658
+ return queryResult;
1659
+ } catch(error){
1660
+ Logger.error('MikroORM raw query error:', error.message);
1661
+ return false;
1662
+ }
1663
+ }
1664
+ ```
1665
+
1666
+ **Uses:** EntityManager.execute()
1667
+
1668
+ **Return type:** Array or false
1669
+
1670
+ #### Prisma Implementation (prisma-data-server.js:118-134)
1671
+
1672
+ ```javascript
1673
+ async rawQuery(content)
1674
+ {
1675
+ try {
1676
+ let statements = this.splitSqlStatements(content);
1677
+ let results = [];
1678
+ for(let statement of statements){
1679
+ if(!statement.trim()){
1680
+ continue;
1681
+ }
1682
+ let result = await this.prisma.$executeRawUnsafe(statement);
1683
+ results.push(result);
1684
+ }
1685
+ return results;
1686
+ } catch(error){
1687
+ Logger.error('Prisma raw query error:', error.message);
1688
+ return false;
1689
+ }
1690
+ }
1691
+ ```
1692
+
1693
+ **Uses:** Prisma.$executeRawUnsafe()
1694
+ **Special:** Splits statements and executes separately
1695
+
1696
+ **Return type:** Array or false
1697
+
1698
+ **Consistency Analysis:**
1699
+ - ✅ **ALL CONSISTENT** - Return array or false
1700
+ - ✅ All execute raw SQL
1701
+ - ⚠️ Prisma splits multi-statement queries
1702
+
1703
+ ---
1704
+
1705
+ ## 4. FILTER OPERATORS ANALYSIS
1706
+
1707
+ ### 4.1 Supported Operators
1708
+
1709
+ **BaseDriver defines these operators** (base-driver.js:24-31):
1710
+ ```javascript
1711
+ this.operatorsMap = {
1712
+ 'GT': '>',
1713
+ 'GTE': '>=',
1714
+ 'LT': '<',
1715
+ 'LTE': '<=',
1716
+ 'NE': '!=',
1717
+ 'EQ': '='
1718
+ };
1719
+ ```
1720
+
1721
+ **Additional operators in drivers:**
1722
+ - `OR` - OR condition
1723
+ - `IN` - IN array
1724
+ - `NOT` - NOT equal
1725
+ - `LIKE` - Pattern matching
1726
+ - `AND` - AND conditions
1727
+
1728
+ ### 4.2 Filter Syntax
1729
+
1730
+ **Basic filter:**
1731
+ ```javascript
1732
+ {field: value} // WHERE field = value
1733
+ ```
1734
+
1735
+ **Operator filter:**
1736
+ ```javascript
1737
+ {field: {operator: 'GT', value: 10}} // WHERE field > 10
1738
+ ```
1739
+
1740
+ **OR filter:**
1741
+ ```javascript
1742
+ {OR: [{field1: value1}, {field2: value2}]} // WHERE field1 = value1 OR field2 = value2
1743
+ ```
1744
+
1745
+ **AND filter:**
1746
+ ```javascript
1747
+ {AND: [{field1: value1}, {field2: value2}]} // WHERE field1 = value1 AND field2 = value2
1748
+ ```
1749
+
1750
+ **IN filter:**
1751
+ ```javascript
1752
+ {field: {operator: 'IN', value: [1, 2, 3]}} // WHERE field IN (1, 2, 3)
1753
+ ```
1754
+
1755
+ **LIKE filter:**
1756
+ ```javascript
1757
+ {field: {operator: 'LIKE', value: 'pattern'}} // WHERE field LIKE '%pattern%'
1758
+ ```
1759
+
1760
+ ### 4.3 ObjectionJS Filter Processing
1761
+
1762
+ **Implementation:** `appendFilters()` method (objection-js-driver.js:274-330)
1763
+
1764
+ **Key behaviors:**
1765
+ - Converts operator strings to UPPERCASE
1766
+ - Maps operators: `GT` → `>`, `GTE` → `>=`, etc.
1767
+ - Special handling for:
1768
+ - `OR` operator → `.orWhere()`
1769
+ - `IN` operator → `.whereIn()`
1770
+ - `NOT` operator → `.whereNot()`
1771
+ - `LIKE` operator → `.where(field, 'like', '%value%')`
1772
+ - JSON fields with LIKE → `.where(ref(field).castText(), 'like', '%value%')`
1773
+ - Nested AND/OR with proper SQL grouping
1774
+ - Relation field flattening (converts `{related_table: {field: value}}` to `{related_table.field: value}`)
1775
+
1776
+ **Case sensitivity:** Operator strings converted to UPPERCASE before processing
1777
+
1778
+ ### 4.4 MikroORM Filter Processing
1779
+
1780
+ **Implementation:** `processFilters()` method (mikro-orm-driver.js:326-353)
1781
+
1782
+ **Operator mapping** (mikro-orm-driver.js:32-41):
1783
+ ```javascript
1784
+ this.mikroOrmOperators = {
1785
+ 'GT': '$gt',
1786
+ 'GTE': '$gte',
1787
+ 'LT': '$lt',
1788
+ 'LTE': '$lte',
1789
+ 'NE': '$ne',
1790
+ 'NOT': '$ne',
1791
+ 'IN': '$in',
1792
+ 'NOT IN': '$nin'
1793
+ };
1794
+ ```
1795
+
1796
+ **Key behaviors:**
1797
+ - Converts operator strings to UPPERCASE
1798
+ - Maps to MikroORM operators: `GT` → `$gt`, `IN` → `$in`, etc.
1799
+ - Special handling for:
1800
+ - `AND` → `$and` array
1801
+ - `OR` → `$or` array
1802
+ - `LIKE` → `$like` with `%value%` wrapping
1803
+ - JSON fields with LIKE → `$like` with `%value%`
1804
+ - Recursive processing for nested filters
1805
+
1806
+ **Case sensitivity:** Operator strings converted to UPPERCASE before processing
1807
+
1808
+ ### 4.5 Prisma Filter Processing
1809
+
1810
+ **Implementation:** `PrismaFilterProcessor` class (prisma-filter-processor.js)
1811
+
1812
+ **Key behaviors:**
1813
+ - Converts filters to Prisma where syntax
1814
+ - Maps operators to Prisma syntax: `GT` → `{gt: value}`, `IN` → `{in: array}`
1815
+ - Special handling for:
1816
+ - `AND` → `AND` array
1817
+ - `OR` → `OR` array
1818
+ - `LIKE` → `{contains: value}` (case-sensitive) or `{mode: 'insensitive'}`
1819
+ - JSON fields → special JSON filtering
1820
+ - Recursive processing for nested filters
1821
+
1822
+ **Case sensitivity:** Depends on database collation and mode setting
1823
+
1824
+ ### 4.6 Filter Consistency Analysis
1825
+
1826
+ **Operator support:**
1827
+ - ✅ **GT, GTE, LT, LTE, NE, EQ** - All drivers support
1828
+ - ✅ **IN, NOT** - All drivers support
1829
+ - ✅ **OR, AND** - All drivers support nested conditions
1830
+ - ✅ **LIKE** - All drivers support with `%pattern%` wrapping
1831
+
1832
+ **Differences:**
1833
+ - ⚠️ **Case handling:** All convert operators to UPPERCASE (consistent)
1834
+ - ⚠️ **LIKE on JSON:** ObjectionJS uses castText(), others use string comparison
1835
+ - ⚠️ **Relation filters:** ObjectionJS flattens, others handle differently
1836
+
1837
+ ---
1838
+
1839
+ ## 5. RELATION LOADING MECHANISMS
1840
+
1841
+ ### 5.1 ObjectionJS Relations
1842
+
1843
+ **Method:** `appendRelationsToQuery()` (objection-js-driver.js:332-342)
1844
+
1845
+ **Implementation:**
1846
+ ```javascript
1847
+ appendRelationsToQuery(queryBuilder, relations, relationsModifiers)
1848
+ {
1849
+ if(!sc.isArray(relations) || 0 === relations.length){
1850
+ relations = Object.keys(this.rawModel.relationMappings || {});
1851
+ }
1852
+ if(0 < relations.length){
1853
+ queryBuilder.withGraphFetched('['+relations.join(',')+']');
1854
+ }
1855
+ this.appendRelationsModifiers(queryBuilder, relations, relationsModifiers);
1856
+ return queryBuilder;
1857
+ }
1858
+ ```
1859
+
1860
+ **Key features:**
1861
+ - Uses Objection's `withGraphFetched()` method
1862
+ - Supports nested relations: `'related_products.related_reviews'`
1863
+ - Supports relation modifiers (orderBy, limit)
1864
+ - Single query with JOINs
1865
+ - Eager loading
1866
+
1867
+ **Relation format:** Comma-separated string in brackets: `'[related_products,related_reviews]'`
1868
+
1869
+ ### 5.2 MikroORM Relations
1870
+
1871
+ **Method:** `appendRelationsToCollection()` (mikro-orm-driver.js:423-439)
1872
+
1873
+ **Implementation:**
1874
+ ```javascript
1875
+ async appendRelationsToCollection(entitiesCollection, relations)
1876
+ {
1877
+ if(!relations || 0 === relations.length){
1878
+ return entitiesCollection;
1879
+ }
1880
+ if(!entitiesCollection){
1881
+ return entitiesCollection;
1882
+ }
1883
+ await this.orm.em.populate(entitiesCollection, relations);
1884
+ if(sc.isArray(entitiesCollection)){
1885
+ for(let entity of entitiesCollection){
1886
+ this.convertCollectionsToArrays(entity);
1887
+ }
1888
+ return entitiesCollection;
1889
+ }
1890
+ return this.convertCollectionsToArrays(entitiesCollection);
1891
+ }
1892
+ ```
1893
+
1894
+ **Key features:**
1895
+ - Uses MikroORM's `em.populate()` method
1896
+ - Loads relations AFTER loading main entities (separate queries)
1897
+ - Converts MikroORM Collections to plain arrays
1898
+ - Lazy loading approach
1899
+ - Supports nested relations
1900
+
1901
+ **Relation format:** Array of strings: `['related_products', 'related_reviews']`
1902
+
1903
+ ### 5.3 Prisma Relations
1904
+
1905
+ **Method:** `executeQueryWithRelations()` (prisma-driver.js:541-559)
1906
+
1907
+ **Implementation:**
1908
+ ```javascript
1909
+ async executeQueryWithRelations(modelMethod, query, relations, errorMessage, defaultReturn)
1910
+ {
1911
+ if(!sc.isArray(relations) || 0 === relations.length){
1912
+ relations = this.getAllRelations();
1913
+ }
1914
+ let includeData = this.relationResolver.buildIncludeObjectWithMapping(relations);
1915
+ if(0 < Object.keys(includeData.include).length){
1916
+ query.include = includeData.include;
1917
+ }
1918
+ return this.executeWithNormalization(
1919
+ async () => this.relationResolver.transformRelationResults(
1920
+ await modelMethod(query),
1921
+ includeData.include,
1922
+ includeData.mapping
1923
+ ),
1924
+ errorMessage,
1925
+ defaultReturn
1926
+ );
1927
+ }
1928
+ ```
1929
+
1930
+ **buildIncludeObjectWithMapping()** (prisma-relation-resolver.js:156-164):
1931
+ ```javascript
1932
+ buildIncludeObjectWithMapping(relations)
1933
+ {
1934
+ let include = {};
1935
+ let mapping = {};
1936
+ for(let relation of relations){
1937
+ this.processRelationPath(relation, include, mapping);
1938
+ }
1939
+ return {include, mapping};
1940
+ }
1941
+ ```
1942
+
1943
+ **Key features:**
1944
+ - Uses Prisma's `include` syntax
1945
+ - Maps relation names (handles Prisma vs Reldens naming)
1946
+ - Supports nested relations: `'related_products.related_reviews'` → `{related_products: {include: {related_reviews: true}}}`
1947
+ - Eager loading with single query
1948
+ - Transforms results to match Reldens naming
1949
+
1950
+ **Relation format:** Array of strings with dot notation: `['related_products.related_reviews']`
1951
+
1952
+ ### 5.4 Relation Consistency Analysis
1953
+
1954
+ **Loading strategy:**
1955
+ - ObjectionJS: Eager (single query with JOINs)
1956
+ - MikroORM: Lazy (separate queries via populate)
1957
+ - Prisma: Eager (single query with include)
1958
+
1959
+ **Nested relations:**
1960
+ - ✅ All support nested relations
1961
+ - ✅ All use dot notation: `'parent.child.grandchild'`
1962
+
1963
+ **Result format:**
1964
+ - ✅ All return plain objects with relation properties
1965
+ - ✅ All use same relation key names (e.g., `related_products`)
1966
+
1967
+ **Differences:**
1968
+ - ⚠️ Performance: ObjectionJS/Prisma faster (fewer queries)
1969
+ - ⚠️ MikroORM converts Collections to arrays (extra processing)
1970
+ - ⚠️ Prisma requires name mapping (transformRelationResults)
1971
+
1972
+ ---
1973
+
1974
+ **Documentation Complete**
1975
+ **Date:** 2026-01-28
1976
+ **Total Public Methods Documented:** 35+
1977
+ **Purpose:** Technical reference for driver abstraction layer and method implementations
1978
+