@reldens/storage 0.76.0 → 0.78.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.md ADDED
@@ -0,0 +1,278 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Package Overview
6
+
7
+ **@reldens/storage** is the database abstraction layer for Reldens. It provides:
8
+ - Multi-ORM support (objection-js, mikro-orm, prisma)
9
+ - Entity/model generation from database schemas
10
+ - Unified API across different ORM drivers
11
+ - Database connection management
12
+ - Schema introspection and code generation
13
+ - Type mapping between database and JavaScript types
14
+
15
+ ## Key Commands
16
+
17
+ ```bash
18
+ # Generate entities from database
19
+ npx reldens-storage generateEntities --user=<user> --pass=<pass> --host=<host> --database=<db> --driver=<driver> --client=<client>
20
+
21
+ # Generate entities with override (overwrites existing entities)
22
+ npx reldens-storage generateEntities --user=<user> --pass=<pass> --database=<db> --driver=<driver> --override
23
+
24
+ # Generate Prisma schema
25
+ npx reldens-storage-prisma --host=<host> --port=<port> --database=<db> --user=<user> --password=<pass>
26
+
27
+ # Generate Prisma schema with database parameters
28
+ npx reldens-storage-prisma --host=<host> --database=<db> --user=<user> --password=<pass> --dbParams="authPlugin=mysql_native_password"
29
+
30
+ # Alternative: Use environment variable for database parameters
31
+ export RELDENS_DB_PARAMS="authPlugin=mysql_native_password&sslmode=require"
32
+ npx reldens-storage-prisma --host=<host> --database=<db> --user=<user> --password=<pass>
33
+ ```
34
+
35
+ ## Architecture
36
+
37
+ ### Core Classes
38
+
39
+ **EntitiesGenerator** (`lib/entities-generator.js`):
40
+ - Main orchestrator for entity generation
41
+ - Coordinates all generation steps
42
+ - Detects existing entities and models
43
+ - Determines what needs to be generated or updated
44
+ - Key methods:
45
+ - `generate()`: Main entry point for generation
46
+ - `detectExistingEntities()`: Scans for existing entity files
47
+ - `detectExistingModels()`: Scans for existing model files
48
+ - `filterTablesToGenerate()`: Determines what needs generation
49
+ - `entityNeedsUpdate()`: Checks if entity fields changed
50
+ - `extractPrismaRelationsMetadata()`: Extracts Prisma relation info
51
+
52
+ **BaseDriver** (`lib/base-driver.js`):
53
+ - Abstract base class for ORM drivers
54
+ - Provides common interface for all database operations
55
+ - Key methods (all must be implemented by drivers):
56
+ - CRUD: `create()`, `update()`, `delete()`, `upsert()`
57
+ - Read: `load()`, `loadById()`, `loadAll()`, `loadOne()`
58
+ - Relations: `loadWithRelations()`, `createWithRelations()`
59
+ - Count: `count()`, `countWithRelations()`
60
+ - Query: `rawQuery()`, `executeCustomQuery()`
61
+ - Helpers: `parseRelationsString()`, `isJsonField()`
62
+
63
+ **BaseDataServer** (`lib/base-data-server.js`):
64
+ - Abstract base class for data servers
65
+ - Manages database connection and entity management
66
+ - Uses EntityManager for entity registry
67
+ - Key methods:
68
+ - `connect()`: Establishes database connection
69
+ - `generateEntities()`: Generates entities from raw models
70
+ - `fetchEntitiesFromDatabase()`: Introspects database schema
71
+ - `getEntity()`: Retrieves entity by name
72
+ - `createConnectionString()`: Builds connection string
73
+
74
+ **EntityManager** (`lib/entity-manager.js`):
75
+ - Registry for managing entities
76
+ - Simple key-value store for entity instances
77
+ - Methods: `get()`, `add()`, `remove()`, `clear()`, `setEntities()`
78
+
79
+ **TypeMapper** (`lib/type-mapper.js`):
80
+ - Maps database types to JavaScript and Prisma types
81
+ - Handles MySQL types: int, varchar, text, json, datetime, enum, blob, etc.
82
+ - Methods:
83
+ - `mapDbTypeToJsType()`: Returns JS type (number, string, Date, object, Buffer, boolean)
84
+ - `mapDbTypeToPrismaType()`: Returns Prisma type (Int, String, DateTime, Json, Bytes, Boolean)
85
+
86
+ ### Driver Implementations
87
+
88
+ **ObjectionJS Driver** (`lib/objection-js/`):
89
+ - `objection-js-driver.js`: Query builder using Objection.js API
90
+ - `objection-js-data-server.js`: Data server using Knex for connection
91
+ - Features:
92
+ - Complex relation support via `relationMappings`
93
+ - Uses `withGraphFetched()` for eager loading
94
+ - Supports relation modifiers (orderBy, limit)
95
+ - JSON field handling with `castText()` for LIKE queries
96
+ - Filter operators: OR, IN, NOT, LIKE
97
+ - Methods: `appendFilters()`, `appendRelationsToQuery()`
98
+
99
+ **MikroORM Driver** (`lib/mikro-orm/`):
100
+ - `mikro-orm-driver.js`: Driver implementation
101
+ - `mikro-orm-data-server.js`: Data server for MongoDB/SQL
102
+ - Features:
103
+ - MongoDB support
104
+ - Entity metadata decorators
105
+ - Automatic schema synchronization
106
+
107
+ **Prisma Driver** (`lib/prisma/`):
108
+ - `prisma-driver.js`: Driver implementation
109
+ - `prisma-data-server.js`: Data server using Prisma Client
110
+ - `prisma-schema-generator.js`: Schema generation and introspection
111
+ - Features:
112
+ - Schema-first approach
113
+ - Type-safe queries
114
+ - Introspection via `prisma db pull`
115
+ - Binary targets configuration
116
+ - Data proxy support
117
+ - Windows permission error handling
118
+
119
+ ### Generators
120
+
121
+ **EntitiesGeneration** (`lib/generators/entities-generation.js`):
122
+ - Generates entity definition files
123
+ - Determines title property (label, title, name, key)
124
+ - Detects primary keys and auto-increment fields
125
+ - Handles ENUM values with formatted labels
126
+ - Generates property configurations with types
127
+ - Creates list/show/edit property arrays
128
+ - Methods:
129
+ - `generateEntityFile()`: Creates entity file
130
+ - `generatePropertiesConfig()`: Builds properties object
131
+ - `determineTitleProperty()`: Finds display field
132
+ - `getPropertyAttributes()`: Builds property metadata
133
+ - `parseEnumValues()`: Extracts ENUM options
134
+
135
+ **ModelsGeneration** (`lib/generators/models-generation.js`):
136
+ - Generates ORM-specific model files
137
+ - Creates relation mappings for ObjectionJS
138
+ - Generates relation types for Prisma
139
+ - Handles forward and reverse relations
140
+ - Creates registered models file
141
+ - Relation key naming:
142
+ - Single reference: `related_[table]`
143
+ - Multiple references: `related_[table]_[column_suffix]`
144
+ - Methods:
145
+ - `generateModelFile()`: Creates model file
146
+ - `generateObjectionJsRelations()`: Builds relationMappings
147
+ - `generatePrismaRelations()`: Builds relationTypes
148
+ - `detectObjectionJsRelations()`: Finds forward relations
149
+ - `detectReverseObjectionJsRelations()`: Finds reverse relations
150
+ - `generateRegisteredModelsFile()`: Creates model registry
151
+ - `countReferencesPerTable()`: Determines relation naming
152
+
153
+ **EntitiesConfigGeneration** (`lib/generators/entities-config-generation.js`):
154
+ - Generates `entities-config.js` file
155
+ - Contains entity-to-entity relation mappings
156
+ - Used by entity loader to resolve relations
157
+
158
+ **EntitiesTranslationsGeneration** (`lib/generators/entities-translations-generation.js`):
159
+ - Generates `entities-translations.js` file
160
+ - Creates i18n translation keys for entities
161
+
162
+ **BaseGenerator** (`lib/generators/base-generator.js`):
163
+ - Base class for all generators
164
+ - Provides `applyReplacements()` method for template processing
165
+
166
+ ### Database Introspection
167
+
168
+ **MySQLTablesProvider** (`lib/mysql-tables-provider.js`):
169
+ - Queries `information_schema` for table structure
170
+ - Fetches columns with types, constraints, defaults
171
+ - Retrieves foreign key relationships
172
+ - Returns structured table data with:
173
+ - Table name
174
+ - Columns with type, length, nullable, key, extra, default
175
+ - Referenced tables and columns for foreign keys
176
+ - Used by ObjectionJS and Prisma drivers
177
+
178
+ ### Entity Templates
179
+
180
+ Located in `lib/entity-templates/`:
181
+ - `entity.template`: Base entity class template
182
+ - `objection-js-model.template`: ObjectionJS model template
183
+ - `mikro-orm-model.template`: MikroORM model template
184
+ - `prisma-model.template`: Prisma model template
185
+ - `entities-config.template`: Entities configuration template
186
+ - `entities-translations.template`: Translations template
187
+ - `registered-models.template`: Model registry template
188
+
189
+ Templates use placeholder replacement with `{{placeholderName}}` syntax.
190
+
191
+ ## Workflow
192
+
193
+ 1. **Database Connection**: Connect to database using appropriate driver
194
+ 2. **Schema Introspection**: Read database schema (tables, columns, foreign keys)
195
+ 3. **Entity Detection**: Scan for existing entities and models
196
+ 4. **Change Detection**: Compare database schema with existing entities
197
+ 5. **Entity Generation**:
198
+ - Generate entity files with property definitions
199
+ - Generate model files with ORM-specific code
200
+ - Generate relation mappings
201
+ 6. **Configuration**:
202
+ - Update `entities-config.js` with new/updated entities
203
+ - Update `entities-translations.js` with translation keys
204
+ - Generate `registered-models-[driver].js` with model imports
205
+ 7. **File Output**: Write all files to `generated-entities/` directory
206
+
207
+ ## Generated File Structure
208
+
209
+ ```
210
+ generated-entities/
211
+ ├── entities/
212
+ │ ├── [table-name]-entity.js # Entity definitions
213
+ │ └── ...
214
+ ├── models/
215
+ │ ├── objection-js/
216
+ │ │ ├── [table-name]-model.js
217
+ │ │ └── registered-models-objection-js.js
218
+ │ ├── mikro-orm/
219
+ │ │ ├── [table-name]-model.js
220
+ │ │ └── registered-models-mikro-orm.js
221
+ │ └── prisma/
222
+ │ ├── [table-name]-model.js
223
+ │ └── registered-models-prisma.js
224
+ ├── entities-config.js # Entity relation configuration
225
+ └── entities-translations.js # Translation keys
226
+ ```
227
+
228
+ ## Relation Keys Pattern
229
+
230
+ All generated entity relations follow the `related_*` prefix pattern:
231
+
232
+ - **Single reference**: `related_[table_name]`
233
+ - Example: `related_players`, `related_users`
234
+ - **Multiple references to same table**: `related_[table_name]_[column_suffix]`
235
+ - Example: `related_skills_skill`, `related_skills_owner`
236
+ - The `_id` suffix is removed from column name when multiple references exist
237
+
238
+ This pattern is consistent across all ORM drivers and is defined in `entities-config.js`.
239
+
240
+ ## Important Notes
241
+
242
+ ### Entity Management
243
+ - **DO NOT modify** generated entities directly (files in `generated-entities/`)
244
+ - Extend generated entities in custom model files if customization needed
245
+ - Custom models should be placed in project-specific directories
246
+ - Entity configuration (`entities-config.js`) defines relation keys used throughout codebase
247
+ - Relation keys are critical - changing them affects all code referencing relations
248
+
249
+ ### Generation Behavior
250
+ - Generation is smart: only creates/updates entities that changed
251
+ - Detects new tables, field changes, missing configs, missing models
252
+ - Use `--override` flag to force regeneration of all files
253
+ - Override flag useful after major schema changes or driver switches
254
+ - Each driver has its own model structure and relation syntax
255
+
256
+ ### Schema Changes
257
+ - Always regenerate entities after database schema changes
258
+ - Adding columns: entities auto-update with new fields
259
+ - Removing columns: entities auto-update, remove fields
260
+ - Changing relations: models regenerate with new relation mappings
261
+ - ENUM changes: entity updates with new available values
262
+
263
+ ### Driver-Specific Notes
264
+ - **ObjectionJS**: Recommended driver, mature and stable
265
+ - **MikroORM**: Use for MongoDB or NoSQL requirements
266
+ - **Prisma**: Requires schema generation first, then entity generation
267
+ - Cannot mix drivers - regenerate all when switching drivers
268
+ - Each driver has different relation syntax in generated models
269
+
270
+ ### Binary Executables
271
+ - `bin/reldens-storage.js`: Main CLI for entity generation
272
+ - `bin/generate-prisma-schema.js`: Prisma schema generator CLI
273
+ - Both are available via npx after package installation
274
+
275
+ ### Environment Variables
276
+ - `RELDENS_DB_PARAMS`: Database connection parameters (used by Prisma)
277
+ - Format: `key1=value1&key2=value2`
278
+ - Example: `authPlugin=mysql_native_password&sslmode=require`
package/README.md CHANGED
@@ -10,54 +10,78 @@ It ensures consistent data access methods across different database types and OR
10
10
 
11
11
  ### ORM Support
12
12
  - **Objection JS** (via Knex) - For SQL databases (recommended)
13
+ - MySQL, MariaDB, PostgreSQL support
14
+ - Complex relation mappings
15
+ - Query builder with filtering and sorting
13
16
  - **Mikro-ORM** - For MongoDB/NoSQL support
17
+ - MongoDB native support
18
+ - Entity metadata decorators
19
+ - Automatic schema synchronization
14
20
  - **Prisma** - Modern database toolkit
21
+ - Type-safe queries
22
+ - Schema-first approach
23
+ - Introspection and migration tools
15
24
 
16
25
  ### Entity Management
17
- - Standardized CRUD operations
26
+ - Standardized CRUD operations across all drivers
18
27
  - Automatic entity generation from database schemas
19
- - Type mapping between database and JavaScript
20
- - Foreign key relationship handling
28
+ - Type mapping between database and JavaScript/Prisma types
29
+ - Foreign key relationship handling with smart naming
21
30
  - ENUM field support with formatted values
31
+ - JSON field support with type casting
32
+ - Relation modifiers (orderBy, limit) for complex queries
22
33
 
23
34
  ### CLI Tools
24
- Generate entity files directly from your database structure:
35
+
36
+ **Generate entity files directly from your database structure:**
25
37
  ```bash
26
38
  npx reldens-storage generateEntities --user=[dbuser] --pass=[dbpass] --database=[dbname] --driver=[objection-js]
27
39
  ```
28
40
 
29
- Options:
30
- - `--user=[username]` - Database username
31
- - `--pass=[password]` - Database password
41
+ **Entity Generation Options:**
42
+ - `--user=[username]` - Database username (required)
43
+ - `--pass=[password]` - Database password (required)
44
+ - `--database=[name]` - Database name (required)
45
+ - `--driver=[driver]` - ORM driver: objection-js, mikro-orm, or prisma (default: objection-js)
46
+ - `--client=[client]` - Database client: mysql, mysql2, or mongodb (default: mysql2)
32
47
  - `--host=[host]` - Database host (default: localhost)
33
48
  - `--port=[port]` - Database port (default: 3306)
34
- - `--database=[name]` - Database name
35
- - `--driver=[driver]` - ORM driver (objection-js|mikro-orm|prisma)
36
- - `--client=[client]` - Database client (mysql|mysql2|mongodb)
37
- - `--path=[path]` - Project path for output files
49
+ - `--path=[path]` - Project path for output files (default: current directory)
38
50
  - `--override` - Regenerate all files even if they exist
39
51
 
40
- Generate Prisma schema:
52
+ **Smart Generation:**
53
+ - Only generates/updates entities that have changed
54
+ - Detects new tables, field changes, missing configurations
55
+ - Preserves custom code outside generated files
56
+ - Use `--override` to force complete regeneration
57
+
58
+ **Generate Prisma schema:**
41
59
  ```bash
42
- npx reldens-generate-prisma-schema --host=[host] --port=[port] --user=[dbuser] --password=[dbpass] --database=[dbname]
60
+ npx reldens-storage-prisma --host=[host] --port=[port] --user=[dbuser] --password=[dbpass] --database=[dbname]
43
61
  ```
44
62
 
45
- Options:
63
+ **Prisma Schema Generation Options:**
46
64
  - `--host=[host]` - Database host (required)
47
65
  - `--port=[port]` - Database port (required)
48
66
  - `--user=[username]` - Database username (required)
49
67
  - `--password=[password]` - Database password (required)
50
68
  - `--database=[name]` - Database name (required)
51
- - `--client=[client]` - Database client (default: mysql)
69
+ - `--client=[client]` - Database client: mysql, postgresql (default: mysql)
52
70
  - `--debug` - Enable debug mode
53
- - `--dataProxy` - Enable data proxy
54
- - `--checkInterval=[ms]` - Check interval in milliseconds (default: 1000)
55
- - `--maxWaitTime=[ms]` - Max wait time in milliseconds (default: 30000)
56
- - `--prismaSchemaPath=[path]` - Path to Prisma schema directory
57
- - `--clientOutputPath=[path]` - Client output path (if not set, uses Prisma default)
71
+ - `--dataProxy` - Enable Prisma data proxy
72
+ - `--checkInterval=[ms]` - Schema generation check interval (default: 1000)
73
+ - `--maxWaitTime=[ms]` - Maximum wait time for generation (default: 30000)
74
+ - `--prismaSchemaPath=[path]` - Path to Prisma schema directory (default: ./prisma)
75
+ - `--clientOutputPath=[path]` - Client output path (default: Prisma default)
58
76
  - `--generateBinaryTargets=[targets]` - Comma-separated binary targets (default: native,debian-openssl-1.1.x)
59
77
  - `--dbParams=[params]` - Database connection parameters (e.g., authPlugin=mysql_native_password)
60
78
 
79
+ **Prisma Workflow:**
80
+ 1. Generate schema: `npx reldens-storage-prisma --host=... --database=...`
81
+ 2. Schema file created at: `prisma/schema.prisma`
82
+ 3. Prisma client generated automatically
83
+ 4. Generate entities: `npx reldens-storage generateEntities --driver=prisma ...`
84
+
61
85
  ### Environment Variables
62
86
 
63
87
  You can set database connection parameters using environment variables:
@@ -162,18 +186,130 @@ Note: The PrismaDataServer requires the Prisma schema to be generated first. Mak
162
186
 
163
187
  You can create custom storage drivers by extending the base classes:
164
188
 
165
- 1. Extend `BaseDataServer` and `BaseDriver`
166
- 2. Implement all required methods
167
- 3. Pass your custom server instance to Reldens ServerManager:
189
+ ### Creating a Custom Driver
190
+
191
+ 1. **Extend `BaseDataServer`** for connection management:
192
+ ```javascript
193
+ const { BaseDataServer } = require('@reldens/storage');
194
+
195
+ class CustomDataServer extends BaseDataServer {
196
+ async connect() {
197
+ // Implement connection logic
198
+ }
199
+
200
+ async fetchEntitiesFromDatabase() {
201
+ // Implement schema introspection
202
+ }
203
+
204
+ generateEntities() {
205
+ // Generate entities from raw models
206
+ }
207
+ }
208
+ ```
209
+
210
+ 2. **Extend `BaseDriver`** for query operations:
211
+ ```javascript
212
+ const { BaseDriver } = require('@reldens/storage');
213
+
214
+ class CustomDriver extends BaseDriver {
215
+ // Implement all required methods:
216
+ // create(), update(), delete(), load(), loadById(), etc.
217
+ }
218
+ ```
168
219
 
220
+ 3. **Use your custom driver** in your application:
169
221
  ```javascript
170
222
  const { ServerManager } = require('@reldens/server');
171
- const YourCustomDriver = require('./your-custom-driver');
223
+ const CustomDataServer = require('./custom-data-server');
172
224
 
173
- const customDriver = new YourCustomDriver(options);
225
+ const customDriver = new CustomDataServer(options);
174
226
  const appServer = new ServerManager(serverConfig, eventsManager, customDriver);
175
227
  ```
176
228
 
229
+ ### Required Methods
230
+
231
+ All drivers must implement the methods defined in `BaseDriver`:
232
+ - **CRUD**: `create()`, `update()`, `delete()`, `upsert()`
233
+ - **Read**: `load()`, `loadById()`, `loadAll()`, `loadOne()`
234
+ - **Relations**: `loadWithRelations()`, `createWithRelations()`
235
+ - **Count**: `count()`, `countWithRelations()`
236
+ - **Helpers**: `tableName()`, `databaseName()`, `property()`
237
+
238
+ ## Generated File Structure
239
+
240
+ When you run entity generation, files are created in the `generated-entities/` directory:
241
+
242
+ ```
243
+ generated-entities/
244
+ ├── entities/
245
+ │ ├── users-entity.js # Entity definitions with properties
246
+ │ ├── players-entity.js
247
+ │ └── ...
248
+ ├── models/
249
+ │ ├── objection-js/
250
+ │ │ ├── users-model.js # ObjectionJS models with relationMappings
251
+ │ │ ├── players-model.js
252
+ │ │ └── registered-models-objection-js.js
253
+ │ ├── mikro-orm/
254
+ │ │ ├── users-model.js # MikroORM models
255
+ │ │ └── registered-models-mikro-orm.js
256
+ │ └── prisma/
257
+ │ ├── users-model.js # Prisma models with relationTypes
258
+ │ └── registered-models-prisma.js
259
+ ├── entities-config.js # Entity configuration and relations
260
+ └── entities-translations.js # i18n translation keys
261
+ ```
262
+
263
+ ### Entity Files
264
+ - **Entity classes**: Define properties, types, validations
265
+ - **Property metadata**: Type, required, reference, availableValues (for ENUMs)
266
+ - **Display properties**: Separate arrays for list, show, edit views
267
+
268
+ ### Model Files
269
+ - **Driver-specific**: Each driver has its own model syntax
270
+ - **Relations**: Automatically generated based on foreign keys
271
+ - **Registered models**: Central registry for all models
272
+
273
+ ### Relation Naming Pattern
274
+
275
+ All relations use the `related_*` prefix:
276
+ - **Single reference**: `related_users`, `related_players`
277
+ - **Multiple references**: `related_skills_skill`, `related_skills_owner`
278
+
279
+ Example usage:
280
+ ```javascript
281
+ // Load user with related player
282
+ const user = await dataServer.getEntity('users')
283
+ .loadByIdWithRelations(userId, ['related_player']);
284
+
285
+ // Access nested relations
286
+ const player = await dataServer.getEntity('players')
287
+ .loadByIdWithRelations(playerId, ['related_state', 'related_scenes']);
288
+ ```
289
+
290
+ ## Architecture Overview
291
+
292
+ ### Core Components
293
+
294
+ - **EntitiesGenerator**: Orchestrates entity generation
295
+ - **BaseDriver**: Abstract interface for database operations
296
+ - **BaseDataServer**: Connection and entity management
297
+ - **EntityManager**: Entity registry
298
+ - **TypeMapper**: Database type to JavaScript/Prisma type conversion
299
+
300
+ ### Generators
301
+
302
+ - **EntitiesGeneration**: Creates entity definition files
303
+ - **ModelsGeneration**: Creates ORM-specific models
304
+ - **EntitiesConfigGeneration**: Creates configuration file
305
+ - **EntitiesTranslationsGeneration**: Creates translation keys
306
+
307
+ ### Database Support
308
+
309
+ - **MySQL/MariaDB**: Via ObjectionJS or Prisma
310
+ - **PostgreSQL**: Via Prisma
311
+ - **MongoDB**: Via MikroORM
312
+
177
313
  ## Links
178
314
  - [Reldens Website](https://www.reldens.com/)
179
315
  - [GitHub Repository](https://github.com/damian-pastorini/reldens/tree/master)
@@ -66,6 +66,11 @@ class BaseDataServer
66
66
  ErrorManager.error('BaseDriver connect() not implemented.');
67
67
  }
68
68
 
69
+ async disconnect()
70
+ {
71
+ ErrorManager.error('BaseDriver disconnect() not implemented.');
72
+ }
73
+
69
74
  async fetchEntitiesFromDatabase()
70
75
  {
71
76
  ErrorManager.error('BaseDriver fetchEntitiesFromDatabase() not implemented.');
@@ -36,10 +36,14 @@ class EntitiesGeneration extends BaseGenerator
36
36
  for(let columnName of Object.keys(tableData.columns)){
37
37
  tableData.columns[columnName].tableName = tableName;
38
38
  }
39
- let propertiesConfig = this.generatePropertiesConfig(tableData.columns, titleProperty);
39
+ let primaryKeyColumn = this.detectPrimaryKeyColumn(tableData.columns);
40
+ let propertiesConfig = this.generatePropertiesConfig(tableData.columns, titleProperty, primaryKeyColumn);
40
41
  let titlePropertyDeclaration = titleProperty ? '\n let titleProperty = \''+titleProperty+'\';' : '';
41
42
  let titlePropertyReturn = titleProperty ? '\n titleProperty,' : '';
42
- let fieldsToRemoveFromEdit = ['id'];
43
+ let fieldsToRemoveFromEdit = [];
44
+ if(primaryKeyColumn){
45
+ fieldsToRemoveFromEdit.push(primaryKeyColumn);
46
+ }
43
47
  if(tableData.columns['created_at']){
44
48
  fieldsToRemoveFromEdit.push('created_at');
45
49
  }
@@ -80,6 +84,17 @@ class EntitiesGeneration extends BaseGenerator
80
84
  return true;
81
85
  }
82
86
 
87
+ detectPrimaryKeyColumn(columns)
88
+ {
89
+ for(let columnName of Object.keys(columns)){
90
+ let column = columns[columnName];
91
+ if('PRI' === column.key){
92
+ return columnName;
93
+ }
94
+ }
95
+ return false;
96
+ }
97
+
83
98
  getFieldsToRemoveFromList(columns)
84
99
  {
85
100
  let fieldsToRemove = [];
@@ -98,6 +113,9 @@ class EntitiesGeneration extends BaseGenerator
98
113
 
99
114
  getEditPropertiesRemoval(fieldsToRemove)
100
115
  {
116
+ if(0 === fieldsToRemove.length){
117
+ return 'let editProperties = propertiesKeys;';
118
+ }
101
119
  if(1 === fieldsToRemove.length){
102
120
  return 'let editProperties = [...propertiesKeys];\n editProperties.splice(editProperties.indexOf(\''+
103
121
  fieldsToRemove[0]
@@ -123,15 +141,17 @@ class EntitiesGeneration extends BaseGenerator
123
141
  return false;
124
142
  }
125
143
 
126
- generatePropertiesConfig(columns, titleProperty)
144
+ generatePropertiesConfig(columns, titleProperty, primaryKeyColumn)
127
145
  {
128
146
  let propertiesConfig = [];
129
147
  for(let columnName of Object.keys(columns)){
130
148
  let column = columns[columnName];
131
149
  let propertyKey = titleProperty && titleProperty === columnName ? '[titleProperty]' : columnName;
132
- if('id' === columnName){
133
- let idType = column.type.toLowerCase();
134
- propertiesConfig.push(columnName+': {\n dbType: \''+idType+'\'\n }');
150
+ let isPrimaryKey = primaryKeyColumn && primaryKeyColumn === columnName;
151
+ if(isPrimaryKey){
152
+ let props = this.getPropertyAttributes(column);
153
+ props.unshift('isId: true');
154
+ propertiesConfig.push(propertyKey+': {\n '+props.join(',\n ')+ '\n }');
135
155
  continue;
136
156
  }
137
157
  let props = this.getPropertyAttributes(column);
@@ -19,6 +19,7 @@ class ModelsGeneration extends BaseGenerator
19
19
  this.templates = sc.get(props, 'templates', {});
20
20
  this.generatedModels = {};
21
21
  this.allTablesData = {};
22
+ this.removeIdFromMultipleRelations = sc.get(props, 'removeIdFromMultipleRelations', true);
22
23
  }
23
24
 
24
25
  async generateModelFile(tableName, tableData, driverKey, entityInfo, relationMetadata = {})
@@ -135,7 +136,7 @@ class ModelsGeneration extends BaseGenerator
135
136
  for(let modelInfo of requiredModels){
136
137
  requireStatements.push(' const { '+modelInfo.modelClass+' } = require(\'./'+modelInfo.fileName+'\');');
137
138
  }
138
- let relationMappingsMethod = '\n\n static get relationMappings()\n {\n';
139
+ let relationMappingsMethod = '\n static get relationMappings()\n {\n';
139
140
  relationMappingsMethod += requireStatements.join('\n')+'\n';
140
141
  relationMappingsMethod += ' return {\n';
141
142
  relationMappingsMethod += relationMappings.join(',\n');
@@ -147,13 +148,14 @@ class ModelsGeneration extends BaseGenerator
147
148
  detectObjectionJsRelations(tableName, tableData)
148
149
  {
149
150
  let relations = {};
151
+ let referenceCounts = this.countReferencesPerTable(tableData);
150
152
  for(let columnName of Object.keys(tableData.columns)){
151
153
  let column = tableData.columns[columnName];
152
154
  if(!sc.hasOwn(column, 'referencedTable')){
153
155
  continue;
154
156
  }
155
- let relationKey = 'related_'+column.referencedTable;
156
- let relationType = this.determineRelationType(column, true);
157
+ let relationKey = this.generateForwardRelationKey(column.referencedTable, columnName, referenceCounts);
158
+ let relationType = this.determineForwardRelationType(tableName, columnName, column);
157
159
  relations[relationKey] = {
158
160
  fromColumn: columnName,
159
161
  toColumn: column.referencedColumn,
@@ -172,6 +174,7 @@ class ModelsGeneration extends BaseGenerator
172
174
  continue;
173
175
  }
174
176
  let otherTableData = this.allTablesData[otherTableName];
177
+ let referenceCounts = this.countReferencesToTable(tableName, otherTableData);
175
178
  for(let columnName of Object.keys(otherTableData.columns)){
176
179
  let column = otherTableData.columns[columnName];
177
180
  if(!sc.hasOwn(column, 'referencedTable')){
@@ -180,8 +183,8 @@ class ModelsGeneration extends BaseGenerator
180
183
  if(column.referencedTable !== tableName){
181
184
  continue;
182
185
  }
183
- let relationKey = 'related_'+otherTableName;
184
- let relationType = this.determineRelationType(column, false);
186
+ let relationKey = this.generateReverseRelationKey(otherTableName, columnName, referenceCounts);
187
+ let relationType = this.determineReverseRelationType(otherTableName, columnName, column);
185
188
  reverseRelations[relationKey] = {
186
189
  fromColumn: column.referencedColumn,
187
190
  toColumn: columnName,
@@ -193,15 +196,88 @@ class ModelsGeneration extends BaseGenerator
193
196
  return reverseRelations;
194
197
  }
195
198
 
199
+ countReferencesPerTable(tableData)
200
+ {
201
+ let counts = {};
202
+ for(let columnName of Object.keys(tableData.columns)){
203
+ let column = tableData.columns[columnName];
204
+ if(!sc.hasOwn(column, 'referencedTable')){
205
+ continue;
206
+ }
207
+ if(!counts[column.referencedTable]){
208
+ counts[column.referencedTable] = 0;
209
+ }
210
+ counts[column.referencedTable]++;
211
+ }
212
+ return counts;
213
+ }
214
+
215
+ countReferencesToTable(targetTable, sourceTableData)
216
+ {
217
+ let count = 0;
218
+ for(let columnName of Object.keys(sourceTableData.columns)){
219
+ let column = sourceTableData.columns[columnName];
220
+ if(!sc.hasOwn(column, 'referencedTable')){
221
+ continue;
222
+ }
223
+ if(column.referencedTable === targetTable){
224
+ count++;
225
+ }
226
+ }
227
+ return count;
228
+ }
229
+
230
+ generateForwardRelationKey(referencedTable, columnName, referenceCounts)
231
+ {
232
+ if(1 === referenceCounts[referencedTable]){
233
+ return 'related_'+referencedTable;
234
+ }
235
+ let columnSuffix = columnName;
236
+ if(this.removeIdFromMultipleRelations){
237
+ columnSuffix = columnName.replace(/_id$/i, '');
238
+ }
239
+ return 'related_'+referencedTable+'_'+columnSuffix;
240
+ }
241
+
242
+ generateReverseRelationKey(referencingTable, columnName, referenceCount)
243
+ {
244
+ if(1 === referenceCount){
245
+ return 'related_'+referencingTable;
246
+ }
247
+ let columnSuffix = columnName;
248
+ if(this.removeIdFromMultipleRelations){
249
+ columnSuffix = columnName.replace(/_id$/i, '');
250
+ }
251
+ return 'related_'+referencingTable+'_'+columnSuffix;
252
+ }
253
+
254
+ determineForwardRelationType(tableName, columnName, column)
255
+ {
256
+ return 'BelongsToOneRelation';
257
+ }
258
+
259
+ determineReverseRelationType(referencingTableName, referencingColumnName, referencingColumn)
260
+ {
261
+ if(!this.allTablesData[referencingTableName]){
262
+ return 'HasManyRelation';
263
+ }
264
+ let referencingTableData = this.allTablesData[referencingTableName];
265
+ let referencingColumnData = referencingTableData.columns[referencingColumnName];
266
+ if(!referencingColumnData){
267
+ return 'HasManyRelation';
268
+ }
269
+ if('UNI' === referencingColumnData.key || 'PRI' === referencingColumnData.key){
270
+ return 'HasOneRelation';
271
+ }
272
+ return 'HasManyRelation';
273
+ }
274
+
196
275
  determineRelationType(column, isForwardRelation)
197
276
  {
198
277
  if(isForwardRelation){
199
- if('PRI' === column.key){
200
- return 'HasOneRelation';
201
- }
202
278
  return 'BelongsToOneRelation';
203
279
  }
204
- if('PRI' === column.key){
280
+ if('PRI' === column.key || 'UNI' === column.key){
205
281
  return 'HasOneRelation';
206
282
  }
207
283
  return 'HasManyRelation';
@@ -54,6 +54,15 @@ class MikroOrmDataServer extends BaseDataServer
54
54
  return this.initialized;
55
55
  }
56
56
 
57
+ async disconnect()
58
+ {
59
+ if(!this.orm){
60
+ return false;
61
+ }
62
+ await this.orm.close();
63
+ return true;
64
+ }
65
+
57
66
  generateEntities()
58
67
  {
59
68
  if(!this.initialized){
@@ -47,6 +47,15 @@ class ObjectionJsDataServer extends BaseDataServer
47
47
  return this.initialized;
48
48
  }
49
49
 
50
+ async disconnect()
51
+ {
52
+ if(!this.knex){
53
+ return false;
54
+ }
55
+ await this.knex.destroy();
56
+ return true;
57
+ }
58
+
50
59
  generateEntities()
51
60
  {
52
61
  if(!this.rawEntities){
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reldens/storage",
3
3
  "scope": "@reldens",
4
- "version": "0.76.0",
4
+ "version": "0.78.0",
5
5
  "description": "Reldens - Storage",
6
6
  "author": "Damian A. Pastorini",
7
7
  "license": "MIT",
@@ -42,16 +42,16 @@
42
42
  "url": "https://github.com/damian-pastorini/reldens-storage/issues"
43
43
  },
44
44
  "dependencies": {
45
- "@mikro-orm/core": "6.5.8",
46
- "@mikro-orm/mongodb": "6.5.8",
47
- "@mikro-orm/mysql": "6.5.8",
48
- "@prisma/client": "6.18.0",
49
- "@reldens/server-utils": "^0.39.0",
50
- "@reldens/utils": "^0.53.0",
45
+ "@mikro-orm/core": "6.6.0",
46
+ "@mikro-orm/mongodb": "6.6.0",
47
+ "@mikro-orm/mysql": "6.6.0",
48
+ "@prisma/client": "6.19.0",
49
+ "@reldens/server-utils": "^0.40.0",
50
+ "@reldens/utils": "^0.54.0",
51
51
  "knex": "3.1.0",
52
52
  "mysql": "2.18.1",
53
53
  "mysql2": "3.15.3",
54
54
  "objection": "3.1.5",
55
- "prisma": "6.18.0"
55
+ "prisma": "6.19.0"
56
56
  }
57
57
  }