@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.
- package/.claude/driver-methods-complete-analysis.md +1978 -0
- package/.claude/test-architecture.md +973 -0
- package/CLAUDE.md +141 -7
- package/README.md +14 -2
- 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-client-loader.js +4 -0
- 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
|
|
|
@@ -413,8 +440,8 @@ PrismaClientLoader.load(projectPath, customPath, connectionData)
|
|
|
413
440
|
|
|
414
441
|
**Parameters:**
|
|
415
442
|
- `projectPath` (string): Project root directory path
|
|
416
|
-
- `customPath` (string|null): Optional custom path to Prisma client (overrides default)
|
|
417
|
-
- `connectionData` (object):
|
|
443
|
+
- `customPath` (string|null): Optional custom path to a Prisma client (overrides default)
|
|
444
|
+
- `connectionData` (object|null): Optional database connection configuration with properties:
|
|
418
445
|
- `client` (string): Database client type (mysql, postgresql, etc.)
|
|
419
446
|
- `user` (string): Database username
|
|
420
447
|
- `password` (string): Database password
|
|
@@ -426,13 +453,27 @@ PrismaClientLoader.load(projectPath, customPath, connectionData)
|
|
|
426
453
|
|
|
427
454
|
**Behavior:**
|
|
428
455
|
- If `customPath` is provided, uses that path
|
|
429
|
-
- Otherwise uses default path: `projectPath/prisma/client`
|
|
456
|
+
- Otherwise, uses a default path: `projectPath/prisma/client`
|
|
430
457
|
- Validates that Prisma Client exists at the path
|
|
431
458
|
- Requires `prismaModule.PrismaClient` export
|
|
432
|
-
-
|
|
433
|
-
-
|
|
459
|
+
- If `connectionData` is null: Uses default connection from Prisma schema datasource
|
|
460
|
+
- If `connectionData` is provided: Builds custom connection string and overrides datasource
|
|
461
|
+
- Returns initialized PrismaClient instance
|
|
462
|
+
|
|
463
|
+
**Usage Examples:**
|
|
464
|
+
|
|
465
|
+
Using the default connection from schema:
|
|
466
|
+
```javascript
|
|
467
|
+
const { PrismaClientLoader } = require('@reldens/storage');
|
|
468
|
+
|
|
469
|
+
const prismaClient = PrismaClientLoader.load(process.cwd(), null, null);
|
|
470
|
+
if(!prismaClient){
|
|
471
|
+
console.error('Failed to load Prisma client');
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
```
|
|
434
475
|
|
|
435
|
-
|
|
476
|
+
Using custom connection:
|
|
436
477
|
```javascript
|
|
437
478
|
const { PrismaClientLoader } = require('@reldens/storage');
|
|
438
479
|
|
|
@@ -458,3 +499,96 @@ if(!prismaClient){
|
|
|
458
499
|
**Used By:**
|
|
459
500
|
- `bin/reldens-storage.js`: CLI entity generator
|
|
460
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/README.md
CHANGED
|
@@ -186,6 +186,18 @@ Note: The PrismaDataServer requires the Prisma schema to be generated first. Mak
|
|
|
186
186
|
|
|
187
187
|
If you need to load a Prisma Client instance in your CLI tools or applications:
|
|
188
188
|
|
|
189
|
+
Using the default connection from schema:
|
|
190
|
+
```javascript
|
|
191
|
+
const { PrismaClientLoader } = require('@reldens/storage');
|
|
192
|
+
|
|
193
|
+
const prismaClient = PrismaClientLoader.load(process.cwd(), null, null);
|
|
194
|
+
if(!prismaClient){
|
|
195
|
+
console.error('Failed to load Prisma client');
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Using custom connection:
|
|
189
201
|
```javascript
|
|
190
202
|
const { PrismaClientLoader } = require('@reldens/storage');
|
|
191
203
|
|
|
@@ -210,8 +222,8 @@ if(!prismaClient){
|
|
|
210
222
|
|
|
211
223
|
Parameters:
|
|
212
224
|
- `projectPath`: Project root directory
|
|
213
|
-
- `customPath`: Optional custom path to Prisma client (null for default)
|
|
214
|
-
- `connectionData`:
|
|
225
|
+
- `customPath`: Optional custom path to a Prisma client (null for default)
|
|
226
|
+
- `connectionData`: Optional database connection configuration object (null to use schema default)
|
|
215
227
|
|
|
216
228
|
## Custom Drivers
|
|
217
229
|
|
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
|
|