@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.
- package/.claude/driver-methods-complete-analysis.md +1978 -0
- package/.claude/test-architecture.md +973 -0
- package/CLAUDE.md +121 -1
- package/bin/reldens-storage.js +1 -1
- package/lib/base-driver.js +8 -0
- package/lib/entities-generator.js +1 -3
- package/lib/entity-templates/mikro-orm-model.template +2 -1
- package/lib/generators/models-generation.js +126 -5
- package/lib/mikro-orm/mikro-orm-data-server.js +24 -10
- package/lib/mikro-orm/mikro-orm-driver.js +184 -116
- package/lib/objection-js/objection-js-driver.js +111 -19
- package/lib/prisma/prisma-data-server.js +3 -2
- package/lib/prisma/prisma-driver.js +12 -10
- package/lib/prisma/prisma-filter-processor.js +193 -146
- package/lib/prisma/prisma-metadata-loader.js +0 -4
- package/lib/prisma/prisma-relation-resolver.js +292 -267
- package/lib/prisma/prisma-type-caster.js +280 -260
- package/package.json +15 -6
- package/tests/.env.test.example +7 -0
- package/tests/fixtures/categories-fixtures.js +164 -0
- package/tests/fixtures/expected-entities-mikro-orm/entities/test-categories-entity.js +70 -0
- package/tests/fixtures/expected-entities-mikro-orm/entities/test-products-entity.js +95 -0
- package/tests/fixtures/expected-entities-mikro-orm/entities/test-reviews-entity.js +84 -0
- package/tests/fixtures/expected-entities-mikro-orm/entities-config.js +17 -0
- package/tests/fixtures/expected-entities-mikro-orm/entities-translations.js +13 -0
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/registered-models-mikro-orm.js +23 -0
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-categories-model.js +57 -0
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-products-model.js +79 -0
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-reviews-model.js +70 -0
- package/tests/fixtures/expected-entities-objection-js/entities/test-categories-entity.js +70 -0
- package/tests/fixtures/expected-entities-objection-js/entities/test-products-entity.js +95 -0
- package/tests/fixtures/expected-entities-objection-js/entities/test-reviews-entity.js +84 -0
- package/tests/fixtures/expected-entities-objection-js/entities-config.js +17 -0
- package/tests/fixtures/expected-entities-objection-js/entities-translations.js +13 -0
- package/tests/fixtures/expected-entities-objection-js/models/objection-js/registered-models-objection-js.js +23 -0
- package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-categories-model.js +33 -0
- package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-products-model.js +42 -0
- package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-reviews-model.js +33 -0
- package/tests/fixtures/expected-entities-prisma/entities/test-categories-entity.js +70 -0
- package/tests/fixtures/expected-entities-prisma/entities/test-products-entity.js +95 -0
- package/tests/fixtures/expected-entities-prisma/entities/test-reviews-entity.js +84 -0
- package/tests/fixtures/expected-entities-prisma/entities-config.js +17 -0
- package/tests/fixtures/expected-entities-prisma/entities-translations.js +13 -0
- package/tests/fixtures/expected-entities-prisma/models/prisma/registered-models-prisma.js +23 -0
- package/tests/fixtures/expected-entities-prisma/models/prisma/test-categories-model.js +43 -0
- package/tests/fixtures/expected-entities-prisma/models/prisma/test-products-model.js +50 -0
- package/tests/fixtures/expected-entities-prisma/models/prisma/test-reviews-model.js +46 -0
- package/tests/fixtures/products-fixtures.js +119 -0
- package/tests/fixtures/reviews-fixtures.js +42 -0
- package/tests/fixtures/sql/test-schema.sql +59 -0
- package/tests/integration/test-drivers.js +257 -0
- package/tests/integration/test-nested-filters.js +408 -0
- package/tests/integration/test-relations.js +349 -0
- package/tests/run-tests.js +213 -0
- package/tests/unit/test-drivers.js +89 -0
- package/tests/unit/test-entities-generator.js +533 -0
- package/tests/unit/test-entity-manager.js +128 -0
- package/tests/unit/test-type-mapper.js +232 -0
- package/tests/utils/custom-reporter.js +73 -0
- package/tests/utils/driver-registry.js +144 -0
- package/tests/utils/prisma-subprocess-worker.js +150 -0
- package/tests/utils/test-helpers.js +726 -0
- package/tests/utils/test-runner.js +63 -0
package/CLAUDE.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
4
|
|
|
5
|
+
## ⚠️ CRITICAL: The Library Works - Fix The Tests
|
|
6
|
+
|
|
7
|
+
**This package is battle-tested in hundreds of production projects handling thousands of API calls successfully.**
|
|
8
|
+
|
|
9
|
+
**Default Assumption: Tests are failing because TEST CODE is incorrect, NOT because library code is broken.**
|
|
10
|
+
|
|
11
|
+
**Your Primary Task:**
|
|
12
|
+
- Fix test files in `tests/` directory to properly use the library
|
|
13
|
+
- Study working examples from reldens-cms and reldens main project
|
|
14
|
+
- Make all tests pass by correcting how they call the library APIs
|
|
15
|
+
|
|
16
|
+
**If You Suspect a Library Bug:**
|
|
17
|
+
- Present complete proof with production evidence
|
|
18
|
+
- Wait for user confirmation before proposing any library changes
|
|
19
|
+
- Do NOT modify `lib/` code without explicit approval
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
5
23
|
## Package Overview
|
|
6
24
|
|
|
7
25
|
**@reldens/storage** is the database abstraction layer for Reldens. It provides:
|
|
@@ -52,6 +70,7 @@ npx reldens-storage-prisma --host=<host> --database=<db> --user=<user> --passwor
|
|
|
52
70
|
**BaseDriver** (`lib/base-driver.js`):
|
|
53
71
|
- Abstract base class for ORM drivers
|
|
54
72
|
- Provides common interface for all database operations
|
|
73
|
+
- Stores operator mapping for all drivers (`this.operatorsMap`)
|
|
55
74
|
- Key methods (all must be implemented by drivers):
|
|
56
75
|
- CRUD: `create()`, `update()`, `delete()`, `upsert()`
|
|
57
76
|
- Read: `load()`, `loadById()`, `loadAll()`, `loadOne()`
|
|
@@ -59,6 +78,9 @@ npx reldens-storage-prisma --host=<host> --database=<db> --user=<user> --passwor
|
|
|
59
78
|
- Count: `count()`, `countWithRelations()`
|
|
60
79
|
- Query: `rawQuery()`, `executeCustomQuery()`
|
|
61
80
|
- Helpers: `parseRelationsString()`, `isJsonField()`
|
|
81
|
+
- **Operator Mapping**: Maps string operators to SQL operators
|
|
82
|
+
- `GT` → `>`, `GTE` → `>=`, `LT` → `<`, `LTE` → `<=`, `NE` → `!=`, `EQ` → `=`
|
|
83
|
+
- Available to all drivers via `this.operatorsMap`
|
|
62
84
|
|
|
63
85
|
**BaseDataServer** (`lib/base-data-server.js`):
|
|
64
86
|
- Abstract base class for data servers
|
|
@@ -93,8 +115,11 @@ npx reldens-storage-prisma --host=<host> --database=<db> --user=<user> --passwor
|
|
|
93
115
|
- Uses `withGraphFetched()` for eager loading
|
|
94
116
|
- Supports relation modifiers (orderBy, limit)
|
|
95
117
|
- JSON field handling with `castText()` for LIKE queries
|
|
96
|
-
- Filter operators: OR, IN, NOT, LIKE
|
|
118
|
+
- **Filter operators** (case-insensitive): OR, IN, NOT, LIKE, GT, GTE, LT, LTE, NE, EQ
|
|
119
|
+
- **Operator conversion**: String operators converted to uppercase before processing
|
|
120
|
+
- **Nested filtering**: AND/OR operators support nested conditions with proper SQL grouping
|
|
97
121
|
- Methods: `appendFilters()`, `appendRelationsToQuery()`
|
|
122
|
+
- **Upsert behavior**: Check if record exists → update if found, create if not
|
|
98
123
|
|
|
99
124
|
**MikroORM Driver** (`lib/mikro-orm/`):
|
|
100
125
|
- `mikro-orm-driver.js`: Driver implementation
|
|
@@ -122,6 +147,8 @@ npx reldens-storage-prisma --host=<host> --database=<db> --user=<user> --passwor
|
|
|
122
147
|
- Binary targets configuration
|
|
123
148
|
- Data proxy support
|
|
124
149
|
- Windows permission error handling
|
|
150
|
+
- **Prisma.DbNull handling**: PrismaDataServer passes `Prisma.DbNull` to driver, which passes it to type caster
|
|
151
|
+
- **Type caster isolation**: PrismaTypeCaster never requires `@prisma/client` directly, receives `prismaDbNull` as prop
|
|
125
152
|
|
|
126
153
|
### Generators
|
|
127
154
|
|
|
@@ -472,3 +499,96 @@ if(!prismaClient){
|
|
|
472
499
|
**Used By:**
|
|
473
500
|
- `bin/reldens-storage.js`: CLI entity generator
|
|
474
501
|
- External packages: `@reldens/cms` CLI tools (update-password, generate-entities, generate-sitemap)
|
|
502
|
+
|
|
503
|
+
## Test Suite Architecture
|
|
504
|
+
|
|
505
|
+
**CRITICAL:** For detailed test suite architecture, execution flow, and lifecycle hooks, see: `.claude/test-architecture.md`
|
|
506
|
+
|
|
507
|
+
### The Golden Rule: Connect Once, Test Many
|
|
508
|
+
|
|
509
|
+
**DO NOT** create database connections, tables, or entities in `beforeEach()` hooks. These operations happen **ONCE per driver** in `before()` hooks.
|
|
510
|
+
|
|
511
|
+
**Correct test lifecycle:**
|
|
512
|
+
1. `before()` hook - RUNS ONCE per driver:
|
|
513
|
+
- Connect to database
|
|
514
|
+
- Create tables via SQL
|
|
515
|
+
- Generate entities (introspects database, creates models)
|
|
516
|
+
- For Prisma: Generate schema → Generate client → Reconnect
|
|
517
|
+
- Get repository references
|
|
518
|
+
|
|
519
|
+
2. `beforeEach()` hook - RUNS BEFORE EACH TEST:
|
|
520
|
+
- DELETE data from tables (fast cleanup)
|
|
521
|
+
- Never drop/recreate tables
|
|
522
|
+
- Never reconnect to database
|
|
523
|
+
|
|
524
|
+
3. `after()` hook - RUNS ONCE per driver:
|
|
525
|
+
- DROP all tables
|
|
526
|
+
- Disconnect from database
|
|
527
|
+
|
|
528
|
+
### Test Database Operations
|
|
529
|
+
|
|
530
|
+
- **cleanDatabase()**: Uses DELETE statements to clear data between tests (fast)
|
|
531
|
+
- **dropTestTables()**: Uses DROP TABLE statements for final cleanup (slow, runs once)
|
|
532
|
+
- **executeRawSQL()**: Creates tables from SQL schema file (slow, runs once)
|
|
533
|
+
|
|
534
|
+
### DataServer Flow
|
|
535
|
+
|
|
536
|
+
All three drivers follow this pattern:
|
|
537
|
+
|
|
538
|
+
```
|
|
539
|
+
1. new DataServer({config, rawEntities})
|
|
540
|
+
↓
|
|
541
|
+
2. await dataServer.connect()
|
|
542
|
+
↓
|
|
543
|
+
3. await executeRawSQL(dataServer, schemaSql) ← Tables must exist before next step
|
|
544
|
+
↓
|
|
545
|
+
4. await generateTestEntities(dataServer, driverName)
|
|
546
|
+
├─ Introspects database schema
|
|
547
|
+
├─ Creates models from tables
|
|
548
|
+
├─ [Prisma only]: Generate schema + client + reconnect
|
|
549
|
+
└─ Calls dataServer.generateEntities()
|
|
550
|
+
↓
|
|
551
|
+
5. dataServer.getEntity('entityName') ← Returns repository
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### Why Tables Must Exist Before Entity Generation
|
|
555
|
+
|
|
556
|
+
- `generateTestEntities()` calls `fetchEntitiesFromDatabase()`
|
|
557
|
+
- This queries MySQL `information_schema` for actual table structures
|
|
558
|
+
- Without tables, introspection returns empty/null
|
|
559
|
+
- Entity generation fails without table metadata
|
|
560
|
+
|
|
561
|
+
### Prisma Special Handling
|
|
562
|
+
|
|
563
|
+
Prisma requires additional subprocess steps during entity generation:
|
|
564
|
+
|
|
565
|
+
1. Generate `schema.prisma` file from database (subprocess: `npx reldens-storage-prisma`)
|
|
566
|
+
2. Generate PrismaClient code (subprocess: `npx prisma generate`)
|
|
567
|
+
3. Disconnect old client
|
|
568
|
+
4. Clear Node.js module cache
|
|
569
|
+
5. Reconnect with newly generated client
|
|
570
|
+
6. Now PrismaClient has models for the current database schema
|
|
571
|
+
|
|
572
|
+
This happens ONCE during the `before()` hook inside `generateTestEntities()`.
|
|
573
|
+
|
|
574
|
+
**For critical patterns including:**
|
|
575
|
+
- Why tests use dynamic models (not generated models)
|
|
576
|
+
- Prisma client loading pattern
|
|
577
|
+
- Common pitfalls and solutions
|
|
578
|
+
|
|
579
|
+
**See:** `.claude/test-architecture.md` - Critical Patterns section
|
|
580
|
+
|
|
581
|
+
### Test Files
|
|
582
|
+
|
|
583
|
+
**Integration tests:**
|
|
584
|
+
- `tests/integration/test-cross-driver-compatibility.js`: Full CRUD cycle for all 3 drivers
|
|
585
|
+
- `tests/integration/test-nested-filters.js`: Complex filter syntax (AND/OR/NOT/IN/LIKE)
|
|
586
|
+
- `tests/integration/test-relations.js`: Relation loading and nested relations
|
|
587
|
+
|
|
588
|
+
**Test utilities:**
|
|
589
|
+
- `tests/utils/test-helpers.js`: Database setup, entity generation, cleanup utilities
|
|
590
|
+
- `tests/utils/test-runner.js`: Test framework with suite/group/test methods, uses Logger for output
|
|
591
|
+
|
|
592
|
+
**All integration test files use the correct lifecycle pattern as of 2026-01-18.**
|
|
593
|
+
|
|
594
|
+
For complete details on test architecture, common issues, performance comparison, and troubleshooting, see `.claude/test-architecture.md`.
|
package/bin/reldens-storage.js
CHANGED
package/lib/base-driver.js
CHANGED
|
@@ -21,6 +21,14 @@ class BaseDriver
|
|
|
21
21
|
this.sortBy = sc.get(props, 'sortBy', false);
|
|
22
22
|
this.sortDirection = sc.get(props, 'sortDirection', 'ASC');
|
|
23
23
|
this.entityConfig = sc.get(props, 'entityConfig', {});
|
|
24
|
+
this.operatorsMap = {
|
|
25
|
+
'GT': '>',
|
|
26
|
+
'GTE': '>=',
|
|
27
|
+
'LT': '<',
|
|
28
|
+
'LTE': '<=',
|
|
29
|
+
'NE': '!=',
|
|
30
|
+
'EQ': '='
|
|
31
|
+
};
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
databaseName()
|
|
@@ -402,9 +402,7 @@ class EntitiesGenerator
|
|
|
402
402
|
Logger.critical('Failed to fetch driver key.');
|
|
403
403
|
return false;
|
|
404
404
|
}
|
|
405
|
-
|
|
406
|
-
this.modelsGeneration.setAllTablesData(tables);
|
|
407
|
-
}
|
|
405
|
+
this.modelsGeneration.setAllTablesData(tables);
|
|
408
406
|
let tablesToGenerate = this.filterTablesToGenerate(tables, driverKey);
|
|
409
407
|
if(0 === Object.keys(tablesToGenerate).length && !this.isOverride){
|
|
410
408
|
return true;
|
|
@@ -25,11 +25,12 @@ class {{modelClassName}}
|
|
|
25
25
|
|
|
26
26
|
const schema = new EntitySchema({
|
|
27
27
|
class: {{modelClassName}},
|
|
28
|
+
tableName: '{{tableName}}',
|
|
28
29
|
properties: {
|
|
29
30
|
{{entityPropertiesDefinition}}
|
|
30
31
|
},
|
|
31
32
|
});
|
|
32
|
-
|
|
33
|
+
{{fkMappingsAttachment}}
|
|
33
34
|
module.exports = {
|
|
34
35
|
{{modelClassName}},
|
|
35
36
|
entity: {{modelClassName}},
|
|
@@ -20,6 +20,12 @@ class ModelsGeneration extends BaseGenerator
|
|
|
20
20
|
this.generatedModels = {};
|
|
21
21
|
this.allTablesData = {};
|
|
22
22
|
this.removeIdFromMultipleRelations = sc.get(props, 'removeIdFromMultipleRelations', true);
|
|
23
|
+
this.mikroOrmRelationKindMap = {
|
|
24
|
+
'BelongsToOneRelation': 'm:1',
|
|
25
|
+
'HasOneRelation': '1:1',
|
|
26
|
+
'HasManyRelation': '1:m',
|
|
27
|
+
'ManyToManyRelation': 'm:n'
|
|
28
|
+
};
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
async generateModelFile(tableName, tableData, driverKey, entityInfo, relationMetadata = {})
|
|
@@ -40,7 +46,12 @@ class ModelsGeneration extends BaseGenerator
|
|
|
40
46
|
.map(columnName => 'this.'+columnName+' = '+columnName+';')
|
|
41
47
|
.join('\n ');
|
|
42
48
|
let modelRelations = this.generateModelRelations(tableName, tableData, relationMetadata, driverKey);
|
|
43
|
-
let entityPropertiesDefinition = this.getEntityPropertiesDefinition(tableData.columns, driverKey);
|
|
49
|
+
let entityPropertiesDefinition = this.getEntityPropertiesDefinition(tableData.columns, driverKey, tableName, tableData);
|
|
50
|
+
let fkMappingsJson = this.generateFkMappings(tableName, tableData, driverKey);
|
|
51
|
+
let fkMappingsAttachment = '';
|
|
52
|
+
if('mikro-orm' === driverKey && fkMappingsJson){
|
|
53
|
+
fkMappingsAttachment = 'schema._fkMappings = '+fkMappingsJson+';';
|
|
54
|
+
}
|
|
44
55
|
let idColumn = this.generateIdColumn(tableData.columns, driverKey);
|
|
45
56
|
let replacements = {
|
|
46
57
|
modelClassName,
|
|
@@ -49,6 +60,7 @@ class ModelsGeneration extends BaseGenerator
|
|
|
49
60
|
modelPropertiesConstructor,
|
|
50
61
|
modelRelations,
|
|
51
62
|
entityPropertiesDefinition,
|
|
63
|
+
fkMappingsAttachment,
|
|
52
64
|
idColumn
|
|
53
65
|
};
|
|
54
66
|
let modelContent = this.applyReplacements(modelTemplateContent, replacements);
|
|
@@ -461,24 +473,85 @@ class ModelsGeneration extends BaseGenerator
|
|
|
461
473
|
return 'HasManyRelation';
|
|
462
474
|
}
|
|
463
475
|
|
|
464
|
-
getEntityPropertiesDefinition(columns, driverKey)
|
|
476
|
+
getEntityPropertiesDefinition(columns, driverKey, tableName = null, tableData = null)
|
|
465
477
|
{
|
|
466
478
|
if('mikro-orm' === driverKey){
|
|
467
479
|
let entityProps = [];
|
|
480
|
+
if(!tableName || !tableData){
|
|
481
|
+
for(let columnName of Object.keys(columns)){
|
|
482
|
+
let column = columns[columnName];
|
|
483
|
+
let isPrimary = 'PRI' === column.key;
|
|
484
|
+
let type = TypeMapper.mapDbTypeToJsType(column.type);
|
|
485
|
+
let hasDefault = null !== column.default && undefined !== column.default;
|
|
486
|
+
let propDef = columnName+': { type: \''+type+'\'';
|
|
487
|
+
if(isPrimary){
|
|
488
|
+
propDef += ', primary: true';
|
|
489
|
+
}
|
|
490
|
+
if(column.nullable || hasDefault){
|
|
491
|
+
propDef += ', nullable: true';
|
|
492
|
+
}
|
|
493
|
+
propDef += ' }';
|
|
494
|
+
entityProps.push(propDef);
|
|
495
|
+
}
|
|
496
|
+
return entityProps.join(',\n ');
|
|
497
|
+
}
|
|
498
|
+
let relations = this.detectObjectionJsRelations(tableName, tableData);
|
|
499
|
+
let fkColumnsUsedByRelations = {};
|
|
500
|
+
for(let relationKey of Object.keys(relations)){
|
|
501
|
+
let relation = relations[relationKey];
|
|
502
|
+
if(relation.fromColumn){
|
|
503
|
+
fkColumnsUsedByRelations[relation.fromColumn] = true;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
468
506
|
for(let columnName of Object.keys(columns)){
|
|
469
507
|
let column = columns[columnName];
|
|
508
|
+
if(fkColumnsUsedByRelations[columnName]){
|
|
509
|
+
let type = TypeMapper.mapDbTypeToJsType(column.type);
|
|
510
|
+
let propDef = columnName+': { type: \''+type+'\', persist: false }';
|
|
511
|
+
entityProps.push(propDef);
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
470
514
|
let isPrimary = 'PRI' === column.key;
|
|
471
515
|
let type = TypeMapper.mapDbTypeToJsType(column.type);
|
|
516
|
+
let hasDefault = null !== column.default && undefined !== column.default;
|
|
472
517
|
let propDef = columnName+': { type: \''+type+'\'';
|
|
473
518
|
if(isPrimary){
|
|
474
519
|
propDef += ', primary: true';
|
|
475
520
|
}
|
|
476
|
-
if(column.nullable){
|
|
521
|
+
if(column.nullable || hasDefault){
|
|
477
522
|
propDef += ', nullable: true';
|
|
478
523
|
}
|
|
479
524
|
propDef += ' }';
|
|
480
525
|
entityProps.push(propDef);
|
|
481
526
|
}
|
|
527
|
+
for(let relationKey of Object.keys(relations)){
|
|
528
|
+
let relation = relations[relationKey];
|
|
529
|
+
let relatedModelClass = sc.capitalizedCamelCase(relation.referencedTable)+'Model';
|
|
530
|
+
let kind = sc.get(this.mikroOrmRelationKindMap, relation.relationType, 'm:1');
|
|
531
|
+
let propDef = relationKey+': {\n';
|
|
532
|
+
propDef += ' kind: \''+kind+'\',\n';
|
|
533
|
+
propDef += ' entity: \''+relatedModelClass+'\'';
|
|
534
|
+
if(relation.fromColumn){
|
|
535
|
+
propDef += ',\n joinColumn: \''+relation.fromColumn+'\'';
|
|
536
|
+
}
|
|
537
|
+
propDef += '\n }';
|
|
538
|
+
entityProps.push(propDef);
|
|
539
|
+
}
|
|
540
|
+
let reverseRelations = this.detectReverseObjectionJsRelations(tableName);
|
|
541
|
+
for(let relationKey of Object.keys(reverseRelations)){
|
|
542
|
+
let relation = reverseRelations[relationKey];
|
|
543
|
+
let relatedModelClass = sc.capitalizedCamelCase(relation.referencingTable)+'Model';
|
|
544
|
+
let kind = sc.get(this.mikroOrmRelationKindMap, relation.relationType, '1:m');
|
|
545
|
+
let mappedBy = this.findMappedByKey(tableName, relation.referencingTable, relation);
|
|
546
|
+
let propDef = relationKey+': {\n';
|
|
547
|
+
propDef += ' kind: \''+kind+'\',\n';
|
|
548
|
+
propDef += ' entity: \''+relatedModelClass+'\'';
|
|
549
|
+
if(mappedBy){
|
|
550
|
+
propDef += ',\n mappedBy: \''+mappedBy+'\'';
|
|
551
|
+
}
|
|
552
|
+
propDef += '\n }';
|
|
553
|
+
entityProps.push(propDef);
|
|
554
|
+
}
|
|
482
555
|
return entityProps.join(',\n ');
|
|
483
556
|
}
|
|
484
557
|
if('prisma' === driverKey){
|
|
@@ -502,6 +575,52 @@ class ModelsGeneration extends BaseGenerator
|
|
|
502
575
|
return '';
|
|
503
576
|
}
|
|
504
577
|
|
|
578
|
+
findMappedByKey(currentTable, referencingTable, reverseRelation)
|
|
579
|
+
{
|
|
580
|
+
if(!this.allTablesData[referencingTable]){
|
|
581
|
+
return 'related_'+currentTable;
|
|
582
|
+
}
|
|
583
|
+
let referencingTableData = this.allTablesData[referencingTable];
|
|
584
|
+
let forwardRelations = this.detectObjectionJsRelations(referencingTable, referencingTableData);
|
|
585
|
+
for(let forwardKey of Object.keys(forwardRelations)){
|
|
586
|
+
let forward = forwardRelations[forwardKey];
|
|
587
|
+
if(forward.referencedTable === currentTable && forward.toColumn === reverseRelation.fromColumn){
|
|
588
|
+
return forwardKey;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return 'related_'+currentTable;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
generateFkMappings(tableName, tableData, driverKey)
|
|
595
|
+
{
|
|
596
|
+
if('mikro-orm' !== driverKey){
|
|
597
|
+
return '';
|
|
598
|
+
}
|
|
599
|
+
if(!tableName || !tableData){
|
|
600
|
+
return '';
|
|
601
|
+
}
|
|
602
|
+
let fkMappings = {};
|
|
603
|
+
let referenceCounts = this.countReferencesPerTable(tableData);
|
|
604
|
+
for(let columnName of Object.keys(tableData.columns)){
|
|
605
|
+
let column = tableData.columns[columnName];
|
|
606
|
+
if(!sc.hasOwn(column, 'referencedTable')){
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
let relationKey = this.generateForwardRelationKey(column.referencedTable, columnName, referenceCounts);
|
|
610
|
+
let relatedModelClass = sc.capitalizedCamelCase(column.referencedTable)+'Model';
|
|
611
|
+
fkMappings[columnName] = {
|
|
612
|
+
relationKey: relationKey,
|
|
613
|
+
entityName: relatedModelClass,
|
|
614
|
+
referencedColumn: column.referencedColumn,
|
|
615
|
+
nullable: column.nullable || false
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
if(0 === Object.keys(fkMappings).length){
|
|
619
|
+
return '';
|
|
620
|
+
}
|
|
621
|
+
return JSON.stringify(fkMappings, null, 4);
|
|
622
|
+
}
|
|
623
|
+
|
|
505
624
|
generateRegisteredModelsFile(entitiesInfo, existingModels, driverKey, templatePath)
|
|
506
625
|
{
|
|
507
626
|
if(!FileHandler.exists(templatePath)){
|
|
@@ -541,8 +660,9 @@ class ModelsGeneration extends BaseGenerator
|
|
|
541
660
|
}
|
|
542
661
|
let modelClassName = entity.modelClassName || sc.capitalizedCamelCase(tableName)+'Model';
|
|
543
662
|
let modelFileName = entity.modelFileName || sc.kebabCase(tableName)+'-model.js';
|
|
663
|
+
let variableName = 'mikro-orm' !== driverKey ? '{ '+modelClassName+' }' : sc.camelCase(tableName)+'Model';
|
|
544
664
|
registeredModels.push(
|
|
545
|
-
'const
|
|
665
|
+
'const '+variableName+' = require(\'./'+modelFileName.replace('.js', '')+'\');'
|
|
546
666
|
);
|
|
547
667
|
}
|
|
548
668
|
return registeredModels.join('\n');
|
|
@@ -559,7 +679,8 @@ class ModelsGeneration extends BaseGenerator
|
|
|
559
679
|
}
|
|
560
680
|
}
|
|
561
681
|
let modelClassName = entity.modelClassName || sc.capitalizedCamelCase(tableName)+'Model';
|
|
562
|
-
|
|
682
|
+
let variableName = 'mikro-orm' === driverKey ? sc.camelCase(tableName)+'Model' : modelClassName;
|
|
683
|
+
registeredEntitiesObject.push(sc.camelCase(tableName)+': '+variableName);
|
|
563
684
|
}
|
|
564
685
|
return registeredEntitiesObject.join(',\n ');
|
|
565
686
|
}
|
|
@@ -10,7 +10,7 @@ const { MySQLTablesProvider } = require('../mysql-tables-provider');
|
|
|
10
10
|
const { MikroORM } = require('@mikro-orm/core');
|
|
11
11
|
const { MySqlDriver } = require('@mikro-orm/mysql');
|
|
12
12
|
const { MongoDriver } = require('@mikro-orm/mongodb');
|
|
13
|
-
const {
|
|
13
|
+
const { Logger, sc } = require('@reldens/utils');
|
|
14
14
|
|
|
15
15
|
class MikroOrmDataServer extends BaseDataServer
|
|
16
16
|
{
|
|
@@ -28,6 +28,27 @@ class MikroOrmDataServer extends BaseDataServer
|
|
|
28
28
|
this.warnWhenNoEntities = Boolean(props.warnWhenNoEntities);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
getEntitiesToRegister()
|
|
32
|
+
{
|
|
33
|
+
if(this.entitiesPath){
|
|
34
|
+
//Logger.debug('MikroORM using entitiesPath');
|
|
35
|
+
return this.entitiesPath;
|
|
36
|
+
}
|
|
37
|
+
if(0 < Object.keys(this.entities).length){
|
|
38
|
+
//Logger.debug('MikroORM using this.entities: '+Object.keys(this.entities).join(', '));
|
|
39
|
+
return this.entities;
|
|
40
|
+
}
|
|
41
|
+
if(!this.rawEntities){
|
|
42
|
+
//Logger.debug('MikroORM has no entities!');
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
let entities = Object.values(this.rawEntities);
|
|
46
|
+
//Logger.debug('MikroORM extracting from rawEntities, count: '+entities.length);
|
|
47
|
+
return entities.map(rawEntity => {
|
|
48
|
+
return (rawEntity && rawEntity.schema) ? rawEntity.schema : rawEntity;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
31
52
|
async connect()
|
|
32
53
|
{
|
|
33
54
|
if(this.initialized){
|
|
@@ -39,15 +60,7 @@ class MikroOrmDataServer extends BaseDataServer
|
|
|
39
60
|
clientUrl: this.connectString,
|
|
40
61
|
allowGlobalContext: true,
|
|
41
62
|
};
|
|
42
|
-
|
|
43
|
-
providedSetup.entities = Object.values(this.rawEntities);
|
|
44
|
-
}
|
|
45
|
-
if(0 < Object.keys(this.entities).length){
|
|
46
|
-
providedSetup.entities = this.entities;
|
|
47
|
-
}
|
|
48
|
-
if(this.entitiesPath){
|
|
49
|
-
providedSetup.entities = this.entitiesPath;
|
|
50
|
-
}
|
|
63
|
+
providedSetup.entities = this.getEntitiesToRegister();
|
|
51
64
|
providedSetup.discovery = {warnWhenNoEntities: this.warnWhenNoEntities};
|
|
52
65
|
this.orm = await MikroORM.init(providedSetup);
|
|
53
66
|
this.initialized = Date.now();
|
|
@@ -60,6 +73,7 @@ class MikroOrmDataServer extends BaseDataServer
|
|
|
60
73
|
return false;
|
|
61
74
|
}
|
|
62
75
|
await this.orm.close();
|
|
76
|
+
this.initialized = null;
|
|
63
77
|
return true;
|
|
64
78
|
}
|
|
65
79
|
|