@reldens/storage 0.52.0 → 0.54.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 +28 -21
- package/bin/generate-prisma-schema.js +132 -59
- package/bin/reldens-storage.js +106 -51
- package/lib/entities-generator.js +230 -41
- package/lib/prisma/prisma-driver.js +55 -2
- package/lib/prisma/prisma-schema-generator.js +34 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,32 +23,39 @@ It ensures consistent data access methods across different database types and OR
|
|
|
23
23
|
### CLI Tools
|
|
24
24
|
Generate entity files directly from your database structure:
|
|
25
25
|
```bash
|
|
26
|
-
npx reldens-storage generateEntities --user=dbuser --pass=dbpass --database=dbname --driver=objection-js
|
|
26
|
+
npx reldens-storage generateEntities --user=[dbuser] --pass=[dbpass] --database=[dbname] --driver=[objection-js]
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
Options:
|
|
30
|
-
- `--user` - Database username
|
|
31
|
-
- `--pass` - Database password
|
|
32
|
-
- `--host` - Database host (default: localhost)
|
|
33
|
-
- `--port` - Database port (default: 3306)
|
|
34
|
-
- `--database` - Database name
|
|
35
|
-
- `--driver` - ORM driver (objection-js|mikro-orm)
|
|
36
|
-
- `--client` - Database client (mysql|mysql2|mongodb)
|
|
37
|
-
- `--path` - Project path for output files
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
- `--user=[username]` - Database username
|
|
31
|
+
- `--pass=[password]` - Database password
|
|
32
|
+
- `--host=[host]` - Database host (default: localhost)
|
|
33
|
+
- `--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
|
|
38
|
+
- `--override` - Regenerate all files even if they exist
|
|
39
|
+
|
|
40
|
+
Generate Prisma schema:
|
|
40
41
|
```bash
|
|
41
|
-
npx reldens-
|
|
42
|
+
npx reldens-generate-prisma-schema --host=[host] --port=[port] --user=[dbuser] --password=[dbpass] --database=[dbname]
|
|
42
43
|
```
|
|
43
44
|
|
|
44
45
|
Options:
|
|
45
|
-
- `--
|
|
46
|
-
- `--
|
|
47
|
-
- `--
|
|
48
|
-
- `--
|
|
49
|
-
- `--database` - Database name
|
|
50
|
-
- `--client` - Database client (mysql
|
|
51
|
-
- `--
|
|
46
|
+
- `--host=[host]` - Database host (required)
|
|
47
|
+
- `--port=[port]` - Database port (required)
|
|
48
|
+
- `--user=[username]` - Database username (required)
|
|
49
|
+
- `--password=[password]` - Database password (required)
|
|
50
|
+
- `--database=[name]` - Database name (required)
|
|
51
|
+
- `--client=[client]` - Database client (default: mysql)
|
|
52
|
+
- `--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)
|
|
58
|
+
- `--generateBinaryTargets=[targets]` - Comma-separated binary targets (default: native)
|
|
52
59
|
|
|
53
60
|
## Usage Examples
|
|
54
61
|
|
|
@@ -96,7 +103,7 @@ const entities = server.generateEntities();
|
|
|
96
103
|
|
|
97
104
|
First, generate your Prisma schema:
|
|
98
105
|
```bash
|
|
99
|
-
npx reldens-
|
|
106
|
+
npx reldens-generate-prisma-schema --host=localhost --port=3306 --user=dbuser --password=dbpass --database=dbname
|
|
100
107
|
```
|
|
101
108
|
|
|
102
109
|
Then, use the PrismaDataServer in your code:
|
|
@@ -119,7 +126,7 @@ await server.connect();
|
|
|
119
126
|
const entities = server.generateEntities();
|
|
120
127
|
```
|
|
121
128
|
|
|
122
|
-
Note: The PrismaDataServer requires the Prisma schema to be generated first. Make sure to run the `reldens-
|
|
129
|
+
Note: The PrismaDataServer requires the Prisma schema to be generated first. Make sure to run the `reldens-generate-prisma-schema` command before using PrismaDataServer.
|
|
123
130
|
|
|
124
131
|
## Custom Drivers
|
|
125
132
|
|
|
@@ -7,79 +7,152 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const { PrismaSchemaGenerator } = require('../lib/prisma/prisma-schema-generator');
|
|
10
|
-
const { Logger } = require('@reldens/utils');
|
|
10
|
+
const { Logger, sc } = require('@reldens/utils');
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
class PrismaSchemaGeneratorCLI
|
|
13
|
+
{
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
host: 'localhost',
|
|
20
|
-
database: '',
|
|
21
|
-
port: 3306
|
|
15
|
+
constructor()
|
|
16
|
+
{
|
|
17
|
+
this.args = process.argv.slice(2);
|
|
18
|
+
this.config = {};
|
|
19
|
+
this.parseArguments();
|
|
22
20
|
}
|
|
23
|
-
};
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
parseArguments()
|
|
23
|
+
{
|
|
24
|
+
for(let i = 0; i < this.args.length; i++){
|
|
25
|
+
let arg = this.args[i];
|
|
26
|
+
if(!arg.startsWith('--')){
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
let equalIndex = arg.indexOf('=');
|
|
30
|
+
if(-1 === equalIndex){
|
|
31
|
+
let flag = arg.substring(2);
|
|
32
|
+
if('debug' === flag || 'dataProxy' === flag || 'help' === flag || 'h' === flag){
|
|
33
|
+
this.config[flag] = true;
|
|
34
|
+
}
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
let key = arg.substring(2, equalIndex);
|
|
38
|
+
let value = arg.substring(equalIndex + 1);
|
|
39
|
+
if('port' === key || 'checkInterval' === key || 'maxWaitTime' === key){
|
|
40
|
+
this.config[key] = parseInt(value);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if('generateBinaryTargets' === key){
|
|
44
|
+
this.config[key] = value.split(',').map(target => target.trim());
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
this.config[key] = value;
|
|
48
|
+
}
|
|
30
49
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if('pass' === key){
|
|
36
|
-
connectionData.config.password = value;
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if('path' === key){
|
|
40
|
-
projectPath = value;
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
if('user' === key){
|
|
44
|
-
connectionData.config.user = value;
|
|
45
|
-
continue;
|
|
50
|
+
|
|
51
|
+
shouldShowHelp()
|
|
52
|
+
{
|
|
53
|
+
return 0 === this.args.length || sc.get(this.config, 'help', false) || sc.get(this.config, 'h', false);
|
|
46
54
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
|
|
56
|
+
showHelp()
|
|
57
|
+
{
|
|
58
|
+
Logger.info('');
|
|
59
|
+
Logger.info('Reldens Prisma Schema Generator');
|
|
60
|
+
Logger.info('================================');
|
|
61
|
+
Logger.info('');
|
|
62
|
+
Logger.info('Usage: npx reldens-generate-prisma-schema [options]');
|
|
63
|
+
Logger.info('');
|
|
64
|
+
Logger.info('Required Options:');
|
|
65
|
+
Logger.info(' --host=[host] Database host');
|
|
66
|
+
Logger.info(' --port=[port] Database port');
|
|
67
|
+
Logger.info(' --user=[user] Database user');
|
|
68
|
+
Logger.info(' --password=[password] Database password');
|
|
69
|
+
Logger.info(' --database=[database] Database name');
|
|
70
|
+
Logger.info('');
|
|
71
|
+
Logger.info('Optional Options:');
|
|
72
|
+
Logger.info(' --client=[client] Database client (default: mysql)');
|
|
73
|
+
Logger.info(' --debug Enable debug mode (default: false)');
|
|
74
|
+
Logger.info(' --dataProxy Enable data proxy (default: false)');
|
|
75
|
+
Logger.info(' --checkInterval=[ms] Check interval in milliseconds (default: 1000)');
|
|
76
|
+
Logger.info(' --maxWaitTime=[ms] Max wait time in milliseconds (default: 30000)');
|
|
77
|
+
Logger.info(' --prismaSchemaPath=[path] Path to Prisma schema directory');
|
|
78
|
+
Logger.info(' --clientOutputPath=[path] Client output path (if not set, uses Prisma default)');
|
|
79
|
+
Logger.info(' --generateBinaryTargets=[targets] Comma-separated binary targets (default: native)');
|
|
80
|
+
Logger.info('');
|
|
81
|
+
Logger.info('Example:');
|
|
82
|
+
Logger.info(' npx reldens-generate-prisma-schema --host=localhost --port=3306 --user=root --password=secret --database=mydb');
|
|
83
|
+
Logger.info('');
|
|
84
|
+
Logger.info('Advanced Example:');
|
|
85
|
+
Logger.info(' npx reldens-generate-prisma-schema --host=localhost --port=3306 --user=root --password=secret --database=mydb --client=postgresql --debug --dataProxy --checkInterval=2000 --maxWaitTime=60000 --prismaSchemaPath=./custom/prisma --clientOutputPath=./custom/client --generateBinaryTargets=native,debian-openssl-1.1.x');
|
|
86
|
+
Logger.info('');
|
|
50
87
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
88
|
+
|
|
89
|
+
validateRequiredArgs()
|
|
90
|
+
{
|
|
91
|
+
let requiredArgs = ['host', 'port', 'user', 'password', 'database'];
|
|
92
|
+
let missing = [];
|
|
93
|
+
for(let arg of requiredArgs){
|
|
94
|
+
if(!this.config[arg]){
|
|
95
|
+
missing.push(arg);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if(0 < missing.length){
|
|
99
|
+
Logger.error('Missing required arguments: ' + missing.join(', '));
|
|
100
|
+
this.showHelp();
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
54
104
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
105
|
+
|
|
106
|
+
getSchemaConfig()
|
|
107
|
+
{
|
|
108
|
+
return {
|
|
109
|
+
config: {
|
|
110
|
+
host: this.config.host,
|
|
111
|
+
port: this.config.port,
|
|
112
|
+
user: this.config.user,
|
|
113
|
+
password: this.config.password,
|
|
114
|
+
database: this.config.database
|
|
115
|
+
},
|
|
116
|
+
client: sc.get(this.config, 'client', 'mysql'),
|
|
117
|
+
debug: sc.get(this.config, 'debug', false),
|
|
118
|
+
dataProxy: sc.get(this.config, 'dataProxy', false),
|
|
119
|
+
checkInterval: sc.get(this.config, 'checkInterval', 1000),
|
|
120
|
+
maxWaitTime: sc.get(this.config, 'maxWaitTime', 30000),
|
|
121
|
+
prismaSchemaPath: sc.get(this.config, 'prismaSchemaPath'),
|
|
122
|
+
clientOutputPath: sc.get(this.config, 'clientOutputPath'),
|
|
123
|
+
generateBinaryTargets: sc.get(this.config, 'generateBinaryTargets')
|
|
124
|
+
};
|
|
58
125
|
}
|
|
59
|
-
|
|
60
|
-
|
|
126
|
+
|
|
127
|
+
async run()
|
|
128
|
+
{
|
|
129
|
+
if(this.shouldShowHelp()){
|
|
130
|
+
this.showHelp();
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
if(!this.validateRequiredArgs()){
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
let schemaConfig = this.getSchemaConfig();
|
|
137
|
+
let generator = new PrismaSchemaGenerator(schemaConfig);
|
|
138
|
+
let success = await generator.generate();
|
|
139
|
+
if(!success){
|
|
140
|
+
Logger.critical('Failed to generate Prisma schema');
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
Logger.info('Prisma schema generation completed successfully');
|
|
144
|
+
return true;
|
|
61
145
|
}
|
|
62
|
-
}
|
|
63
146
|
|
|
64
|
-
if(!connectionData.config.user || !connectionData.config.database){
|
|
65
|
-
Logger.error('Required parameters missing.');
|
|
66
|
-
Logger.error(
|
|
67
|
-
'Usage:',
|
|
68
|
-
'npx reldens-storage generatePrismaSchema --user=[db-username] --pass=[db-password] --host=[db-host] --database=[db-name] --client=[db-client] --path=[project-path]'
|
|
69
|
-
);
|
|
70
|
-
process.exit();
|
|
71
147
|
}
|
|
72
148
|
|
|
73
|
-
let generator = new
|
|
74
|
-
|
|
75
|
-
generator.generate().then((success) => {
|
|
149
|
+
let generator = new PrismaSchemaGeneratorCLI();
|
|
150
|
+
generator.run().then((success) => {
|
|
76
151
|
if(!success){
|
|
77
|
-
|
|
78
|
-
process.exit();
|
|
152
|
+
process.exit(1);
|
|
79
153
|
}
|
|
80
|
-
|
|
81
|
-
process.exit();
|
|
154
|
+
process.exit(0);
|
|
82
155
|
}).catch((error) => {
|
|
83
|
-
Logger.
|
|
84
|
-
process.exit();
|
|
156
|
+
Logger.critical('Unexpected error: ' + error.message);
|
|
157
|
+
process.exit(1);
|
|
85
158
|
});
|
package/bin/reldens-storage.js
CHANGED
|
@@ -7,69 +7,124 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const { EntitiesGenerator } = require('../lib/entities-generator');
|
|
10
|
-
const { Logger } = require('@reldens/utils');
|
|
10
|
+
const { Logger, sc } = require('@reldens/utils');
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
class StorageEntitiesGenerator
|
|
13
|
+
{
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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;
|
|
15
|
+
constructor()
|
|
16
|
+
{
|
|
17
|
+
this.args = process.argv.slice(2);
|
|
18
|
+
this.command = this.args[0];
|
|
19
|
+
this.config = {};
|
|
20
|
+
this.projectPath = process.cwd();
|
|
21
|
+
this.isOverride = false;
|
|
22
|
+
this.parseArguments();
|
|
35
23
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
24
|
+
|
|
25
|
+
parseArguments()
|
|
26
|
+
{
|
|
27
|
+
for(let i = 1; i < this.args.length; i++){
|
|
28
|
+
let arg = this.args[i];
|
|
29
|
+
if(!arg.startsWith('--')){
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
let equalIndex = arg.indexOf('=');
|
|
33
|
+
if(-1 === equalIndex){
|
|
34
|
+
let flag = arg.substring(2);
|
|
35
|
+
if('override' === flag){
|
|
36
|
+
this.isOverride = true;
|
|
37
|
+
}
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
let key = arg.substring(2, equalIndex);
|
|
41
|
+
let value = arg.substring(equalIndex + 1);
|
|
42
|
+
if('path' === key){
|
|
43
|
+
this.projectPath = value;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if('pass' === key){
|
|
47
|
+
this.config['password'] = value;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if('port' === key){
|
|
51
|
+
this.config[key] = parseInt(value);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
this.config[key] = value;
|
|
55
|
+
}
|
|
39
56
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
57
|
+
|
|
58
|
+
getConnectionData()
|
|
59
|
+
{
|
|
60
|
+
let connectionData = {
|
|
61
|
+
driver: sc.get(this.config, 'driver', 'objection-js'),
|
|
62
|
+
client: sc.get(this.config, 'client', 'mysql2'),
|
|
63
|
+
user: sc.get(this.config, 'user', ''),
|
|
64
|
+
password: sc.get(this.config, 'password', ''),
|
|
65
|
+
host: sc.get(this.config, 'host', 'localhost'),
|
|
66
|
+
database: sc.get(this.config, 'database', ''),
|
|
67
|
+
port: sc.get(this.config, 'port', 3306)
|
|
68
|
+
};
|
|
69
|
+
if('mikro-orm' === connectionData.driver && !this.config.client){
|
|
70
|
+
connectionData.client = 'mysql';
|
|
71
|
+
}
|
|
72
|
+
return connectionData;
|
|
43
73
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
74
|
+
|
|
75
|
+
validateCommand()
|
|
76
|
+
{
|
|
77
|
+
if('generateEntities' !== this.command){
|
|
78
|
+
Logger.error('Unknown command. Available command "generateEntities".');
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
47
82
|
}
|
|
48
|
-
connectionData[key] = value;
|
|
49
|
-
}
|
|
50
83
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
84
|
+
validateRequiredArgs(connectionData)
|
|
85
|
+
{
|
|
86
|
+
if(!connectionData.user || !connectionData.database){
|
|
87
|
+
Logger.error('Required parameters missing.');
|
|
88
|
+
Logger.error('Usage: 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] --override');
|
|
89
|
+
Logger.error('');
|
|
90
|
+
Logger.error('Optional flags:');
|
|
91
|
+
Logger.error(' --override Regenerate all files even if they exist');
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
54
96
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
97
|
+
async run()
|
|
98
|
+
{
|
|
99
|
+
if(!this.validateCommand()){
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
let connectionData = this.getConnectionData();
|
|
103
|
+
if(!this.validateRequiredArgs(connectionData)){
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
let generator = new EntitiesGenerator({
|
|
107
|
+
connectionData,
|
|
108
|
+
projectPath: this.projectPath,
|
|
109
|
+
isOverride: this.isOverride
|
|
110
|
+
});
|
|
111
|
+
let success = await generator.generate();
|
|
112
|
+
if(!success){
|
|
113
|
+
Logger.error('Entity generation failed.');
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
Logger.info('Entity generation completed successfully!');
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
62
119
|
}
|
|
63
120
|
|
|
64
|
-
let generator = new
|
|
65
|
-
generator.
|
|
121
|
+
let generator = new StorageEntitiesGenerator();
|
|
122
|
+
generator.run().then((success) => {
|
|
66
123
|
if(!success){
|
|
67
|
-
|
|
68
|
-
process.exit();
|
|
124
|
+
process.exit(1);
|
|
69
125
|
}
|
|
70
|
-
|
|
71
|
-
process.exit();
|
|
126
|
+
process.exit(0);
|
|
72
127
|
}).catch((error) => {
|
|
73
128
|
Logger.error('Error during entity generation: '+error.message);
|
|
74
|
-
process.exit();
|
|
129
|
+
process.exit(1);
|
|
75
130
|
});
|
|
@@ -47,6 +47,8 @@ class EntitiesGenerator
|
|
|
47
47
|
this.isOverride = sc.get(props, 'isOverride', false);
|
|
48
48
|
this.generatedEntities = {};
|
|
49
49
|
this.existingEntities = {};
|
|
50
|
+
this.existingEntityFields = {};
|
|
51
|
+
this.existingModels = {};
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
async generateEntityFile(tableName, tableData)
|
|
@@ -57,7 +59,7 @@ class EntitiesGenerator
|
|
|
57
59
|
return false;
|
|
58
60
|
}
|
|
59
61
|
let entityTemplateContent = FileHandler.fetchFileContents(entityTemplatePath);
|
|
60
|
-
if(!entityTemplateContent)
|
|
62
|
+
if(!entityTemplateContent){
|
|
61
63
|
Logger.critical('Failed to read entity template file: '+entityTemplatePath);
|
|
62
64
|
return false;
|
|
63
65
|
}
|
|
@@ -158,7 +160,7 @@ class EntitiesGenerator
|
|
|
158
160
|
let propertiesConfig = [];
|
|
159
161
|
for(let columnName of Object.keys(columns)){
|
|
160
162
|
let column = columns[columnName];
|
|
161
|
-
let propertyKey = titleProperty &&
|
|
163
|
+
let propertyKey = titleProperty && titleProperty === columnName ? '[titleProperty]' : columnName;
|
|
162
164
|
if('id' === columnName){
|
|
163
165
|
propertiesConfig.push(columnName+': {}');
|
|
164
166
|
continue;
|
|
@@ -195,7 +197,7 @@ class EntitiesGenerator
|
|
|
195
197
|
props.push('type: \'datetime\'');
|
|
196
198
|
return;
|
|
197
199
|
}
|
|
198
|
-
if('text' === type || 'longtext' === type || 'mediumtext' === type || 'tinytext' === type){
|
|
200
|
+
if('text' === type || 'longtext' === type || 'mediumtext' === type || 'tinytext' === type || 'json' === type){
|
|
199
201
|
props.push('type: \'textarea\'');
|
|
200
202
|
return;
|
|
201
203
|
}
|
|
@@ -332,7 +334,7 @@ class EntitiesGenerator
|
|
|
332
334
|
let entityPropertiesDefinition = this.getEntityPropertiesDefinition(tableData.columns, driverKey);
|
|
333
335
|
let replacements = {
|
|
334
336
|
modelClassName,
|
|
335
|
-
tableName:
|
|
337
|
+
tableName: 'prisma' === driverKey ? tableName.toLowerCase() : tableName,
|
|
336
338
|
modelPropertiesList,
|
|
337
339
|
modelPropertiesConstructor,
|
|
338
340
|
modelRelations,
|
|
@@ -341,10 +343,7 @@ class EntitiesGenerator
|
|
|
341
343
|
let modelContent = this.applyReplacements(modelTemplateContent, replacements);
|
|
342
344
|
let fileName = sc.kebabCase(tableName)+'-model.js';
|
|
343
345
|
let driverFolder = FileHandler.joinPaths(this.modelsFolder, driverKey);
|
|
344
|
-
|
|
345
|
-
Logger.critical('Failed to create driver folder.');
|
|
346
|
-
return false;
|
|
347
|
-
}
|
|
346
|
+
FileHandler.createFolder(driverFolder);
|
|
348
347
|
let filePath = FileHandler.joinPaths(driverFolder, fileName);
|
|
349
348
|
if(!FileHandler.writeFile(filePath, modelContent)){
|
|
350
349
|
Logger.critical('Failed to write model file: '+fileName);
|
|
@@ -410,32 +409,166 @@ class EntitiesGenerator
|
|
|
410
409
|
}
|
|
411
410
|
let tableName = file.replace('-entity.js', '').replace(/-/g, '_');
|
|
412
411
|
this.existingEntities[tableName] = {entityFileName: file, tableName};
|
|
412
|
+
this.detectExistingEntityFields(tableName, file);
|
|
413
413
|
}
|
|
414
|
+
this.detectExistingModels();
|
|
414
415
|
Logger.info('Detected '+Object.keys(this.existingEntities).length+' existing entities.');
|
|
415
416
|
}
|
|
416
417
|
|
|
417
|
-
|
|
418
|
+
detectExistingModels()
|
|
418
419
|
{
|
|
419
|
-
if(this.
|
|
420
|
-
return
|
|
420
|
+
if(!FileHandler.exists(this.modelsFolder)){
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
let driverFolders = FileHandler.fetchSubFoldersList(this.modelsFolder);
|
|
424
|
+
for(let driverKey of driverFolders){
|
|
425
|
+
let driverFolder = FileHandler.joinPaths(this.modelsFolder, driverKey);
|
|
426
|
+
let modelFiles = FileHandler.getFilesInFolder(driverFolder, ['.js']);
|
|
427
|
+
for(let file of modelFiles){
|
|
428
|
+
if(!file.endsWith('-model.js')){
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
let tableName = file.replace('-model.js', '').replace(/-/g, '_');
|
|
432
|
+
if(!this.existingModels[tableName]){
|
|
433
|
+
this.existingModels[tableName] = {};
|
|
434
|
+
}
|
|
435
|
+
this.existingModels[tableName][driverKey] = {
|
|
436
|
+
modelFileName: file,
|
|
437
|
+
driverKey: driverKey
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
detectExistingEntityFields(tableName, fileName)
|
|
444
|
+
{
|
|
445
|
+
let filePath = FileHandler.joinPaths(this.entitiesFolder, fileName);
|
|
446
|
+
let fileContent = FileHandler.readFile(filePath);
|
|
447
|
+
if(!fileContent){
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
let fieldsMatch = fileContent.match(/properties\s*=\s*\{([^}]+)\}/s);
|
|
451
|
+
if(!fieldsMatch){
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
let propertiesContent = fieldsMatch[1];
|
|
455
|
+
let fieldMatches = propertiesContent.match(/(\w+):\s*\{[^}]*\}/g);
|
|
456
|
+
if(!fieldMatches){
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
this.existingEntityFields[tableName] = [];
|
|
460
|
+
for(let fieldMatch of fieldMatches){
|
|
461
|
+
let fieldName = fieldMatch.split(':')[0].trim();
|
|
462
|
+
this.existingEntityFields[tableName].push(fieldName);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
entityNeedsUpdate(tableName, tableData)
|
|
467
|
+
{
|
|
468
|
+
if(!this.existingEntityFields[tableName]){
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
let existingFields = this.existingEntityFields[tableName];
|
|
472
|
+
let databaseFields = Object.keys(tableData.columns);
|
|
473
|
+
for(let dbField of databaseFields){
|
|
474
|
+
if(!existingFields.includes(dbField)){
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
421
477
|
}
|
|
478
|
+
for(let existingField of existingFields){
|
|
479
|
+
if('id' === existingField){
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if(!databaseFields.includes(existingField)){
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
filterTablesToGenerate(tables, driverKey)
|
|
490
|
+
{
|
|
422
491
|
let filteredTables = {};
|
|
423
492
|
let newTablesCount = 0;
|
|
493
|
+
let updateTablesCount = 0;
|
|
494
|
+
let missingConfigCount = 0;
|
|
495
|
+
let missingModelCount = 0;
|
|
424
496
|
for(let tableName of Object.keys(tables)){
|
|
497
|
+
let needsGeneration = false;
|
|
498
|
+
let reasons = [];
|
|
425
499
|
if(!this.existingEntities[tableName]){
|
|
426
|
-
|
|
500
|
+
needsGeneration = true;
|
|
427
501
|
newTablesCount++;
|
|
502
|
+
reasons.push('new entity');
|
|
503
|
+
}
|
|
504
|
+
if(this.existingEntities[tableName]){
|
|
505
|
+
if(this.entityNeedsUpdate(tableName, tables[tableName])){
|
|
506
|
+
needsGeneration = true;
|
|
507
|
+
updateTablesCount++;
|
|
508
|
+
reasons.push('fields mismatch');
|
|
509
|
+
}
|
|
510
|
+
if(!this.entityExistsInConfig(tableName)){
|
|
511
|
+
needsGeneration = true;
|
|
512
|
+
missingConfigCount++;
|
|
513
|
+
reasons.push('missing from config');
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if(!this.existingModels[tableName] || !this.existingModels[tableName][driverKey]){
|
|
517
|
+
needsGeneration = true;
|
|
518
|
+
missingModelCount++;
|
|
519
|
+
reasons.push('missing model');
|
|
520
|
+
}
|
|
521
|
+
if(this.existingModels[tableName] && this.existingModels[tableName][driverKey]){
|
|
522
|
+
if(!this.modelExistsInRegistered(tableName, driverKey)){
|
|
523
|
+
needsGeneration = true;
|
|
524
|
+
reasons.push('missing from registered models');
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if(needsGeneration || this.isOverride){
|
|
528
|
+
filteredTables[tableName] = tables[tableName];
|
|
529
|
+
if(0 < reasons.length){
|
|
530
|
+
Logger.info('Entity '+tableName+' needs generation: '+reasons.join(', '));
|
|
531
|
+
}
|
|
428
532
|
}
|
|
429
533
|
}
|
|
430
|
-
if(0 === newTablesCount){
|
|
431
|
-
Logger.info('No new tables found
|
|
534
|
+
if(0 === newTablesCount && 0 === updateTablesCount && 0 === missingConfigCount && 0 === missingModelCount){
|
|
535
|
+
Logger.info('No new tables found and all entities are properly configured.');
|
|
536
|
+
return filteredTables;
|
|
432
537
|
}
|
|
433
538
|
if(0 < newTablesCount){
|
|
434
539
|
Logger.info('Found '+newTablesCount+' new tables to generate entities for.');
|
|
435
540
|
}
|
|
541
|
+
if(0 < updateTablesCount){
|
|
542
|
+
Logger.info('Found '+updateTablesCount+' existing entities that need updates.');
|
|
543
|
+
}
|
|
544
|
+
if(0 < missingConfigCount){
|
|
545
|
+
Logger.info('Found '+missingConfigCount+' entities missing from config.');
|
|
546
|
+
}
|
|
547
|
+
if(0 < missingModelCount){
|
|
548
|
+
Logger.info('Found '+missingModelCount+' entities missing models.');
|
|
549
|
+
}
|
|
436
550
|
return filteredTables;
|
|
437
551
|
}
|
|
438
552
|
|
|
553
|
+
entityExistsInConfig(tableName)
|
|
554
|
+
{
|
|
555
|
+
if(!FileHandler.exists(this.entitiesConfigPath)){
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
let configContent = FileHandler.readFile(this.entitiesConfigPath);
|
|
559
|
+
return configContent && configContent.includes(sc.camelCase(tableName)+':');
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
modelExistsInRegistered(tableName, driverKey)
|
|
563
|
+
{
|
|
564
|
+
let registeredPath = FileHandler.joinPaths(this.modelsFolder, driverKey, 'registered-models-'+driverKey+'.js');
|
|
565
|
+
if(!FileHandler.exists(registeredPath)){
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
let registeredContent = FileHandler.readFile(registeredPath);
|
|
569
|
+
return registeredContent && registeredContent.includes(sc.camelCase(tableName)+':');
|
|
570
|
+
}
|
|
571
|
+
|
|
439
572
|
generateEntitiesConfigFile()
|
|
440
573
|
{
|
|
441
574
|
if(this.isOverride){
|
|
@@ -447,7 +580,7 @@ class EntitiesGenerator
|
|
|
447
580
|
regenerateEntitiesConfigFile()
|
|
448
581
|
{
|
|
449
582
|
let configTemplateContent = FileHandler.fetchFileContents(this.templates['entities-config']);
|
|
450
|
-
if(!configTemplateContent)
|
|
583
|
+
if(!configTemplateContent){
|
|
451
584
|
Logger.critical('Failed to read entities config template file: '+this.templates['entities-config']);
|
|
452
585
|
return false;
|
|
453
586
|
}
|
|
@@ -473,8 +606,8 @@ class EntitiesGenerator
|
|
|
473
606
|
return true;
|
|
474
607
|
}
|
|
475
608
|
if(!FileHandler.exists(this.entitiesConfigPath)){
|
|
476
|
-
Logger.
|
|
477
|
-
return
|
|
609
|
+
Logger.info('Entities config file does not exist, creating new file.');
|
|
610
|
+
return this.regenerateEntitiesConfigFile();
|
|
478
611
|
}
|
|
479
612
|
let existingContent = FileHandler.readFile(this.entitiesConfigPath);
|
|
480
613
|
if(!existingContent){
|
|
@@ -492,37 +625,66 @@ class EntitiesGenerator
|
|
|
492
625
|
if(!existingContent.includes(requireStatement)){
|
|
493
626
|
newRequires.push(requireStatement);
|
|
494
627
|
}
|
|
495
|
-
if(!
|
|
628
|
+
if(!this.entityExistsInConfig(tableName)){
|
|
496
629
|
newConfigs.push(configEntry);
|
|
497
630
|
}
|
|
498
631
|
}
|
|
499
632
|
if(0 === newRequires.length && 0 === newConfigs.length){
|
|
500
633
|
return true;
|
|
501
634
|
}
|
|
635
|
+
let normalizedContent = existingContent.replace(/\s+/g, ' ');
|
|
636
|
+
let entitiesConfigPattern = /let\s+entitiesConfig\s*=\s*\{/;
|
|
637
|
+
let entitiesConfigMatch = normalizedContent.match(entitiesConfigPattern);
|
|
638
|
+
if(!entitiesConfigMatch){
|
|
639
|
+
Logger.error('Could not find entitiesConfig variable in config file.');
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
let entitiesConfigStart = existingContent.indexOf(entitiesConfigMatch[0]);
|
|
643
|
+
let searchStart = entitiesConfigStart + entitiesConfigMatch[0].length;
|
|
644
|
+
let braceCount = 1;
|
|
645
|
+
let entitiesConfigEnd = -1;
|
|
646
|
+
for(let i = searchStart; i < existingContent.length; i++){
|
|
647
|
+
if('{' === existingContent[i]){
|
|
648
|
+
braceCount++;
|
|
649
|
+
}
|
|
650
|
+
if('}' === existingContent[i]){
|
|
651
|
+
braceCount--;
|
|
652
|
+
if(0 === braceCount){
|
|
653
|
+
entitiesConfigEnd = i;
|
|
654
|
+
break;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
if(-1 === entitiesConfigEnd){
|
|
659
|
+
Logger.error('Could not find end of entitiesConfig object.');
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
502
662
|
let firstRequirePosition = existingContent.indexOf('const {');
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
Logger.error('Could not find insertion points in config file.');
|
|
663
|
+
if(-1 === firstRequirePosition){
|
|
664
|
+
Logger.error('Could not find require statements in config file.');
|
|
506
665
|
return false;
|
|
507
666
|
}
|
|
508
667
|
let beforeFirstRequire = existingContent.substring(0, firstRequirePosition);
|
|
509
668
|
let afterFirstRequire = existingContent.substring(firstRequirePosition, entitiesConfigEnd);
|
|
669
|
+
let afterConfig = existingContent.substring(entitiesConfigEnd);
|
|
510
670
|
let updatedContent = beforeFirstRequire;
|
|
511
671
|
if(0 < newRequires.length){
|
|
512
672
|
updatedContent += newRequires.join('\n') + '\n';
|
|
513
673
|
}
|
|
514
674
|
updatedContent += afterFirstRequire;
|
|
515
675
|
if(0 < newConfigs.length){
|
|
516
|
-
|
|
676
|
+
let trimmedContent = afterFirstRequire.trimEnd();
|
|
677
|
+
if(!trimmedContent.endsWith(',')){
|
|
517
678
|
updatedContent = updatedContent.trimEnd() + ',';
|
|
518
679
|
}
|
|
519
|
-
updatedContent += '\n ' + newConfigs.join(',\n ')
|
|
680
|
+
updatedContent += '\n ' + newConfigs.join(',\n ');
|
|
520
681
|
}
|
|
521
|
-
updatedContent +=
|
|
682
|
+
updatedContent += afterConfig;
|
|
522
683
|
if(!FileHandler.writeFile(this.entitiesConfigPath, updatedContent)){
|
|
523
684
|
Logger.error('Failed to append to entities config file: '+this.entitiesConfigPath);
|
|
524
685
|
return false;
|
|
525
686
|
}
|
|
687
|
+
Logger.info('Updated entities config file with '+newConfigs.length+' new entities.');
|
|
526
688
|
return true;
|
|
527
689
|
}
|
|
528
690
|
|
|
@@ -591,8 +753,8 @@ class EntitiesGenerator
|
|
|
591
753
|
return true;
|
|
592
754
|
}
|
|
593
755
|
if(!FileHandler.exists(this.entitiesTranslationsPath)){
|
|
594
|
-
Logger.
|
|
595
|
-
return
|
|
756
|
+
Logger.info('Entities translations file does not exist, creating new file.');
|
|
757
|
+
return this.regenerateEntitiesTranslationsFile();
|
|
596
758
|
}
|
|
597
759
|
let existingContent = FileHandler.readFile(this.entitiesTranslationsPath);
|
|
598
760
|
if(!existingContent){
|
|
@@ -611,22 +773,45 @@ class EntitiesGenerator
|
|
|
611
773
|
if(0 === newTranslations.length){
|
|
612
774
|
return true;
|
|
613
775
|
}
|
|
614
|
-
let
|
|
615
|
-
|
|
616
|
-
|
|
776
|
+
let normalizedContent = existingContent.replace(/\s+/g, ' ');
|
|
777
|
+
let labelsPattern = /labels\s*:\s*\{/;
|
|
778
|
+
let labelsMatch = normalizedContent.match(labelsPattern);
|
|
779
|
+
if(!labelsMatch){
|
|
780
|
+
Logger.error('Could not find labels object in translations file.');
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
let labelsStart = existingContent.indexOf(labelsMatch[0]);
|
|
784
|
+
let searchStart = labelsStart + labelsMatch[0].length;
|
|
785
|
+
let braceCount = 1;
|
|
786
|
+
let labelsEnd = -1;
|
|
787
|
+
for(let i = searchStart; i < existingContent.length; i++){
|
|
788
|
+
if('{' === existingContent[i]){
|
|
789
|
+
braceCount++;
|
|
790
|
+
}
|
|
791
|
+
if('}' === existingContent[i]){
|
|
792
|
+
braceCount--;
|
|
793
|
+
if(0 === braceCount){
|
|
794
|
+
labelsEnd = i;
|
|
795
|
+
break;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
if(-1 === labelsEnd){
|
|
800
|
+
Logger.error('Could not find end of labels object.');
|
|
617
801
|
return false;
|
|
618
802
|
}
|
|
619
803
|
let beforeLabelsEnd = existingContent.substring(0, labelsEnd);
|
|
620
|
-
|
|
621
|
-
|
|
804
|
+
let contentBeforeEnd = beforeLabelsEnd.trimEnd();
|
|
805
|
+
let lastChar = contentBeforeEnd[contentBeforeEnd.length - 1];
|
|
806
|
+
if('{' !== lastChar && ',' !== lastChar){
|
|
807
|
+
beforeLabelsEnd = contentBeforeEnd + ',';
|
|
622
808
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
beforeLabelsEnd + '\n ' + newTranslations.join(',\n ') + existingContent.substring(labelsEnd)
|
|
626
|
-
)){
|
|
809
|
+
let updatedContent = beforeLabelsEnd + '\n ' + newTranslations.join(',\n ') + existingContent.substring(labelsEnd);
|
|
810
|
+
if(!FileHandler.writeFile(this.entitiesTranslationsPath, updatedContent)){
|
|
627
811
|
Logger.error('Failed to append to entities translations file: '+this.entitiesTranslationsPath);
|
|
628
812
|
return false;
|
|
629
813
|
}
|
|
814
|
+
Logger.info('Updated translations file with '+newTranslations.length+' new labels.');
|
|
630
815
|
return true;
|
|
631
816
|
}
|
|
632
817
|
|
|
@@ -677,7 +862,9 @@ class EntitiesGenerator
|
|
|
677
862
|
for(let tableName of Object.keys(allEntities)){
|
|
678
863
|
let entity = allEntities[tableName];
|
|
679
864
|
if(entity.driverKey && driverKey !== entity.driverKey){
|
|
680
|
-
|
|
865
|
+
if(!this.existingModels[tableName] || !this.existingModels[tableName][driverKey]){
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
681
868
|
}
|
|
682
869
|
let modelClassName = entity.modelClassName || sc.capitalizedCamelCase(tableName)+'Model';
|
|
683
870
|
let modelFileName = entity.modelFileName || sc.kebabCase(tableName)+'-model.js';
|
|
@@ -694,7 +881,9 @@ class EntitiesGenerator
|
|
|
694
881
|
for(let tableName of Object.keys(allEntities)){
|
|
695
882
|
let entity = allEntities[tableName];
|
|
696
883
|
if(entity.driverKey && driverKey !== entity.driverKey){
|
|
697
|
-
|
|
884
|
+
if(!this.existingModels[tableName] || !this.existingModels[tableName][driverKey]){
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
698
887
|
}
|
|
699
888
|
let modelClassName = entity.modelClassName || sc.capitalizedCamelCase(tableName)+'Model';
|
|
700
889
|
registeredEntitiesObject.push(sc.camelCase(tableName)+': '+modelClassName);
|
|
@@ -731,10 +920,6 @@ class EntitiesGenerator
|
|
|
731
920
|
return false;
|
|
732
921
|
}
|
|
733
922
|
this.detectExistingEntities();
|
|
734
|
-
let tablesToGenerate = this.filterTablesToGenerate(tables);
|
|
735
|
-
if(0 === Object.keys(tablesToGenerate).length && !this.isOverride){
|
|
736
|
-
return true;
|
|
737
|
-
}
|
|
738
923
|
let driverKey = sc.get(
|
|
739
924
|
this.connectionData,
|
|
740
925
|
'driver',
|
|
@@ -744,6 +929,10 @@ class EntitiesGenerator
|
|
|
744
929
|
Logger.critical('Failed to fetch driver key.');
|
|
745
930
|
return false;
|
|
746
931
|
}
|
|
932
|
+
let tablesToGenerate = this.filterTablesToGenerate(tables, driverKey);
|
|
933
|
+
if(0 === Object.keys(tablesToGenerate).length && !this.isOverride){
|
|
934
|
+
return true;
|
|
935
|
+
}
|
|
747
936
|
for(let tableName of Object.keys(tablesToGenerate)){
|
|
748
937
|
let tableData = tablesToGenerate[tableName];
|
|
749
938
|
await this.generateEntityFile(tableName, tableData);
|
|
@@ -32,6 +32,7 @@ class PrismaDriver extends BaseDriver
|
|
|
32
32
|
this.requiredFields = [];
|
|
33
33
|
this.fieldTypes = {};
|
|
34
34
|
this.idFieldType = 'String';
|
|
35
|
+
this.referenceFields = {};
|
|
35
36
|
this.initFieldMetadata();
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -55,6 +56,13 @@ class PrismaDriver extends BaseDriver
|
|
|
55
56
|
if(field.isId){
|
|
56
57
|
this.idFieldType = field.type;
|
|
57
58
|
}
|
|
59
|
+
if(field.kind === 'scalar' && field.isList === false){
|
|
60
|
+
let isNumericType = ('Int' === field.type || 'BigInt' === field.type || 'Float' === field.type || 'Decimal' === field.type);
|
|
61
|
+
let isReferenceField = field.relationFromFields && 0 < field.relationFromFields.length;
|
|
62
|
+
if(isNumericType || isReferenceField){
|
|
63
|
+
this.referenceFields[field.name] = field.type;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
58
66
|
}
|
|
59
67
|
} catch(error) {
|
|
60
68
|
Logger.warning('Could not initialize field metadata: '+error.message);
|
|
@@ -75,6 +83,26 @@ class PrismaDriver extends BaseDriver
|
|
|
75
83
|
return String(id);
|
|
76
84
|
}
|
|
77
85
|
|
|
86
|
+
castNumericValue(value, fieldType)
|
|
87
|
+
{
|
|
88
|
+
if(null === value || undefined === value || '' === value){
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
if('Int' === fieldType || 'BigInt' === fieldType){
|
|
92
|
+
let numericValue = Number(value);
|
|
93
|
+
if(!isNaN(numericValue)){
|
|
94
|
+
return Math.floor(numericValue);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if('Float' === fieldType || 'Decimal' === fieldType){
|
|
98
|
+
let numericValue = Number(value);
|
|
99
|
+
if(!isNaN(numericValue)){
|
|
100
|
+
return numericValue;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
|
|
78
106
|
databaseName()
|
|
79
107
|
{
|
|
80
108
|
return this.config.database || '';
|
|
@@ -109,6 +137,10 @@ class PrismaDriver extends BaseDriver
|
|
|
109
137
|
}
|
|
110
138
|
if(this.isDateTimeField(key)){
|
|
111
139
|
data[key] = this.convertToDate(data[key], key);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if(sc.hasOwn(this.referenceFields, key)){
|
|
143
|
+
data[key] = this.castNumericValue(data[key], this.referenceFields[key]);
|
|
112
144
|
}
|
|
113
145
|
}
|
|
114
146
|
return data;
|
|
@@ -173,6 +205,12 @@ class PrismaDriver extends BaseDriver
|
|
|
173
205
|
if('id' === key && !sc.isArray(operatorValue)){
|
|
174
206
|
operatorValue = this.castIdValue(operatorValue);
|
|
175
207
|
}
|
|
208
|
+
if(sc.hasOwn(this.referenceFields, key) && sc.isArray(operatorValue)){
|
|
209
|
+
operatorValue = operatorValue.map(val => this.castNumericValue(val, this.referenceFields[key]));
|
|
210
|
+
}
|
|
211
|
+
if(sc.hasOwn(this.referenceFields, key) && !sc.isArray(operatorValue)){
|
|
212
|
+
operatorValue = this.castNumericValue(operatorValue, this.referenceFields[key]);
|
|
213
|
+
}
|
|
176
214
|
processedFilters[key] = this.applyOperator(operatorValue, value.operator);
|
|
177
215
|
continue;
|
|
178
216
|
}
|
|
@@ -188,6 +226,14 @@ class PrismaDriver extends BaseDriver
|
|
|
188
226
|
processedFilters[key] = this.castIdValue(value);
|
|
189
227
|
continue;
|
|
190
228
|
}
|
|
229
|
+
if(sc.hasOwn(this.referenceFields, key) && sc.isArray(value)){
|
|
230
|
+
processedFilters[key] = value.map(val => this.castNumericValue(val, this.referenceFields[key]));
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if(sc.hasOwn(this.referenceFields, key)){
|
|
234
|
+
processedFilters[key] = this.castNumericValue(value, this.referenceFields[key]);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
191
237
|
processedFilters[key] = value;
|
|
192
238
|
}
|
|
193
239
|
return processedFilters;
|
|
@@ -590,11 +636,18 @@ class PrismaDriver extends BaseDriver
|
|
|
590
636
|
createSingleFilter(field, fieldValue, operator = null)
|
|
591
637
|
{
|
|
592
638
|
let filter = {};
|
|
639
|
+
let processedValue = fieldValue;
|
|
640
|
+
if(sc.hasOwn(this.referenceFields, field)){
|
|
641
|
+
processedValue = this.castNumericValue(fieldValue, this.referenceFields[field]);
|
|
642
|
+
}
|
|
643
|
+
if('id' === field){
|
|
644
|
+
processedValue = this.castIdValue(fieldValue);
|
|
645
|
+
}
|
|
593
646
|
if(null === operator){
|
|
594
|
-
filter[field] =
|
|
647
|
+
filter[field] = processedValue;
|
|
595
648
|
return filter;
|
|
596
649
|
}
|
|
597
|
-
filter[field] = this.applyOperator(
|
|
650
|
+
filter[field] = this.applyOperator(processedValue, operator);
|
|
598
651
|
return filter;
|
|
599
652
|
}
|
|
600
653
|
|
|
@@ -21,34 +21,50 @@ class PrismaSchemaGenerator
|
|
|
21
21
|
this.maxWaitTime = sc.get(props, 'maxWaitTime', 30000);
|
|
22
22
|
this.prismaSchemaPath = sc.get(props, 'prismaSchemaPath', FileHandler.joinPaths(process.cwd(), 'prisma'));
|
|
23
23
|
this.schemaFilePath = FileHandler.joinPaths(this.prismaSchemaPath, 'schema.prisma');
|
|
24
|
-
this.clientOutputPath = sc.get(props, 'clientOutputPath', '
|
|
24
|
+
this.clientOutputPath = sc.get(props, 'clientOutputPath', '');
|
|
25
25
|
this.generateBinaryTargets = sc.get(props, 'generateBinaryTargets', ['native']);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
async generate()
|
|
29
29
|
{
|
|
30
|
+
FileHandler.createFolder(this.prismaSchemaPath);
|
|
31
|
+
this.generateSchemaFile();
|
|
32
|
+
Logger.info('Running prisma introspect "npx prisma db pull"...');
|
|
30
33
|
try {
|
|
31
|
-
FileHandler.createFolder(this.prismaSchemaPath);
|
|
32
|
-
this.generateSchemaFile();
|
|
33
|
-
Logger.info('Running prisma introspect "npx prisma db pull"...');
|
|
34
34
|
execSync('npx prisma db pull', { stdio: 'inherit' });
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
} catch(error) {
|
|
36
|
+
Logger.critical('Failed to pull database schema: ' + error.message);
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
let generateCommand = 'npx prisma generate';
|
|
40
|
+
if(this.dataProxy){
|
|
41
|
+
generateCommand += ' --data-proxy';
|
|
42
|
+
}
|
|
43
|
+
Logger.info('Running prisma generate "'+generateCommand+'"...');
|
|
44
|
+
try {
|
|
40
45
|
execSync(generateCommand, { stdio: 'inherit' });
|
|
41
|
-
await this.waitForSchemaGeneration();
|
|
42
|
-
return true;
|
|
43
46
|
} catch(error) {
|
|
44
|
-
|
|
47
|
+
let errorString = error.toString();
|
|
48
|
+
if(errorString.includes('EPERM') && errorString.includes('query_engine-windows.dll.node')){
|
|
49
|
+
Logger.warning('Windows permission error detected with query engine file.');
|
|
50
|
+
Logger.warning('This is a known Prisma issue on Windows.');
|
|
51
|
+
Logger.warning('Please manually run: ' + generateCommand);
|
|
52
|
+
Logger.warning('You may need to close any processes using the Prisma client first.');
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
Logger.critical('Generate command failed: ' + error.message);
|
|
56
|
+
return false;
|
|
45
57
|
}
|
|
46
|
-
|
|
58
|
+
await this.waitForSchemaGeneration();
|
|
59
|
+
return true;
|
|
47
60
|
}
|
|
48
61
|
|
|
49
62
|
async waitForSchemaGeneration()
|
|
50
63
|
{
|
|
51
|
-
let
|
|
64
|
+
let clientPath = this.clientOutputPath || 'node_modules/.prisma/client';
|
|
65
|
+
let generatedClientPath = clientPath.startsWith('node_modules')
|
|
66
|
+
? FileHandler.joinPaths(process.cwd(), clientPath)
|
|
67
|
+
: FileHandler.joinPaths(this.prismaSchemaPath, clientPath);
|
|
52
68
|
let clientIndexPath = FileHandler.joinPaths(generatedClientPath, 'index.js');
|
|
53
69
|
let startTime = Date.now();
|
|
54
70
|
return new Promise((resolve) => {
|
|
@@ -123,7 +139,10 @@ class PrismaSchemaGenerator
|
|
|
123
139
|
{
|
|
124
140
|
let generatorContent = 'generator client {\n';
|
|
125
141
|
generatorContent += ' provider = "prisma-client-js"\n';
|
|
126
|
-
|
|
142
|
+
if(this.clientOutputPath){
|
|
143
|
+
let normalizedPath = this.clientOutputPath.replace(/\\/g, '/');
|
|
144
|
+
generatorContent += ' output = "' + normalizedPath + '"\n';
|
|
145
|
+
}
|
|
127
146
|
if(0 < this.generateBinaryTargets.length){
|
|
128
147
|
generatorContent += ' binaryTargets = [' +
|
|
129
148
|
this.generateBinaryTargets.map(target => '"' + target + '"').join(', ')
|