@reldens/storage 0.32.0 → 0.33.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/README.md +71 -24
- package/bin/reldens-storage.js +75 -0
- package/index.js +19 -6
- package/lib/base-data-server.js +5 -0
- package/lib/entities-generator.js +584 -0
- package/lib/entity-properties.js +26 -0
- package/lib/entity-templates/entities-config.template +13 -0
- package/lib/entity-templates/entities-translations.template +11 -0
- package/lib/entity-templates/entity.template +32 -0
- package/lib/entity-templates/mikro-orm-entity.template +37 -0
- package/lib/entity-templates/objection-js-entity.template +19 -0
- package/lib/entity-templates/registered-models.template +19 -0
- package/lib/mikro-orm/mikro-orm-data-server.js +42 -5
- package/lib/mysql-tables-provider.js +87 -0
- package/lib/objection-js/objection-js-data-server.js +12 -1
- package/lib/type-mapper.js +49 -0
- package/package.json +10 -6
package/README.md
CHANGED
|
@@ -3,56 +3,103 @@
|
|
|
3
3
|
# Reldens - Storage
|
|
4
4
|
|
|
5
5
|
## About this package
|
|
6
|
-
This package
|
|
6
|
+
This package provides standardized database drivers for Reldens projects.
|
|
7
|
+
It ensures consistent data access methods across different database types and ORM implementations.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
## Features
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
### ORM Support
|
|
12
|
+
- **Objection JS** (via Knex) - For SQL databases (recommended)
|
|
13
|
+
- **Mikro-ORM** - For MongoDB/NoSQL support
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
+
### Entity Management
|
|
16
|
+
- Standardized CRUD operations
|
|
17
|
+
- Automatic entity generation from database schemas
|
|
18
|
+
- Type mapping between database and JavaScript
|
|
19
|
+
- Foreign key relationship handling
|
|
20
|
+
- ENUM field support with formatted values
|
|
21
|
+
|
|
22
|
+
### CLI Tools
|
|
23
|
+
Generate entity files directly from your database structure:
|
|
24
|
+
```bash
|
|
25
|
+
npx reldens-storage generateEntities --user=dbuser --pass=dbpass --database=dbname --driver=objection-js
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Options:
|
|
29
|
+
- `--user` - Database username
|
|
30
|
+
- `--pass` - Database password
|
|
31
|
+
- `--host` - Database host (default: localhost)
|
|
32
|
+
- `--port` - Database port (default: 3306)
|
|
33
|
+
- `--database` - Database name
|
|
34
|
+
- `--driver` - ORM driver (objection-js|mikro-orm)
|
|
35
|
+
- `--client` - Database client (mysql|mysql2|mongodb)
|
|
36
|
+
- `--path` - Project path for output files
|
|
37
|
+
|
|
38
|
+
## Usage Examples
|
|
39
|
+
|
|
40
|
+
### SQL with Objection JS
|
|
15
41
|
```javascript
|
|
16
|
-
|
|
17
|
-
|
|
42
|
+
const { ObjectionJsDataServer } = require('@reldens/storage');
|
|
43
|
+
|
|
44
|
+
const server = new ObjectionJsDataServer({
|
|
45
|
+
client: 'mysql2',
|
|
18
46
|
config: {
|
|
19
47
|
user: 'reldens',
|
|
20
48
|
password: 'reldens',
|
|
21
49
|
database: 'reldens',
|
|
22
|
-
|
|
50
|
+
host: 'localhost',
|
|
51
|
+
port: 3306
|
|
23
52
|
}
|
|
24
53
|
});
|
|
54
|
+
|
|
55
|
+
await server.connect();
|
|
56
|
+
const entities = server.generateEntities();
|
|
25
57
|
```
|
|
26
|
-
|
|
58
|
+
|
|
59
|
+
### MongoDB with Mikro-ORM
|
|
27
60
|
```javascript
|
|
28
|
-
|
|
61
|
+
const { MikroOrmDataServer } = require('@reldens/storage');
|
|
62
|
+
|
|
63
|
+
const server = new MikroOrmDataServer({
|
|
29
64
|
client: 'mongodb',
|
|
30
65
|
config: {
|
|
31
66
|
user: 'reldens',
|
|
32
67
|
password: 'reldens',
|
|
33
|
-
database: '
|
|
34
|
-
|
|
68
|
+
database: 'reldens',
|
|
69
|
+
host: 'localhost',
|
|
70
|
+
port: 27017
|
|
35
71
|
},
|
|
36
|
-
connectStringOptions: 'authSource=reldens&readPreference=primary&
|
|
37
|
-
|
|
38
|
-
rawEntities: Object.values(rawRegisteredEntities)
|
|
72
|
+
connectStringOptions: 'authSource=reldens&readPreference=primary&ssl=false',
|
|
73
|
+
rawEntities: yourEntities
|
|
39
74
|
});
|
|
75
|
+
|
|
76
|
+
await server.connect();
|
|
77
|
+
const entities = server.generateEntities();
|
|
40
78
|
```
|
|
41
79
|
|
|
42
|
-
## Custom
|
|
43
|
-
Through this package you can also create your own drivers (to support your own storage system) and later implement it on any Reldens project.
|
|
80
|
+
## Custom Drivers
|
|
44
81
|
|
|
45
|
-
|
|
82
|
+
You can create custom storage drivers by extending the base classes:
|
|
46
83
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
84
|
+
1. Extend `BaseDataServer` and `BaseDriver`
|
|
85
|
+
2. Implement all required methods
|
|
86
|
+
3. Pass your custom server instance to Reldens ServerManager:
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
const { ServerManager } = require('@reldens/server');
|
|
90
|
+
const YourCustomDriver = require('./your-custom-driver');
|
|
91
|
+
|
|
92
|
+
const customDriver = new YourCustomDriver(options);
|
|
93
|
+
const appServer = new ServerManager(serverConfig, eventsManager, customDriver);
|
|
50
94
|
```
|
|
51
95
|
|
|
52
|
-
|
|
96
|
+
## Links
|
|
97
|
+
- [Reldens Website](https://www.reldens.com/)
|
|
98
|
+
- [GitHub Repository](https://github.com/damian-pastorini/reldens/tree/master)
|
|
53
99
|
|
|
54
|
-
|
|
100
|
+
---
|
|
55
101
|
|
|
56
102
|
### [Reldens](https://www.reldens.com/ "Reldens")
|
|
57
103
|
|
|
58
104
|
##### [By DwDeveloper](https://www.dwdeveloper.com/ "DwDeveloper")
|
|
105
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* Reldens - Storage CLI
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { EntitiesGenerator } = require('../lib/entities-generator');
|
|
10
|
+
const { Logger } = require('@reldens/utils');
|
|
11
|
+
|
|
12
|
+
let args = process.argv.slice(2);
|
|
13
|
+
let command = args[0];
|
|
14
|
+
|
|
15
|
+
if('generateEntities' !== command){
|
|
16
|
+
Logger.error('Unknown command. Available command "generateEntities".');
|
|
17
|
+
process.exit();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let connectionData = {
|
|
21
|
+
driver: 'objection-js',
|
|
22
|
+
client: 'mysql2',
|
|
23
|
+
user: '',
|
|
24
|
+
password: '',
|
|
25
|
+
host: 'localhost',
|
|
26
|
+
database: '',
|
|
27
|
+
port: 3306
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
let projectPath = process.cwd();
|
|
31
|
+
for(let i = 1; i < args.length; i++){
|
|
32
|
+
let arg = args[i];
|
|
33
|
+
if(!arg.startsWith('--')){
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
let [key, value] = arg.substring(2).split('=');
|
|
37
|
+
if(!key || !value){
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if('pass' === key){
|
|
41
|
+
connectionData.password = value;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if('path' === key){
|
|
45
|
+
projectPath = value;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
connectionData[key] = value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if('mikro-orm' === connectionData.driver && !args.find(arg => arg.startsWith('--client='))){
|
|
52
|
+
connectionData.client = 'mysql';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if(!connectionData.user || !connectionData.database){
|
|
56
|
+
Logger.error('Required parameters missing.');
|
|
57
|
+
Logger.error(
|
|
58
|
+
'Usage:',
|
|
59
|
+
'npx reldens-storage generateEntities --user=[db-username] --pass=[db-password] --host=[db-host] --database=[db-name] --driver=[driver-map-key] --client=[db-client] --path=[project-path]'
|
|
60
|
+
);
|
|
61
|
+
process.exit();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let generator = new EntitiesGenerator({connectionData, projectPath});
|
|
65
|
+
generator.generate().then((success) => {
|
|
66
|
+
if(!success){
|
|
67
|
+
Logger.error('Entity generation failed.');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
Logger.info('Entity generation completed successfully!');
|
|
71
|
+
process.exit();
|
|
72
|
+
}).catch((error) => {
|
|
73
|
+
Logger.error('Error during entity generation: '+error.message);
|
|
74
|
+
process.exit();
|
|
75
|
+
});
|
package/index.js
CHANGED
|
@@ -12,17 +12,30 @@ const { Model } = require('objection');
|
|
|
12
12
|
const { MikroOrmDriver } = require('./lib/mikro-orm/mikro-orm-driver');
|
|
13
13
|
const { MikroOrmDataServer } = require('./lib/mikro-orm/mikro-orm-data-server');
|
|
14
14
|
const MikroOrmCore = require('@mikro-orm/core');
|
|
15
|
+
const { EntitiesGenerator } = require('./lib/entities-generator');
|
|
16
|
+
const { EntityProperties } = require('./lib/entity-properties');
|
|
17
|
+
const { TypeMapper } = require('./lib/type-mapper');
|
|
18
|
+
const { MySQLTablesProvider } = require('./lib/mysql-tables-provider');
|
|
15
19
|
|
|
16
20
|
module.exports = {
|
|
17
21
|
// base:
|
|
18
|
-
BaseDataServer
|
|
19
|
-
BaseDriver
|
|
22
|
+
BaseDataServer,
|
|
23
|
+
BaseDriver,
|
|
24
|
+
DriversMap: {
|
|
25
|
+
'objection-js': ObjectionJsDataServer,
|
|
26
|
+
'mikro-orm': MikroOrmDataServer
|
|
27
|
+
},
|
|
20
28
|
// objection-js:
|
|
21
|
-
ObjectionJsDataServer
|
|
22
|
-
ObjectionJsDriver
|
|
29
|
+
ObjectionJsDataServer,
|
|
30
|
+
ObjectionJsDriver,
|
|
23
31
|
ObjectionJsRawModel: Model,
|
|
24
32
|
// mikro-orm:
|
|
25
33
|
MikroOrmCore,
|
|
26
|
-
MikroOrmDataServer
|
|
27
|
-
MikroOrmDriver
|
|
34
|
+
MikroOrmDataServer,
|
|
35
|
+
MikroOrmDriver,
|
|
36
|
+
// entities:
|
|
37
|
+
EntitiesGenerator,
|
|
38
|
+
EntityProperties,
|
|
39
|
+
TypeMapper,
|
|
40
|
+
MySQLTablesProvider
|
|
28
41
|
};
|
package/lib/base-data-server.js
CHANGED
|
@@ -56,6 +56,11 @@ class BaseDataServer
|
|
|
56
56
|
ErrorManager.error('BaseDriver connect() not implemented.');
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
async fetchEntitiesFromDatabase()
|
|
60
|
+
{
|
|
61
|
+
ErrorManager.error('BaseDriver fetchEntitiesFromDatabase() not implemented.');
|
|
62
|
+
}
|
|
63
|
+
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
module.exports.BaseDataServer = BaseDataServer;
|
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - EntitiesGenerator
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { FileHandler } = require('@reldens/server-utils');
|
|
8
|
+
const { ErrorManager, Logger, sc } = require('@reldens/utils');
|
|
9
|
+
const { ObjectionJsDataServer } = require('./objection-js/objection-js-data-server');
|
|
10
|
+
const { MikroOrmDataServer } = require('./mikro-orm/mikro-orm-data-server');
|
|
11
|
+
const { TypeMapper } = require('./type-mapper');
|
|
12
|
+
|
|
13
|
+
class EntitiesGenerator
|
|
14
|
+
{
|
|
15
|
+
|
|
16
|
+
constructor(props)
|
|
17
|
+
{
|
|
18
|
+
this.templatesFolderPath = FileHandler.joinPaths(__dirname, 'entity-templates');
|
|
19
|
+
this.templates = {
|
|
20
|
+
'objection-js': FileHandler.joinPaths(this.templatesFolderPath, 'objection-js-entity.template'),
|
|
21
|
+
'mikro-orm': FileHandler.joinPaths(this.templatesFolderPath, 'mikro-orm-entity.template'),
|
|
22
|
+
'entity': FileHandler.joinPaths(this.templatesFolderPath, 'entity.template'),
|
|
23
|
+
'entities-config': FileHandler.joinPaths(this.templatesFolderPath, 'entities-config.template'),
|
|
24
|
+
'entities-translations': FileHandler.joinPaths(this.templatesFolderPath, 'entities-translations.template'),
|
|
25
|
+
'registered-models': FileHandler.joinPaths(this.templatesFolderPath, 'registered-models.template')
|
|
26
|
+
};
|
|
27
|
+
this.projectPath = sc.get(props, 'projectPath', FileHandler.joinPaths(__dirname, '..'));
|
|
28
|
+
this.generationFolder = FileHandler.joinPaths(this.projectPath, 'generated-entities');
|
|
29
|
+
this.entitiesFolder = FileHandler.joinPaths(this.generationFolder, 'entities');
|
|
30
|
+
this.modelsFolder = FileHandler.joinPaths(this.generationFolder, 'models');
|
|
31
|
+
this.entitiesConfigPath = FileHandler.joinPaths(this.generationFolder, 'entities-config.js');
|
|
32
|
+
this.entitiesTranslationsPath = FileHandler.joinPaths(this.generationFolder, 'entities-translations.js');
|
|
33
|
+
this.driverMap = {
|
|
34
|
+
'objection-js': ObjectionJsDataServer,
|
|
35
|
+
'mikro-orm': MikroOrmDataServer
|
|
36
|
+
};
|
|
37
|
+
this.server = sc.get(props, 'server', false);
|
|
38
|
+
this.connectionData = sc.get(props, 'connectionData', false);
|
|
39
|
+
if(!this.server && !this.connectionData){
|
|
40
|
+
ErrorManager.error('Either server or connectionData must be provided to EntitiesGenerator.');
|
|
41
|
+
}
|
|
42
|
+
this.generatedEntities = {};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
ensureFoldersExist()
|
|
46
|
+
{
|
|
47
|
+
if(!FileHandler.exists(this.generationFolder)){
|
|
48
|
+
try {
|
|
49
|
+
FileHandler.createFolder(this.generationFolder, { recursive: true });
|
|
50
|
+
} catch (error) {
|
|
51
|
+
ErrorManager.error('Failed to create generation folder: '+error.message);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if(!FileHandler.exists(this.entitiesFolder)){
|
|
55
|
+
try {
|
|
56
|
+
FileHandler.createFolder(this.entitiesFolder, { recursive: true });
|
|
57
|
+
} catch (error) {
|
|
58
|
+
ErrorManager.error('Failed to create entities folder: '+error.message);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if(!FileHandler.exists(this.modelsFolder)){
|
|
62
|
+
try {
|
|
63
|
+
FileHandler.createFolder(this.modelsFolder, { recursive: true });
|
|
64
|
+
} catch (error) {
|
|
65
|
+
ErrorManager.error('Failed to create models folder: '+error.message);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
for(let driverKey of Object.keys(this.driverMap)){
|
|
69
|
+
let driverFolder = FileHandler.joinPaths(this.modelsFolder, driverKey);
|
|
70
|
+
if(!FileHandler.exists(driverFolder)){
|
|
71
|
+
try {
|
|
72
|
+
FileHandler.createFolder(driverFolder, { recursive: true });
|
|
73
|
+
} catch (error) {
|
|
74
|
+
ErrorManager.error('Failed to create driver folder: '+error.message);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async generateEntityFile(tableName, tableData)
|
|
81
|
+
{
|
|
82
|
+
let entityTemplatePath = this.templates['entity'];
|
|
83
|
+
if(!FileHandler.exists(entityTemplatePath)){
|
|
84
|
+
ErrorManager.error('Entity template file not found: '+entityTemplatePath);
|
|
85
|
+
}
|
|
86
|
+
let entityTemplateContent;
|
|
87
|
+
try {
|
|
88
|
+
entityTemplateContent = FileHandler.fetchFileContents(entityTemplatePath);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
ErrorManager.error('Failed to read entity template file: '+error.message);
|
|
91
|
+
}
|
|
92
|
+
let entityClassName = sc.capitalizedCamelCase(tableName)+'Entity';
|
|
93
|
+
let titleProperty = this.determineTitleProperty(tableData.columns);
|
|
94
|
+
for(let columnName in tableData.columns){
|
|
95
|
+
tableData.columns[columnName].tableName = tableName;
|
|
96
|
+
}
|
|
97
|
+
let propertiesConfig = this.generatePropertiesConfig(tableData.columns, titleProperty);
|
|
98
|
+
let titlePropertyDeclaration = titleProperty ? '\n let titleProperty = \''+titleProperty+'\';' : '';
|
|
99
|
+
let titlePropertyReturn = titleProperty ? '\n titleProperty,' : '';
|
|
100
|
+
let fieldsToRemoveFromEdit = ['id'];
|
|
101
|
+
if(tableData.columns['created_at']){
|
|
102
|
+
fieldsToRemoveFromEdit.push('created_at');
|
|
103
|
+
}
|
|
104
|
+
if(tableData.columns['updated_at']){
|
|
105
|
+
fieldsToRemoveFromEdit.push('updated_at');
|
|
106
|
+
}
|
|
107
|
+
let editPropertiesRemoval = this.getEditPropertiesRemoval(fieldsToRemoveFromEdit);
|
|
108
|
+
let listPropertiesDeclaration = this.getListPropertiesDeclaration(tableData.columns);
|
|
109
|
+
let needsSc = listPropertiesDeclaration.includes('sc.removeFromArray') || editPropertiesRemoval.includes('sc.removeFromArray');
|
|
110
|
+
let scRequire = needsSc ? '\nconst { sc } = require(\'@reldens/utils\');' : '';
|
|
111
|
+
let replacements = {
|
|
112
|
+
entityClassName,
|
|
113
|
+
propertiesConfig,
|
|
114
|
+
titlePropertyDeclaration,
|
|
115
|
+
titlePropertyReturn,
|
|
116
|
+
listPropertiesDeclaration,
|
|
117
|
+
editPropertiesRemoval,
|
|
118
|
+
scRequire
|
|
119
|
+
};
|
|
120
|
+
let entityContent = this.applyReplacements(entityTemplateContent, replacements);
|
|
121
|
+
let fileName = sc.kebabCase(tableName)+'-entity.js';
|
|
122
|
+
let filePath = FileHandler.joinPaths(this.entitiesFolder, fileName);
|
|
123
|
+
try {
|
|
124
|
+
FileHandler.writeFile(filePath, entityContent);
|
|
125
|
+
Logger.info('Generated entity file: '+filePath);
|
|
126
|
+
this.generatedEntities[tableName] = {
|
|
127
|
+
entityClassName,
|
|
128
|
+
entityFileName: fileName,
|
|
129
|
+
tableName,
|
|
130
|
+
titleProperty
|
|
131
|
+
};
|
|
132
|
+
return true;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
ErrorManager.error('Failed to write entity file: '+error.message);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
getEditPropertiesRemoval(fieldsToRemove)
|
|
139
|
+
{
|
|
140
|
+
if(1 === fieldsToRemove.length){
|
|
141
|
+
return 'let editProperties = [...showProperties];\n editProperties.splice(editProperties.indexOf(\''+fieldsToRemove[0]+'\'), 1);';
|
|
142
|
+
}
|
|
143
|
+
return 'let editProperties = sc.removeFromArray([...showProperties], [\''+fieldsToRemove.join('\', \'')+'\']);';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
determineTitleProperty(columns)
|
|
147
|
+
{
|
|
148
|
+
if(columns['label']){
|
|
149
|
+
return 'label';
|
|
150
|
+
}
|
|
151
|
+
if(columns['title']){
|
|
152
|
+
return 'title';
|
|
153
|
+
}
|
|
154
|
+
if(columns['name']){
|
|
155
|
+
return 'name';
|
|
156
|
+
}
|
|
157
|
+
if(columns['key']){
|
|
158
|
+
return 'key';
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
generatePropertiesConfig(columns, titleProperty)
|
|
164
|
+
{
|
|
165
|
+
let propertiesConfig = [];
|
|
166
|
+
for(let columnName in columns){
|
|
167
|
+
let column = columns[columnName];
|
|
168
|
+
let propertyKey = titleProperty && columnName === titleProperty ? '[titleProperty]' : columnName;
|
|
169
|
+
if('id' === columnName){
|
|
170
|
+
propertiesConfig.push(columnName+': {}');
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
let props = this.getPropertyAttributes(column);
|
|
174
|
+
if(0 === props.length){
|
|
175
|
+
propertiesConfig.push(propertyKey+': {}');
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
propertiesConfig.push(propertyKey+': {\n '+props.join(',\n ')+'\n }');
|
|
179
|
+
}
|
|
180
|
+
return propertiesConfig.join(',\n ');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
getPropertyAttributes(column)
|
|
184
|
+
{
|
|
185
|
+
let props = [];
|
|
186
|
+
let type = column.type.toLowerCase();
|
|
187
|
+
this.addTypeAttribute(props, column, type);
|
|
188
|
+
this.addRequiredAttribute(props, column);
|
|
189
|
+
return props;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
addTypeAttribute(props, column, type)
|
|
193
|
+
{
|
|
194
|
+
if(this.isForeignKey(column)){
|
|
195
|
+
props.push('type: \'reference\'');
|
|
196
|
+
props.push('reference: \''+this.getReferenceTable(column)+'\'');
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if('date' === type || 'datetime' === type || 'timestamp' === type){
|
|
200
|
+
props.push('type: \'datetime\'');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if('tinyint' === type){
|
|
204
|
+
props.push('type: \'boolean\'');
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if('enum' === type){
|
|
208
|
+
this.addEnumValues(props, column);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if(type.includes('int')){
|
|
212
|
+
props.push('type: \'number\'');
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if('varchar' === type || 'char' === type){
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
let jsType = TypeMapper.mapDbTypeToJsType(type);
|
|
219
|
+
if(jsType && 'string' !== jsType){
|
|
220
|
+
props.push('type: \''+jsType+'\'');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
addEnumValues(props, column)
|
|
225
|
+
{
|
|
226
|
+
let enumValues = this.parseEnumValues(column.columnType);
|
|
227
|
+
if(0 === enumValues.length){
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
let availableValues = [];
|
|
231
|
+
for(let i = 0; i < enumValues.length; i++){
|
|
232
|
+
availableValues.push('{value: '+(i+1)+', label: \''+enumValues[i]+'\'}');
|
|
233
|
+
}
|
|
234
|
+
props.push('availableValues: [\n '+availableValues.join(',\n ')+'\n ]');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
parseEnumValues(columnType)
|
|
238
|
+
{
|
|
239
|
+
if(!columnType){
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
let match = columnType.match(/^enum\('(.+)'\)$/i);
|
|
243
|
+
if(!match){
|
|
244
|
+
return [];
|
|
245
|
+
}
|
|
246
|
+
let valuesString = match[1];
|
|
247
|
+
let values = valuesString.split('\',\'');
|
|
248
|
+
return values;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
addRequiredAttribute(props, column)
|
|
252
|
+
{
|
|
253
|
+
if(!column.nullable && !column.default){
|
|
254
|
+
props.push('isRequired: true');
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
isForeignKey(column)
|
|
259
|
+
{
|
|
260
|
+
if(column.referencedTable){
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
getReferenceTable(column)
|
|
267
|
+
{
|
|
268
|
+
if(column.referencedTable){
|
|
269
|
+
return column.referencedTable;
|
|
270
|
+
}
|
|
271
|
+
let refTableName = column.name.replace('_id', '');
|
|
272
|
+
if(refTableName.includes('_')){
|
|
273
|
+
let parts = refTableName.split('_');
|
|
274
|
+
if(column.tableName && column.tableName === parts[0]){
|
|
275
|
+
refTableName = parts.slice(1).join('_');
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return refTableName;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
getListPropertiesDeclaration(columns)
|
|
282
|
+
{
|
|
283
|
+
let fieldsToRemove = this.getTextAndJsonFields(columns);
|
|
284
|
+
if(0 === fieldsToRemove.length){
|
|
285
|
+
return 'let listProperties = showProperties;';
|
|
286
|
+
}
|
|
287
|
+
if(1 === fieldsToRemove.length){
|
|
288
|
+
return 'let listProperties = [...showProperties];\n listProperties.splice(listProperties.indexOf(\''+fieldsToRemove[0]+'\'), 1);';
|
|
289
|
+
}
|
|
290
|
+
return 'let listProperties = sc.removeFromArray([...showProperties], [\''+fieldsToRemove.join('\', \'')+'\']);';
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
getTextAndJsonFields(columns)
|
|
294
|
+
{
|
|
295
|
+
let fieldsToRemove = [];
|
|
296
|
+
for(let columnName in columns){
|
|
297
|
+
let column = columns[columnName];
|
|
298
|
+
let type = column.type.toLowerCase();
|
|
299
|
+
if(['text', 'json', 'tinytext', 'mediumtext', 'longtext'].includes(type)){
|
|
300
|
+
fieldsToRemove.push(columnName);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return fieldsToRemove;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async generateModelFile(tableName, tableData, driverKey)
|
|
307
|
+
{
|
|
308
|
+
let modelTemplatePath = this.templates[driverKey];
|
|
309
|
+
if(!FileHandler.exists(modelTemplatePath)){
|
|
310
|
+
ErrorManager.error('Model template file not found: '+modelTemplatePath);
|
|
311
|
+
}
|
|
312
|
+
let modelTemplateContent;
|
|
313
|
+
try {
|
|
314
|
+
modelTemplateContent = FileHandler.fetchFileContents(modelTemplatePath);
|
|
315
|
+
} catch (error) {
|
|
316
|
+
ErrorManager.error('Failed to read model template file: '+error.message);
|
|
317
|
+
}
|
|
318
|
+
let modelClassName = sc.capitalizedCamelCase(tableName)+'Model';
|
|
319
|
+
let modelPropertiesList = Object.keys(tableData.columns).join(', ');
|
|
320
|
+
let modelPropertiesConstructor = Object.keys(tableData.columns)
|
|
321
|
+
.map(columnName => 'this.'+columnName+' = '+columnName+';')
|
|
322
|
+
.join('\n ');
|
|
323
|
+
let modelRelations = '';
|
|
324
|
+
let entityPropertiesDefinition = this.getEntityPropertiesDefinition(tableData.columns, driverKey);
|
|
325
|
+
let replacements = {
|
|
326
|
+
modelClassName,
|
|
327
|
+
tableName,
|
|
328
|
+
modelPropertiesList,
|
|
329
|
+
modelPropertiesConstructor,
|
|
330
|
+
modelRelations,
|
|
331
|
+
entityPropertiesDefinition
|
|
332
|
+
};
|
|
333
|
+
let modelContent = this.applyReplacements(modelTemplateContent, replacements);
|
|
334
|
+
let fileName = sc.kebabCase(tableName)+'-model.js';
|
|
335
|
+
let driverFolder = FileHandler.joinPaths(this.modelsFolder, driverKey);
|
|
336
|
+
let filePath = FileHandler.joinPaths(driverFolder, fileName);
|
|
337
|
+
try {
|
|
338
|
+
FileHandler.writeFile(filePath, modelContent);
|
|
339
|
+
Logger.info('Generated model file: '+filePath);
|
|
340
|
+
this.generatedEntities[tableName].modelClassName = modelClassName;
|
|
341
|
+
this.generatedEntities[tableName].modelFileName = fileName;
|
|
342
|
+
this.generatedEntities[tableName].driverKey = driverKey;
|
|
343
|
+
return true;
|
|
344
|
+
} catch (error) {
|
|
345
|
+
ErrorManager.error('Failed to write model file: '+error.message);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
getEntityPropertiesDefinition(columns, driverKey)
|
|
350
|
+
{
|
|
351
|
+
if('mikro-orm' !== driverKey){
|
|
352
|
+
return '';
|
|
353
|
+
}
|
|
354
|
+
let entityProps = [];
|
|
355
|
+
for(let columnName in columns){
|
|
356
|
+
let column = columns[columnName];
|
|
357
|
+
let isPrimary = 'PRI' === column.key;
|
|
358
|
+
let type = TypeMapper.mapDbTypeToJsType(column.type);
|
|
359
|
+
let propDef = columnName+': { type: \''+type+'\'';
|
|
360
|
+
if(isPrimary){
|
|
361
|
+
propDef += ', primary: true';
|
|
362
|
+
}
|
|
363
|
+
if(column.nullable){
|
|
364
|
+
propDef += ', nullable: true';
|
|
365
|
+
}
|
|
366
|
+
propDef += ' }';
|
|
367
|
+
entityProps.push(propDef);
|
|
368
|
+
}
|
|
369
|
+
return entityProps.join(',\n ');
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
generateEntitiesConfigFile()
|
|
373
|
+
{
|
|
374
|
+
let configTemplatePath = this.templates['entities-config'];
|
|
375
|
+
if(!FileHandler.exists(configTemplatePath)){
|
|
376
|
+
ErrorManager.error('Entities config template file not found: '+configTemplatePath);
|
|
377
|
+
}
|
|
378
|
+
let configTemplateContent;
|
|
379
|
+
try {
|
|
380
|
+
configTemplateContent = FileHandler.fetchFileContents(configTemplatePath);
|
|
381
|
+
} catch (error) {
|
|
382
|
+
ErrorManager.error('Failed to read entities config template file: '+error.message);
|
|
383
|
+
}
|
|
384
|
+
if(!configTemplateContent || 'string' !== typeof configTemplateContent){
|
|
385
|
+
ErrorManager.error('Invalid entities config template content');
|
|
386
|
+
}
|
|
387
|
+
let requireStatements = this.getRequireStatements();
|
|
388
|
+
let entitiesConfigExport = this.getEntitiesConfigExport();
|
|
389
|
+
let replacements = {
|
|
390
|
+
requireStatements,
|
|
391
|
+
entitiesConfigExport
|
|
392
|
+
};
|
|
393
|
+
let configContent = this.applyReplacements(configTemplateContent, replacements);
|
|
394
|
+
try {
|
|
395
|
+
FileHandler.writeFile(this.entitiesConfigPath, configContent);
|
|
396
|
+
Logger.info('Generated entities config file: '+this.entitiesConfigPath);
|
|
397
|
+
return true;
|
|
398
|
+
} catch (error) {
|
|
399
|
+
ErrorManager.error('Failed to write entities config file: '+error.message);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
getRequireStatements()
|
|
404
|
+
{
|
|
405
|
+
let requireStatements = [];
|
|
406
|
+
for(let tableName in this.generatedEntities){
|
|
407
|
+
let entity = this.generatedEntities[tableName];
|
|
408
|
+
requireStatements.push('const { '+entity.entityClassName+' } = require(\'./entities/'+entity.entityFileName.replace('.js', '')+'\');');
|
|
409
|
+
}
|
|
410
|
+
return requireStatements.join('\n');
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
getEntitiesConfigExport()
|
|
414
|
+
{
|
|
415
|
+
let entitiesConfigExport = [];
|
|
416
|
+
for(let tableName in this.generatedEntities){
|
|
417
|
+
let entity = this.generatedEntities[tableName];
|
|
418
|
+
let camelCaseKey = sc.camelCase(tableName);
|
|
419
|
+
entitiesConfigExport.push(camelCaseKey+': '+entity.entityClassName+'.propertiesConfig()');
|
|
420
|
+
}
|
|
421
|
+
return entitiesConfigExport.join(',\n ');
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
generateEntitiesTranslationsFile()
|
|
425
|
+
{
|
|
426
|
+
let translationsTemplatePath = this.templates['entities-translations'];
|
|
427
|
+
if(!FileHandler.exists(translationsTemplatePath)){
|
|
428
|
+
ErrorManager.error('Entities translations template file not found: '+translationsTemplatePath);
|
|
429
|
+
}
|
|
430
|
+
let translationsTemplateContent;
|
|
431
|
+
try {
|
|
432
|
+
translationsTemplateContent = FileHandler.fetchFileContents(translationsTemplatePath);
|
|
433
|
+
} catch (error) {
|
|
434
|
+
ErrorManager.error('Failed to read entities translations template file: '+error.message);
|
|
435
|
+
}
|
|
436
|
+
if(!translationsTemplateContent || 'string' !== typeof translationsTemplateContent){
|
|
437
|
+
ErrorManager.error('Invalid entities translations template content');
|
|
438
|
+
}
|
|
439
|
+
let labels = this.getTranslationLabels();
|
|
440
|
+
let translationsContent = translationsTemplateContent.replace(
|
|
441
|
+
/{{labels}}/g,
|
|
442
|
+
labels
|
|
443
|
+
);
|
|
444
|
+
try {
|
|
445
|
+
FileHandler.writeFile(this.entitiesTranslationsPath, translationsContent);
|
|
446
|
+
Logger.info('Generated entities translations file: '+this.entitiesTranslationsPath);
|
|
447
|
+
return true;
|
|
448
|
+
} catch (error) {
|
|
449
|
+
ErrorManager.error('Failed to write entities translations file: '+error.message);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
getTranslationLabels()
|
|
454
|
+
{
|
|
455
|
+
let labels = [];
|
|
456
|
+
for(let tableName in this.generatedEntities){
|
|
457
|
+
let humanReadable = tableName
|
|
458
|
+
.split('_')
|
|
459
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
460
|
+
.join(' ');
|
|
461
|
+
labels.push('\''+tableName+'\': \''+humanReadable+'\'');
|
|
462
|
+
}
|
|
463
|
+
return labels.join(',\n ');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
generateRegisteredModelsFile(driverKey)
|
|
467
|
+
{
|
|
468
|
+
let registeredTemplatePath = this.templates['registered-models'];
|
|
469
|
+
if(!FileHandler.exists(registeredTemplatePath)){
|
|
470
|
+
ErrorManager.error('Registered models template file not found: '+registeredTemplatePath);
|
|
471
|
+
}
|
|
472
|
+
let registeredTemplateContent;
|
|
473
|
+
try {
|
|
474
|
+
registeredTemplateContent = FileHandler.fetchFileContents(registeredTemplatePath);
|
|
475
|
+
} catch (error) {
|
|
476
|
+
ErrorManager.error('Failed to read registered models template file: '+error.message);
|
|
477
|
+
}
|
|
478
|
+
if(!registeredTemplateContent || 'string' !== typeof registeredTemplateContent){
|
|
479
|
+
ErrorManager.error('Invalid registered models template content');
|
|
480
|
+
}
|
|
481
|
+
let registeredModels = this.getRegisteredModels(driverKey);
|
|
482
|
+
let registeredEntitiesObject = this.getRegisteredEntitiesObject(driverKey);
|
|
483
|
+
let replacements = {
|
|
484
|
+
registeredModels,
|
|
485
|
+
registeredEntitiesObject
|
|
486
|
+
};
|
|
487
|
+
let registeredContent = this.applyReplacements(registeredTemplateContent, replacements);
|
|
488
|
+
let driverFolder = FileHandler.joinPaths(this.modelsFolder, driverKey);
|
|
489
|
+
let filePath = FileHandler.joinPaths(driverFolder, 'registered-models-'+driverKey+'.js');
|
|
490
|
+
try {
|
|
491
|
+
FileHandler.writeFile(filePath, registeredContent);
|
|
492
|
+
Logger.info('Generated registered models file: '+filePath);
|
|
493
|
+
return true;
|
|
494
|
+
} catch (error) {
|
|
495
|
+
ErrorManager.error('Failed to write registered models file: '+error.message);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
getRegisteredModels(driverKey)
|
|
500
|
+
{
|
|
501
|
+
let registeredModels = [];
|
|
502
|
+
for(let tableName in this.generatedEntities){
|
|
503
|
+
let entity = this.generatedEntities[tableName];
|
|
504
|
+
if(entity.driverKey === driverKey){
|
|
505
|
+
registeredModels.push('const { '+entity.modelClassName+' } = require(\'./'+entity.modelFileName.replace('.js', '')+'\');');
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return registeredModels.join('\n');
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
getRegisteredEntitiesObject(driverKey)
|
|
512
|
+
{
|
|
513
|
+
let registeredEntitiesObject = [];
|
|
514
|
+
for(let tableName in this.generatedEntities){
|
|
515
|
+
let entity = this.generatedEntities[tableName];
|
|
516
|
+
if(entity.driverKey === driverKey){
|
|
517
|
+
let camelCaseKey = sc.camelCase(tableName);
|
|
518
|
+
registeredEntitiesObject.push(camelCaseKey+': '+entity.modelClassName);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return registeredEntitiesObject.join(',\n ');
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
applyReplacements(content, replacements)
|
|
525
|
+
{
|
|
526
|
+
let result = content;
|
|
527
|
+
for(let placeholder in replacements){
|
|
528
|
+
result = result.replace(
|
|
529
|
+
new RegExp('{{'+placeholder+'}}', 'g'),
|
|
530
|
+
replacements[placeholder]
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
return result;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
async generate()
|
|
537
|
+
{
|
|
538
|
+
if(!this.server){
|
|
539
|
+
this.createServer();
|
|
540
|
+
}
|
|
541
|
+
try {
|
|
542
|
+
await this.server.connect();
|
|
543
|
+
} catch (error) {
|
|
544
|
+
ErrorManager.error('Failed to connect to database: '+error.message);
|
|
545
|
+
}
|
|
546
|
+
let tables = await this.server.fetchEntitiesFromDatabase();
|
|
547
|
+
if(!tables){
|
|
548
|
+
ErrorManager.error('Failed to fetch tables from database.');
|
|
549
|
+
}
|
|
550
|
+
this.ensureFoldersExist();
|
|
551
|
+
let driverKey = sc.get(this.connectionData, 'driver', 'objection-js');
|
|
552
|
+
for(let tableName in tables){
|
|
553
|
+
let tableData = tables[tableName];
|
|
554
|
+
await this.generateEntityFile(tableName, tableData);
|
|
555
|
+
await this.generateModelFile(tableName, tableData, driverKey);
|
|
556
|
+
}
|
|
557
|
+
this.generateEntitiesConfigFile();
|
|
558
|
+
this.generateEntitiesTranslationsFile();
|
|
559
|
+
this.generateRegisteredModelsFile(driverKey);
|
|
560
|
+
return true;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
createServer()
|
|
564
|
+
{
|
|
565
|
+
let driverKey = sc.get(this.connectionData, 'driver', 'objection-js');
|
|
566
|
+
let driverClassMapped = this.driverMap[driverKey];
|
|
567
|
+
if(!driverClassMapped){
|
|
568
|
+
ErrorManager.error('Unsupported driver: '+driverKey);
|
|
569
|
+
}
|
|
570
|
+
this.server = new driverClassMapped({
|
|
571
|
+
client: sc.get(this.connectionData, 'client', 'mysql2'),
|
|
572
|
+
config: {
|
|
573
|
+
user: sc.get(this.connectionData, 'user', ''),
|
|
574
|
+
password: sc.get(this.connectionData, 'password', ''),
|
|
575
|
+
database: sc.get(this.connectionData, 'database', ''),
|
|
576
|
+
host: sc.get(this.connectionData, 'host', 'localhost'),
|
|
577
|
+
port: sc.get(this.connectionData, 'port', 3306)
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
module.exports.EntitiesGenerator = EntitiesGenerator;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - EntityProperties
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { Logger } = require('@reldens/utils');
|
|
8
|
+
|
|
9
|
+
class EntityProperties
|
|
10
|
+
{
|
|
11
|
+
|
|
12
|
+
static propertiesDefinition()
|
|
13
|
+
{
|
|
14
|
+
Logger.alert('Method not implemented propertiesDefinition().');
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static propertiesConfig(extraProps)
|
|
19
|
+
{
|
|
20
|
+
Logger.alert('Method not implemented propertiesConfig().', extraProps);
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports.EntityProperties = EntityProperties;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - {{entityClassName}}
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { EntityProperties } = require('@reldens/storage');{{scRequire}}
|
|
8
|
+
|
|
9
|
+
class {{entityClassName}} extends EntityProperties
|
|
10
|
+
{
|
|
11
|
+
|
|
12
|
+
static propertiesConfig(extraProps)
|
|
13
|
+
{{{titlePropertyDeclaration}}
|
|
14
|
+
let properties = {
|
|
15
|
+
{{propertiesConfig}}
|
|
16
|
+
};
|
|
17
|
+
let showProperties = Object.keys(properties);
|
|
18
|
+
{{editPropertiesRemoval}}
|
|
19
|
+
{{listPropertiesDeclaration}}
|
|
20
|
+
return {
|
|
21
|
+
showProperties,
|
|
22
|
+
editProperties,
|
|
23
|
+
listProperties,
|
|
24
|
+
filterProperties: listProperties,
|
|
25
|
+
properties,{{titlePropertyReturn}}
|
|
26
|
+
...extraProps
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports.{{entityClassName}} = {{entityClassName}};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - {{modelClassName}}
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { MikroOrmCore } = require('@reldens/storage');
|
|
8
|
+
const { EntitySchema } = MikroOrmCore;
|
|
9
|
+
|
|
10
|
+
class {{modelClassName}}
|
|
11
|
+
{
|
|
12
|
+
|
|
13
|
+
constructor({{modelPropertiesList}})
|
|
14
|
+
{
|
|
15
|
+
{{modelPropertiesConstructor}}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static createByProps(props)
|
|
19
|
+
{
|
|
20
|
+
const {{{modelPropertiesList}}} = props;
|
|
21
|
+
return new this({{modelPropertiesList}});
|
|
22
|
+
}
|
|
23
|
+
{{modelRelations}}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const schema = new EntitySchema({
|
|
27
|
+
class: {{modelClassName}},
|
|
28
|
+
properties: {
|
|
29
|
+
{{entityPropertiesDefinition}}
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
{{modelClassName}},
|
|
35
|
+
entity: {{modelClassName}},
|
|
36
|
+
schema: schema
|
|
37
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - {{modelClassName}}
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { ObjectionJsRawModel } = require('@reldens/storage');
|
|
8
|
+
|
|
9
|
+
class {{modelClassName}} extends ObjectionJsRawModel
|
|
10
|
+
{
|
|
11
|
+
|
|
12
|
+
static get tableName()
|
|
13
|
+
{
|
|
14
|
+
return '{{tableName}}';
|
|
15
|
+
}
|
|
16
|
+
{{modelRelations}}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports.{{modelClassName}} = {{modelClassName}};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - Registered Models
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
{{registeredModels}}
|
|
8
|
+
const { entitiesConfig } = require('../../entities-config');
|
|
9
|
+
const { entitiesTranslations } = require('../../entities-translations');
|
|
10
|
+
|
|
11
|
+
let rawRegisteredEntities = {
|
|
12
|
+
{{registeredEntitiesObject}}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
module.exports.rawRegisteredEntities = rawRegisteredEntities;
|
|
16
|
+
|
|
17
|
+
module.exports.entitiesConfig = entitiesConfig;
|
|
18
|
+
|
|
19
|
+
module.exports.entitiesTranslations = entitiesTranslations;
|
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
const { MikroOrmDriver } = require('./mikro-orm-driver');
|
|
8
8
|
const { BaseDataServer } = require('../base-data-server');
|
|
9
|
+
const { MySQLTablesProvider } = require('../mysql-tables-provider');
|
|
9
10
|
const { MikroORM } = require('@mikro-orm/core');
|
|
10
11
|
const { MySqlDriver } = require('@mikro-orm/mysql');
|
|
11
12
|
const { MongoDriver } = require('@mikro-orm/mongodb');
|
|
12
|
-
const { Logger,
|
|
13
|
+
const { ErrorManager, Logger, sc } = require('@reldens/utils');
|
|
13
14
|
|
|
14
15
|
class MikroOrmDataServer extends BaseDataServer
|
|
15
16
|
{
|
|
@@ -22,9 +23,9 @@ class MikroOrmDataServer extends BaseDataServer
|
|
|
22
23
|
|
|
23
24
|
constructor(props)
|
|
24
25
|
{
|
|
25
|
-
// @TODO - BETA - Finish implementation.
|
|
26
26
|
super(props);
|
|
27
27
|
this.entitiesPath = props.entitiesPath;
|
|
28
|
+
this.warnWhenNoEntities = Boolean(props.warnWhenNoEntities);
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
async connect()
|
|
@@ -36,7 +37,7 @@ class MikroOrmDataServer extends BaseDataServer
|
|
|
36
37
|
driver: this.clientsMapped[(this.client || 'mongodb')],
|
|
37
38
|
dbName: this.config.database,
|
|
38
39
|
clientUrl: this.connectString,
|
|
39
|
-
allowGlobalContext: true
|
|
40
|
+
allowGlobalContext: true,
|
|
40
41
|
};
|
|
41
42
|
if(this.rawEntities){
|
|
42
43
|
providedSetup.entities = Object.values(this.rawEntities);
|
|
@@ -47,6 +48,7 @@ class MikroOrmDataServer extends BaseDataServer
|
|
|
47
48
|
if(this.entitiesPath){
|
|
48
49
|
providedSetup.entities = this.entitiesPath;
|
|
49
50
|
}
|
|
51
|
+
providedSetup.discovery = {warnWhenNoEntities: this.warnWhenNoEntities};
|
|
50
52
|
this.orm = await MikroORM.init(providedSetup);
|
|
51
53
|
this.initialized = Date.now();
|
|
52
54
|
return this.initialized;
|
|
@@ -85,8 +87,43 @@ class MikroOrmDataServer extends BaseDataServer
|
|
|
85
87
|
|
|
86
88
|
async rawQuery(content)
|
|
87
89
|
{
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
try {
|
|
91
|
+
let queryResult = await this.orm.em.execute(content);
|
|
92
|
+
if(!sc.isArray(queryResult) || 0 === queryResult.length){
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
return queryResult;
|
|
96
|
+
} catch(error) {
|
|
97
|
+
Logger.error('Raw query failed: '+error.message);
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async fetchEntitiesFromDatabase()
|
|
103
|
+
{
|
|
104
|
+
if(!this.initialized){
|
|
105
|
+
Logger.error('Connection was not initialized, please use the connect method first.');
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
if('mysql' === this.client){
|
|
110
|
+
return await MySQLTablesProvider.fetchTables(this);
|
|
111
|
+
}
|
|
112
|
+
if('mongodb' === this.client){
|
|
113
|
+
let tables = {};
|
|
114
|
+
let collections = await this.orm.em.getDriver().getConnection().db().listCollections().toArray();
|
|
115
|
+
for(let collection of collections){
|
|
116
|
+
tables[collection.name] = {
|
|
117
|
+
name: collection.name,
|
|
118
|
+
columns: {}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return tables;
|
|
122
|
+
}
|
|
123
|
+
ErrorManager.error('Unsupported client type: '+this.client);
|
|
124
|
+
} catch(error) {
|
|
125
|
+
ErrorManager.error('Failed to fetch tables from database: ' + error.message);
|
|
126
|
+
}
|
|
90
127
|
}
|
|
91
128
|
|
|
92
129
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - MySQLTablesProvider
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { ErrorManager, sc } = require('@reldens/utils');
|
|
8
|
+
|
|
9
|
+
class MySQLTablesProvider
|
|
10
|
+
{
|
|
11
|
+
|
|
12
|
+
static async fetchTables(server)
|
|
13
|
+
{
|
|
14
|
+
let tablesQuery =
|
|
15
|
+
'SELECT ' +
|
|
16
|
+
' TABLE_NAME, ' +
|
|
17
|
+
' COLUMN_NAME, ' +
|
|
18
|
+
' DATA_TYPE, ' +
|
|
19
|
+
' CHARACTER_MAXIMUM_LENGTH, ' +
|
|
20
|
+
' COLUMN_TYPE, ' +
|
|
21
|
+
' IS_NULLABLE, ' +
|
|
22
|
+
' COLUMN_KEY, ' +
|
|
23
|
+
' EXTRA, ' +
|
|
24
|
+
' COLUMN_DEFAULT ' +
|
|
25
|
+
'FROM ' +
|
|
26
|
+
' information_schema.COLUMNS ' +
|
|
27
|
+
'WHERE ' +
|
|
28
|
+
' TABLE_SCHEMA = \'' + server.config.database + '\' ' +
|
|
29
|
+
'ORDER BY ' +
|
|
30
|
+
' TABLE_NAME, ORDINAL_POSITION';
|
|
31
|
+
try {
|
|
32
|
+
let result = await server.rawQuery(tablesQuery);
|
|
33
|
+
if(!sc.isArray(result) || 0 === result.length){
|
|
34
|
+
ErrorManager.error('No tables found in the database.');
|
|
35
|
+
}
|
|
36
|
+
let tables = {};
|
|
37
|
+
for(let row of result){
|
|
38
|
+
if(!tables[row.TABLE_NAME]){
|
|
39
|
+
tables[row.TABLE_NAME] = {
|
|
40
|
+
name: row.TABLE_NAME,
|
|
41
|
+
columns: {}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
tables[row.TABLE_NAME].columns[row.COLUMN_NAME] = {
|
|
45
|
+
name: row.COLUMN_NAME,
|
|
46
|
+
type: row.DATA_TYPE,
|
|
47
|
+
columnType: row.COLUMN_TYPE,
|
|
48
|
+
length: row.CHARACTER_MAXIMUM_LENGTH,
|
|
49
|
+
nullable: 'YES' === row.IS_NULLABLE,
|
|
50
|
+
key: row.COLUMN_KEY,
|
|
51
|
+
extra: row.EXTRA,
|
|
52
|
+
default: row.COLUMN_DEFAULT
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
let foreignKeysQuery =
|
|
56
|
+
'SELECT ' +
|
|
57
|
+
' TABLE_NAME, ' +
|
|
58
|
+
' COLUMN_NAME, ' +
|
|
59
|
+
' REFERENCED_TABLE_NAME, ' +
|
|
60
|
+
' REFERENCED_COLUMN_NAME ' +
|
|
61
|
+
'FROM ' +
|
|
62
|
+
' information_schema.KEY_COLUMN_USAGE ' +
|
|
63
|
+
'WHERE ' +
|
|
64
|
+
' TABLE_SCHEMA = \'' + server.config.database + '\' ' +
|
|
65
|
+
' AND REFERENCED_TABLE_NAME IS NOT NULL';
|
|
66
|
+
|
|
67
|
+
let foreignKeysResult = await server.rawQuery(foreignKeysQuery);
|
|
68
|
+
if(sc.isArray(foreignKeysResult) && 0 < foreignKeysResult.length){
|
|
69
|
+
for(let row of foreignKeysResult){
|
|
70
|
+
if(
|
|
71
|
+
tables[row.TABLE_NAME] &&
|
|
72
|
+
tables[row.TABLE_NAME].columns[row.COLUMN_NAME]
|
|
73
|
+
){
|
|
74
|
+
tables[row.TABLE_NAME].columns[row.COLUMN_NAME].referencedTable = row.REFERENCED_TABLE_NAME;
|
|
75
|
+
tables[row.TABLE_NAME].columns[row.COLUMN_NAME].referencedColumn = row.REFERENCED_COLUMN_NAME;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return tables;
|
|
80
|
+
} catch(error) {
|
|
81
|
+
ErrorManager.error('Failed to fetch tables from database: '+error.message);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports.MySQLTablesProvider = MySQLTablesProvider;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
const { BaseDataServer } = require('../base-data-server');
|
|
8
8
|
const { ObjectionJsDriver } = require('./objection-js-driver');
|
|
9
|
+
const { MySQLTablesProvider } = require('../mysql-tables-provider');
|
|
9
10
|
const { Model } = require('objection');
|
|
10
11
|
const Knex = require('knex');
|
|
11
12
|
const { Logger } = require('@reldens/utils');
|
|
@@ -72,7 +73,17 @@ class ObjectionJsDataServer extends BaseDataServer
|
|
|
72
73
|
|
|
73
74
|
async rawQuery(content)
|
|
74
75
|
{
|
|
75
|
-
|
|
76
|
+
let result = await this.knex.raw(content);
|
|
77
|
+
return result[0] || false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async fetchEntitiesFromDatabase()
|
|
81
|
+
{
|
|
82
|
+
if(!this.initialized){
|
|
83
|
+
Logger.error('Connection was not initialized, please use the connect method first.');
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
return await MySQLTablesProvider.fetchTables(this);
|
|
76
87
|
}
|
|
77
88
|
|
|
78
89
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - TypeMapper
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class TypeMapper
|
|
8
|
+
{
|
|
9
|
+
|
|
10
|
+
mapDbTypeToJsType(dbType)
|
|
11
|
+
{
|
|
12
|
+
let typeMap = {
|
|
13
|
+
'int': 'number',
|
|
14
|
+
'integer': 'number',
|
|
15
|
+
'tinyint': 'number',
|
|
16
|
+
'smallint': 'number',
|
|
17
|
+
'mediumint': 'number',
|
|
18
|
+
'bigint': 'number',
|
|
19
|
+
'decimal': 'number',
|
|
20
|
+
'float': 'number',
|
|
21
|
+
'double': 'number',
|
|
22
|
+
'varchar': 'string',
|
|
23
|
+
'char': 'string',
|
|
24
|
+
'text': 'string',
|
|
25
|
+
'tinytext': 'string',
|
|
26
|
+
'mediumtext': 'string',
|
|
27
|
+
'longtext': 'string',
|
|
28
|
+
'date': 'Date',
|
|
29
|
+
'datetime': 'Date',
|
|
30
|
+
'timestamp': 'Date',
|
|
31
|
+
'time': 'string',
|
|
32
|
+
'year': 'number',
|
|
33
|
+
'boolean': 'boolean',
|
|
34
|
+
'bool': 'boolean',
|
|
35
|
+
'bit': 'boolean',
|
|
36
|
+
'json': 'object',
|
|
37
|
+
'binary': 'Buffer',
|
|
38
|
+
'varbinary': 'Buffer',
|
|
39
|
+
'blob': 'Buffer',
|
|
40
|
+
'tinyblob': 'Buffer',
|
|
41
|
+
'mediumblob': 'Buffer',
|
|
42
|
+
'longblob': 'Buffer'
|
|
43
|
+
};
|
|
44
|
+
return typeMap[dbType.toLowerCase()];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports.TypeMapper = new TypeMapper();
|
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reldens/storage",
|
|
3
3
|
"scope": "@reldens",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.33.0",
|
|
5
5
|
"description": "Reldens - Storage",
|
|
6
6
|
"author": "Damian A. Pastorini",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"homepage": "https://github.com/damian-pastorini/reldens-storage",
|
|
9
9
|
"source": true,
|
|
10
10
|
"main": "index.js",
|
|
11
|
+
"bin": {
|
|
12
|
+
"reldens-storage": "./bin/reldens-storage.js"
|
|
13
|
+
},
|
|
11
14
|
"repository": {
|
|
12
15
|
"type": "git",
|
|
13
16
|
"url": "https://github.com/damian-pastorini/reldens-storage.git"
|
|
@@ -44,13 +47,14 @@
|
|
|
44
47
|
"url": "https://github.com/damian-pastorini/reldens-storage/issues"
|
|
45
48
|
},
|
|
46
49
|
"dependencies": {
|
|
47
|
-
"@mikro-orm/core": "^6.4.
|
|
48
|
-
"@mikro-orm/mongodb": "^6.4.
|
|
49
|
-
"@mikro-orm/mysql": "^6.4.
|
|
50
|
-
"@reldens/utils": "^0.
|
|
50
|
+
"@mikro-orm/core": "^6.4.15",
|
|
51
|
+
"@mikro-orm/mongodb": "^6.4.15",
|
|
52
|
+
"@mikro-orm/mysql": "^6.4.15",
|
|
53
|
+
"@reldens/utils": "^0.47.0",
|
|
54
|
+
"@reldens/server-utils": "^0.15.0",
|
|
51
55
|
"knex": "^3.1.0",
|
|
52
56
|
"mysql": "^2.18.1",
|
|
53
|
-
"mysql2": "^3.14.
|
|
57
|
+
"mysql2": "^3.14.1",
|
|
54
58
|
"objection": "^3.1.5"
|
|
55
59
|
}
|
|
56
60
|
}
|