@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 +278 -0
- package/README.md +161 -25
- package/lib/base-data-server.js +5 -0
- package/lib/generators/entities-generation.js +26 -6
- package/lib/generators/models-generation.js +85 -9
- package/lib/mikro-orm/mikro-orm-data-server.js +9 -0
- package/lib/objection-js/objection-js-data-server.js +9 -0
- package/package.json +8 -8
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
|
-
|
|
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
|
-
- `--
|
|
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
|
-
|
|
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-
|
|
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]` -
|
|
55
|
-
- `--maxWaitTime=[ms]` -
|
|
56
|
-
- `--prismaSchemaPath=[path]` - Path to Prisma schema directory
|
|
57
|
-
- `--clientOutputPath=[path]` - Client output path (
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
223
|
+
const CustomDataServer = require('./custom-data-server');
|
|
172
224
|
|
|
173
|
-
const customDriver = new
|
|
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)
|
package/lib/base-data-server.js
CHANGED
|
@@ -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
|
|
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 = [
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
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 =
|
|
156
|
-
let relationType = this.
|
|
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 =
|
|
184
|
-
let relationType = this.
|
|
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.
|
|
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.
|
|
46
|
-
"@mikro-orm/mongodb": "6.
|
|
47
|
-
"@mikro-orm/mysql": "6.
|
|
48
|
-
"@prisma/client": "6.
|
|
49
|
-
"@reldens/server-utils": "^0.
|
|
50
|
-
"@reldens/utils": "^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.
|
|
55
|
+
"prisma": "6.19.0"
|
|
56
56
|
}
|
|
57
57
|
}
|