@reldens/storage 0.80.0 → 0.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +125 -3
- package/bin/reldens-storage.js +63 -8
- package/lib/entities-generator.js +5 -4
- package/lib/generators/models-generation.js +161 -8
- package/lib/prisma/prisma-data-server.js +38 -4
- package/lib/prisma/prisma-driver.js +277 -735
- package/lib/prisma/prisma-filter-processor.js +146 -0
- package/lib/prisma/prisma-metadata-loader.js +133 -0
- package/lib/prisma/prisma-query-builder.js +42 -0
- package/lib/prisma/prisma-relation-resolver.js +267 -0
- package/lib/prisma/prisma-schema-generator.js +40 -33
- package/lib/prisma/prisma-type-caster.js +260 -0
- package/package.json +1 -1
|
@@ -21,7 +21,7 @@ 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', './client');
|
|
25
25
|
this.generateBinaryTargets = sc.get(props, 'generateBinaryTargets', ['native', 'debian-openssl-1.1.x']);
|
|
26
26
|
this.dbParams = sc.get(props, 'dbParams', process.env.RELDENS_DB_PARAMS || '');
|
|
27
27
|
}
|
|
@@ -30,11 +30,12 @@ class PrismaSchemaGenerator
|
|
|
30
30
|
{
|
|
31
31
|
FileHandler.createFolder(this.prismaSchemaPath);
|
|
32
32
|
this.generateSchemaFile();
|
|
33
|
+
this.setDatabaseEnvironmentVariables();
|
|
33
34
|
Logger.info('Running prisma introspect "npx prisma db pull"...');
|
|
34
35
|
try {
|
|
35
|
-
execSync('npx prisma db pull', {
|
|
36
|
+
execSync('npx prisma db pull', {stdio: 'inherit'});
|
|
36
37
|
} catch(error) {
|
|
37
|
-
Logger.critical('Failed to pull database schema: '
|
|
38
|
+
Logger.critical('Failed to pull database schema: '+error.message);
|
|
38
39
|
return false;
|
|
39
40
|
}
|
|
40
41
|
let generateCommand = 'npx prisma generate';
|
|
@@ -43,33 +44,42 @@ class PrismaSchemaGenerator
|
|
|
43
44
|
}
|
|
44
45
|
Logger.info('Running prisma generate "'+generateCommand+'"...');
|
|
45
46
|
try {
|
|
46
|
-
execSync(generateCommand, {
|
|
47
|
+
execSync(generateCommand, {stdio: 'inherit'});
|
|
47
48
|
} catch(error) {
|
|
48
49
|
let errorString = error.toString();
|
|
49
50
|
if(errorString.includes('EPERM') && errorString.includes('query_engine-windows.dll.node')){
|
|
50
51
|
Logger.warning('Windows permission error detected with query engine file.');
|
|
51
52
|
Logger.warning('This is a known Prisma issue on Windows.');
|
|
52
|
-
Logger.warning('Please manually run: '
|
|
53
|
+
Logger.warning('Please manually run: '+generateCommand);
|
|
53
54
|
Logger.warning('You may need to close any processes using the Prisma client first.');
|
|
54
55
|
return false;
|
|
55
56
|
}
|
|
56
|
-
Logger.critical('Generate command failed: '
|
|
57
|
+
Logger.critical('Generate command failed: '+error.message);
|
|
57
58
|
return false;
|
|
58
59
|
}
|
|
59
60
|
await this.waitForSchemaGeneration();
|
|
60
61
|
return true;
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
setDatabaseEnvironmentVariables()
|
|
65
|
+
{
|
|
66
|
+
let datasourceProvider = this.getDatasourceProvider();
|
|
67
|
+
process.env.RELDENS_DB_URL = this.buildConnectionString(datasourceProvider);
|
|
68
|
+
if(this.dataProxy){
|
|
69
|
+
process.env.RELDENS_DB_DIRECT_URL = this.buildDirectConnectionString(datasourceProvider);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
63
73
|
async waitForSchemaGeneration()
|
|
64
74
|
{
|
|
65
|
-
let clientPath = this.clientOutputPath || '
|
|
75
|
+
let clientPath = this.clientOutputPath || './client';
|
|
66
76
|
let generatedClientPath = this.resolveClientPath(clientPath);
|
|
67
77
|
let clientIndexPath = FileHandler.joinPaths(generatedClientPath, 'index.js');
|
|
68
78
|
let startTime = Date.now();
|
|
69
79
|
return new Promise((resolve) => {
|
|
70
80
|
let interval = setInterval(() => {
|
|
71
81
|
let awaitTime = Date.now() - startTime;
|
|
72
|
-
Logger.info('Awaiting on schema generation: '
|
|
82
|
+
Logger.info('Awaiting on schema generation: '+clientIndexPath, (awaitTime/1000).toFixed(0));
|
|
73
83
|
if(FileHandler.exists(clientIndexPath)){
|
|
74
84
|
clearInterval(interval);
|
|
75
85
|
resolve();
|
|
@@ -98,11 +108,7 @@ class PrismaSchemaGenerator
|
|
|
98
108
|
generateSchemaFile()
|
|
99
109
|
{
|
|
100
110
|
let datasourceProvider = this.getDatasourceProvider();
|
|
101
|
-
let schemaContent = this.buildSchemaContent(
|
|
102
|
-
datasourceProvider,
|
|
103
|
-
this.buildConnectionString(datasourceProvider),
|
|
104
|
-
this.buildDirectConnectionString(datasourceProvider)
|
|
105
|
-
);
|
|
111
|
+
let schemaContent = this.buildSchemaContent(datasourceProvider);
|
|
106
112
|
FileHandler.writeFile(this.schemaFilePath, schemaContent);
|
|
107
113
|
Logger.info('Generated Prisma schema file at: '+this.schemaFilePath);
|
|
108
114
|
}
|
|
@@ -123,7 +129,7 @@ class PrismaSchemaGenerator
|
|
|
123
129
|
if(this.dataProxy){
|
|
124
130
|
datasourceProvider = 'prisma';
|
|
125
131
|
}
|
|
126
|
-
return datasourceProvider
|
|
132
|
+
return datasourceProvider+this.buildConnectionDataString();
|
|
127
133
|
}
|
|
128
134
|
|
|
129
135
|
buildDirectConnectionString(datasourceProvider)
|
|
@@ -131,47 +137,48 @@ class PrismaSchemaGenerator
|
|
|
131
137
|
if(!this.dataProxy){
|
|
132
138
|
return '';
|
|
133
139
|
}
|
|
134
|
-
return datasourceProvider
|
|
140
|
+
return datasourceProvider+this.buildConnectionDataString();
|
|
135
141
|
}
|
|
136
142
|
|
|
137
143
|
buildConnectionDataString()
|
|
138
144
|
{
|
|
139
|
-
return '://'
|
|
140
|
-
+
|
|
141
|
-
+
|
|
142
|
-
+
|
|
145
|
+
return '://'+this.config.user+':'+this.config.password
|
|
146
|
+
+'@'+this.config.host+':'+this.config.port
|
|
147
|
+
+'/'+this.config.database
|
|
148
|
+
+(this.dbParams ? '?'+this.dbParams : '');
|
|
143
149
|
}
|
|
144
150
|
|
|
145
|
-
buildSchemaContent(datasourceProvider
|
|
151
|
+
buildSchemaContent(datasourceProvider)
|
|
146
152
|
{
|
|
147
|
-
return this.buildGeneratorBlock()
|
|
148
|
-
+
|
|
153
|
+
return this.buildGeneratorBlock()+'\n\n'
|
|
154
|
+
+this.buildDatasourceBlock(datasourceProvider)+'\n';
|
|
149
155
|
}
|
|
150
156
|
|
|
151
157
|
buildGeneratorBlock()
|
|
152
158
|
{
|
|
153
159
|
let generatorContent = 'generator client {\n';
|
|
154
160
|
generatorContent += ' provider = "prisma-client-js"\n';
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
161
|
+
let outputPath = this.clientOutputPath || './client';
|
|
162
|
+
if(!outputPath.startsWith('./')){
|
|
163
|
+
outputPath = './client';
|
|
158
164
|
}
|
|
165
|
+
generatorContent += ' output = "'+outputPath+'"\n';
|
|
159
166
|
if(0 < this.generateBinaryTargets.length){
|
|
160
|
-
generatorContent += ' binaryTargets = ['
|
|
161
|
-
this.generateBinaryTargets.map(target => '"'
|
|
162
|
-
+
|
|
167
|
+
generatorContent += ' binaryTargets = ['
|
|
168
|
+
+this.generateBinaryTargets.map(target => '"'+target+'"').join(', ')
|
|
169
|
+
+']\n';
|
|
163
170
|
}
|
|
164
171
|
generatorContent += '}';
|
|
165
172
|
return generatorContent;
|
|
166
173
|
}
|
|
167
174
|
|
|
168
|
-
buildDatasourceBlock(datasourceProvider
|
|
175
|
+
buildDatasourceBlock(datasourceProvider)
|
|
169
176
|
{
|
|
170
177
|
let datasourceContent = 'datasource db {\n';
|
|
171
|
-
datasourceContent += ' provider = "'
|
|
172
|
-
datasourceContent += ' url = "
|
|
173
|
-
if(
|
|
174
|
-
datasourceContent += ' directUrl = "
|
|
178
|
+
datasourceContent += ' provider = "'+datasourceProvider+'"\n';
|
|
179
|
+
datasourceContent += ' url = env("RELDENS_DB_URL")\n';
|
|
180
|
+
if(this.dataProxy){
|
|
181
|
+
datasourceContent += ' directUrl = env("RELDENS_DB_DIRECT_URL")\n';
|
|
175
182
|
}
|
|
176
183
|
datasourceContent += '}';
|
|
177
184
|
return datasourceContent;
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - PrismaTypeCaster
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { Logger, sc } = require('@reldens/utils');
|
|
8
|
+
const { Prisma } = require('@prisma/client');
|
|
9
|
+
|
|
10
|
+
class PrismaTypeCaster
|
|
11
|
+
{
|
|
12
|
+
|
|
13
|
+
constructor(props)
|
|
14
|
+
{
|
|
15
|
+
this.idFieldType = sc.get(props, 'idFieldType', 'String');
|
|
16
|
+
this.jsonFields = sc.get(props, 'jsonFields', new Set());
|
|
17
|
+
this.fieldTypes = sc.get(props, 'fieldTypes', {});
|
|
18
|
+
this.fieldDefaults = sc.get(props, 'fieldDefaults', {});
|
|
19
|
+
this.requiredFields = sc.get(props, 'requiredFields', []);
|
|
20
|
+
this.optionalFields = sc.get(props, 'optionalFields', []);
|
|
21
|
+
this.rawModel = sc.get(props, 'rawModel', false);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
updateMetadata(metadata)
|
|
25
|
+
{
|
|
26
|
+
this.idFieldType = sc.get(metadata, 'idFieldType', this.idFieldType);
|
|
27
|
+
this.jsonFields = sc.get(metadata, 'jsonFields', this.jsonFields);
|
|
28
|
+
this.fieldTypes = sc.get(metadata, 'fieldTypes', this.fieldTypes);
|
|
29
|
+
this.fieldDefaults = sc.get(metadata, 'fieldDefaults', this.fieldDefaults);
|
|
30
|
+
this.requiredFields = sc.get(metadata, 'requiredFields', this.requiredFields);
|
|
31
|
+
this.optionalFields = sc.get(metadata, 'optionalFields', this.optionalFields);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
isNumericFieldType(fieldType)
|
|
35
|
+
{
|
|
36
|
+
return ('Int' === fieldType || 'BigInt' === fieldType || 'Float' === fieldType || 'Decimal' === fieldType);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
isBooleanFieldType(fieldType)
|
|
40
|
+
{
|
|
41
|
+
return 'Boolean' === fieldType;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
isDateTimeFieldType(fieldType)
|
|
45
|
+
{
|
|
46
|
+
return 'DateTime' === fieldType || 'Date' === fieldType;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
isJsonFieldType(fieldType)
|
|
50
|
+
{
|
|
51
|
+
return 'Json' === fieldType;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
isJsonFieldByName(fieldName)
|
|
55
|
+
{
|
|
56
|
+
if(this.jsonFields.has(fieldName)){
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
if(!this.rawModel || !this.rawModel.properties){
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
return 'json' === sc.get(this.rawModel.properties[fieldName], 'dbType', '');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
isStringFieldType(fieldType)
|
|
66
|
+
{
|
|
67
|
+
return 'String' === fieldType;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
isNullOrEmpty(value)
|
|
71
|
+
{
|
|
72
|
+
return null === value || undefined === value || '' === value;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
isValidCastedValue(value)
|
|
76
|
+
{
|
|
77
|
+
return undefined !== value;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
castValue(value, fieldType, fieldName = null)
|
|
81
|
+
{
|
|
82
|
+
if('id' === fieldName){
|
|
83
|
+
return this.castToIdType(value);
|
|
84
|
+
}
|
|
85
|
+
if(this.isNullOrEmpty(value)){
|
|
86
|
+
if(sc.hasOwn(this.fieldDefaults, fieldName)){
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
if(-1 !== this.optionalFields.indexOf(fieldName)){
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
if(-1 !== this.requiredFields.indexOf(fieldName)){
|
|
93
|
+
Logger.warning('Required field '+fieldName+' cannot be null or empty');
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
if(this.isJsonFieldByName(fieldName)){
|
|
99
|
+
return this.castToJsonType(value);
|
|
100
|
+
}
|
|
101
|
+
if(this.isNumericFieldType(fieldType)){
|
|
102
|
+
return this.castToNumericType(value, fieldType);
|
|
103
|
+
}
|
|
104
|
+
if(this.isBooleanFieldType(fieldType)){
|
|
105
|
+
return this.castToBooleanType(value);
|
|
106
|
+
}
|
|
107
|
+
if(this.isDateTimeFieldType(fieldType)){
|
|
108
|
+
return this.castToDateType(value);
|
|
109
|
+
}
|
|
110
|
+
if(this.isStringFieldType(fieldType)){
|
|
111
|
+
return this.castToStringType(value);
|
|
112
|
+
}
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
castToIdType(value)
|
|
117
|
+
{
|
|
118
|
+
if(this.isNullOrEmpty(value)){
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
if(this.isNumericFieldType(this.idFieldType)){
|
|
122
|
+
let numeric = Number(value);
|
|
123
|
+
if(!isNaN(numeric)){
|
|
124
|
+
return ('Int' === this.idFieldType || 'BigInt' === this.idFieldType) ? Math.floor(numeric) : numeric;
|
|
125
|
+
}
|
|
126
|
+
Logger.warning('Cannot cast non-numeric value "'+value+'" to ID type '+this.idFieldType);
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
return String(value);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
castToNumericType(value, fieldType)
|
|
133
|
+
{
|
|
134
|
+
if(this.isNullOrEmpty(value)){
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
let numericValue = Number(value);
|
|
138
|
+
if(isNaN(numericValue)){
|
|
139
|
+
Logger.warning('Cannot convert value to numeric: '+value);
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
if('Int' === fieldType || 'BigInt' === fieldType){
|
|
143
|
+
return Math.floor(numericValue);
|
|
144
|
+
}
|
|
145
|
+
return numericValue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
castToBooleanType(value)
|
|
149
|
+
{
|
|
150
|
+
if(this.isNullOrEmpty(value)){
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
if('boolean' === typeof value){
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
if('number' === typeof value){
|
|
157
|
+
return 0 !== value;
|
|
158
|
+
}
|
|
159
|
+
if('string' === typeof value){
|
|
160
|
+
let lowerValue = value.toLowerCase();
|
|
161
|
+
if('true' === lowerValue || '1' === lowerValue || 'yes' === lowerValue){
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
if('false' === lowerValue || '0' === lowerValue || 'no' === lowerValue){
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return Boolean(value);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
castToDateType(dateValue)
|
|
172
|
+
{
|
|
173
|
+
if(dateValue instanceof Date){
|
|
174
|
+
return dateValue;
|
|
175
|
+
}
|
|
176
|
+
if(this.isNullOrEmpty(dateValue)){
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
let dateString = String(dateValue);
|
|
180
|
+
if(dateString.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/)){
|
|
181
|
+
return new Date(dateString.replace(' ', 'T')+'Z');
|
|
182
|
+
}
|
|
183
|
+
if(dateString.match(/^\d{4}-\d{2}-\d{2}$/)){
|
|
184
|
+
return new Date(dateString+'T00:00:00Z');
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
let parsedDate = new Date(dateString);
|
|
188
|
+
if(isNaN(parsedDate.getTime())){
|
|
189
|
+
Logger.warning('Invalid date value: '+dateString);
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
return parsedDate;
|
|
193
|
+
} catch(error) {
|
|
194
|
+
Logger.warning('Failed to convert date value: '+dateString);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
castToJsonType(value)
|
|
200
|
+
{
|
|
201
|
+
if(this.isNullOrEmpty(value)){
|
|
202
|
+
return Prisma.DbNull;
|
|
203
|
+
}
|
|
204
|
+
if('object' === typeof value){
|
|
205
|
+
return value;
|
|
206
|
+
}
|
|
207
|
+
if('string' === typeof value){
|
|
208
|
+
if(value.startsWith('%') && value.endsWith('%')){
|
|
209
|
+
return value;
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
return JSON.parse(value);
|
|
213
|
+
} catch(error) {
|
|
214
|
+
return value;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return value;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
castToStringType(value)
|
|
221
|
+
{
|
|
222
|
+
if(null === value || undefined === value){
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
if('string' === typeof value){
|
|
226
|
+
if('null' === value){
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
return value;
|
|
230
|
+
}
|
|
231
|
+
return String(value);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
normalizeReturnData(data)
|
|
235
|
+
{
|
|
236
|
+
if(null === data || undefined === data){
|
|
237
|
+
return data;
|
|
238
|
+
}
|
|
239
|
+
if('bigint' === typeof data){
|
|
240
|
+
return Number(data);
|
|
241
|
+
}
|
|
242
|
+
if(data instanceof Date){
|
|
243
|
+
return data;
|
|
244
|
+
}
|
|
245
|
+
if(sc.isArray(data)){
|
|
246
|
+
return data.map(item => this.normalizeReturnData(item));
|
|
247
|
+
}
|
|
248
|
+
if(sc.isObject(data)){
|
|
249
|
+
let result = {};
|
|
250
|
+
for(let key of Object.keys(data)){
|
|
251
|
+
result[key] = this.normalizeReturnData(data[key]);
|
|
252
|
+
}
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
return data;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
module.exports.PrismaTypeCaster = PrismaTypeCaster;
|