@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.
@@ -5,8 +5,12 @@
5
5
  */
6
6
 
7
7
  const { BaseDriver } = require('../base-driver');
8
+ const { PrismaTypeCaster } = require('./prisma-type-caster');
9
+ const { PrismaMetadataLoader } = require('./prisma-metadata-loader');
10
+ const { PrismaRelationResolver } = require('./prisma-relation-resolver');
11
+ const { PrismaFilterProcessor } = require('./prisma-filter-processor');
12
+ const { PrismaQueryBuilder } = require('./prisma-query-builder');
8
13
  const { Logger, sc } = require('@reldens/utils');
9
- const { Prisma } = require('@prisma/client');
10
14
 
11
15
  class PrismaDriver extends BaseDriver
12
16
  {
@@ -24,325 +28,80 @@ class PrismaDriver extends BaseDriver
24
28
  }
25
29
  this.prisma = props.prisma;
26
30
  this.model = props.model;
27
- this.requiredFields = [];
28
- this.optionalFields = [];
29
- this.fieldTypes = {};
30
- this.fieldDefaults = {};
31
- this.idFieldType = 'String';
32
- this.referenceFields = {};
33
- this.relationMetadata = {};
34
- this.foreignKeyMappings = {};
35
- this.jsonFields = new Set();
31
+ this.allRelationMappings = sc.get(props, 'allRelationMappings', {});
32
+ this.initHelpers();
36
33
  this.initModelMetadata();
37
34
  }
38
35
 
39
- initModelMetadata()
40
- {
41
- let dmmf = this.getDmmf();
42
- if(!dmmf){
43
- Logger.warning('Could not access Prisma DMMF metadata');
44
- return;
45
- }
46
- let modelName = this.tableName().toLowerCase();
47
- let modelInfo = dmmf.models?.[modelName] || dmmf.models?.find(m => m.name.toLowerCase() === modelName);
48
- if(!modelInfo){
49
- Logger.warning('Could not find model info for: '+modelName);
50
- return;
51
- }
52
- for(let field of modelInfo.fields){
53
- this.processFieldMetadata(field);
54
- }
55
- if(this.rawModel && this.rawModel.relationTypes){
56
- for(let relationName of Object.keys(this.rawModel.relationTypes)){
57
- if(sc.hasOwn(this.relationMetadata, relationName)){
58
- this.relationMetadata[relationName].type = this.rawModel.relationTypes[relationName];
59
- }
60
- }
61
- }
36
+ initHelpers()
37
+ {
38
+ this.metadataLoader = new PrismaMetadataLoader({
39
+ prisma: this.prisma,
40
+ rawModel: this.rawModel
41
+ });
42
+ this.typeCaster = new PrismaTypeCaster({
43
+ idFieldType: 'String',
44
+ jsonFields: new Set(),
45
+ fieldTypes: {},
46
+ fieldDefaults: {},
47
+ requiredFields: [],
48
+ optionalFields: [],
49
+ rawModel: this.rawModel
50
+ });
51
+ this.relationResolver = new PrismaRelationResolver({
52
+ relationMetadata: {},
53
+ metadataLoader: this.metadataLoader,
54
+ allRelationMappings: this.allRelationMappings,
55
+ foreignKeyMappings: {},
56
+ foreignKeyTargetFields: {},
57
+ typeCaster: this.typeCaster
58
+ });
59
+ this.filterProcessor = new PrismaFilterProcessor({
60
+ typeCaster: this.typeCaster,
61
+ fieldTypes: {},
62
+ referenceFields: {},
63
+ jsonFields: new Set()
64
+ });
65
+ this.queryBuilder = new PrismaQueryBuilder();
62
66
  }
63
67
 
64
- getDmmf()
65
- {
66
- try {
67
- if(this.prisma._dmmf){
68
- return this.prisma._dmmf.datamodel;
69
- }
70
- if(this.prisma._getDmmf && sc.isFunction(this.prisma._getDmmf)){
71
- return this.prisma._getDmmf().datamodel;
72
- }
73
- if(this.prisma._runtimeDataModel){
74
- return this.prisma._runtimeDataModel;
75
- }
76
- if(Prisma.dmmf && Prisma.dmmf.datamodel){
77
- return Prisma.dmmf.datamodel;
78
- }
79
- return null;
80
- } catch(error) {
81
- Logger.warning('Failed to access DMMF: '+error.message);
82
- return null;
83
- }
84
- }
85
-
86
- processFieldMetadata(field)
87
- {
88
- this.fieldTypes[field.name] = field.type;
89
- if('Json' === field.type){
90
- this.jsonFields.add(field.name);
91
- }
92
- if(field.isId){
93
- this.idFieldType = field.type;
94
- }
95
- if(field.hasDefaultValue){
96
- this.fieldDefaults[field.name] = field.default;
97
- }
98
- if(field.isRequired && !field.hasDefaultValue && !field.isId && 'object' !== field.kind){
99
- this.requiredFields.push(field.name);
100
- }
101
- if(field.isOptional || field.hasDefaultValue){
102
- this.optionalFields.push(field.name);
103
- }
104
- if(field.kind === 'scalar' && field.isList === false){
105
- let isNumericType = this.isNumericFieldType(field.type);
106
- let isReferenceField = field.relationFromFields && 0 < field.relationFromFields.length;
107
- if(isNumericType || isReferenceField){
108
- this.referenceFields[field.name] = field.type;
109
- }
110
- }
111
- if('object' === field.kind){
112
- let relationType = field.isList ? 'many' : 'one';
113
- this.relationMetadata[field.name] = {
114
- type: relationType,
115
- model: field.type,
116
- isList: field.isList,
117
- isOptional: field.isOptional
118
- };
119
- }
120
- if('object' === field.kind && sc.hasOwn(field, 'relationFromFields') && sc.isArray(field.relationFromFields)){
121
- for(let foreignKeyField of field.relationFromFields){
122
- this.foreignKeyMappings[foreignKeyField] = field.name;
123
- }
124
- }
125
- }
126
-
127
- getAllRelations()
128
- {
129
- return Object.keys(this.relationMetadata || {});
130
- }
131
-
132
- normalizeRelationName(relationName)
133
- {
134
- if(sc.hasOwn(this.relationMetadata, relationName)){
135
- return relationName;
136
- }
137
- if(relationName.startsWith('related_')){
138
- let withoutPrefix = relationName.substring(8);
139
- if(sc.hasOwn(this.relationMetadata, withoutPrefix)){
140
- return withoutPrefix;
141
- }
142
- }
143
- let availableRelations = Object.keys(this.relationMetadata);
144
- for(let availableRelation of availableRelations){
145
- if(relationName.endsWith(availableRelation)){
146
- return availableRelation;
147
- }
148
- if(availableRelation.endsWith(relationName.replace('related_', ''))){
149
- return availableRelation;
150
- }
151
- }
152
- return relationName;
153
- }
154
-
155
- isNumericFieldType(fieldType)
156
- {
157
- return ('Int' === fieldType || 'BigInt' === fieldType || 'Float' === fieldType || 'Decimal' === fieldType);
158
- }
159
-
160
- isBooleanFieldType(fieldType)
161
- {
162
- return 'Boolean' === fieldType;
163
- }
164
-
165
- isDateTimeFieldType(fieldType)
166
- {
167
- return 'DateTime' === fieldType || 'Date' === fieldType;
168
- }
169
-
170
- isJsonFieldType(fieldType)
171
- {
172
- return 'Json' === fieldType;
173
- }
174
-
175
- isJsonFieldByName(fieldName)
176
- {
177
- if(this.jsonFields.has(fieldName)){
178
- return true;
179
- }
180
- if(!this.rawModel || !this.rawModel.properties){
181
- return false;
182
- }
183
- return 'json' === sc.get(this.rawModel.properties[fieldName], 'dbType', '');
184
- }
185
-
186
- isStringFieldType(fieldType)
187
- {
188
- return 'String' === fieldType;
189
- }
190
-
191
- castValue(value, fieldType, fieldName = null)
192
- {
193
- if('id' === fieldName){
194
- return this.castToIdType(value);
195
- }
196
- if(this.isNullOrEmpty(value)){
197
- if(sc.hasOwn(this.fieldDefaults, fieldName)){
198
- return undefined;
199
- }
200
- if(-1 !== this.optionalFields.indexOf(fieldName)){
201
- return null;
202
- }
203
- if(-1 !== this.requiredFields.indexOf(fieldName)){
204
- Logger.warning('Required field '+fieldName+' cannot be null or empty');
205
- return undefined;
206
- }
207
- return null;
208
- }
209
- if(this.isJsonFieldByName(fieldName)){
210
- return this.castToJsonType(value);
211
- }
212
- if(this.isNumericFieldType(fieldType)){
213
- return this.castToNumericType(value, fieldType);
214
- }
215
- if(this.isBooleanFieldType(fieldType)){
216
- return this.castToBooleanType(value);
217
- }
218
- if(this.isDateTimeFieldType(fieldType)){
219
- return this.castToDateType(value);
220
- }
221
- if(this.isStringFieldType(fieldType)){
222
- return this.castToStringType(value);
223
- }
224
- return value;
225
- }
226
-
227
- isNullOrEmpty(value)
228
- {
229
- return null === value || undefined === value || '' === value;
230
- }
231
-
232
- castToIdType(value)
233
- {
234
- if(this.isNullOrEmpty(value)){
235
- return value;
236
- }
237
- if(this.isNumericFieldType(this.idFieldType)){
238
- let numeric = Number(value);
239
- if(!isNaN(numeric)){
240
- return ('Int' === this.idFieldType || 'BigInt' === this.idFieldType) ? Math.floor(numeric) : numeric;
241
- }
242
- }
243
- return String(value);
244
- }
245
-
246
- castToNumericType(value, fieldType)
247
- {
248
- if(this.isNullOrEmpty(value)){
249
- return null;
250
- }
251
- let numericValue = Number(value);
252
- if(isNaN(numericValue)){
253
- Logger.warning('Cannot convert value to numeric: '+value);
254
- return null;
255
- }
256
- if('Int' === fieldType || 'BigInt' === fieldType){
257
- return Math.floor(numericValue);
258
- }
259
- return numericValue;
260
- }
261
-
262
- castToBooleanType(value)
263
- {
264
- if(this.isNullOrEmpty(value)){
265
- return null;
266
- }
267
- if('boolean' === typeof value){
268
- return value;
269
- }
270
- if('number' === typeof value){
271
- return 0 !== value;
272
- }
273
- if('string' === typeof value){
274
- let lowerValue = value.toLowerCase();
275
- if('true' === lowerValue || '1' === lowerValue || 'yes' === lowerValue){
276
- return true;
277
- }
278
- if('false' === lowerValue || '0' === lowerValue || 'no' === lowerValue){
279
- return false;
280
- }
281
- }
282
- return Boolean(value);
283
- }
284
-
285
- castToDateType(dateValue)
286
- {
287
- if(dateValue instanceof Date){
288
- return dateValue;
289
- }
290
- if(this.isNullOrEmpty(dateValue)){
291
- return null;
292
- }
293
- let dateString = String(dateValue);
294
- if(dateString.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/)){
295
- return new Date(dateString.replace(' ', 'T') + 'Z');
296
- }
297
- if(dateString.match(/^\d{4}-\d{2}-\d{2}$/)){
298
- return new Date(dateString + 'T00:00:00Z');
299
- }
300
- try {
301
- let parsedDate = new Date(dateString);
302
- if(isNaN(parsedDate.getTime())){
303
- Logger.warning('Invalid date value: '+dateString);
304
- return null;
305
- }
306
- return parsedDate;
307
- } catch(error) {
308
- Logger.warning('Failed to convert date value: '+dateString);
309
- return null;
310
- }
311
- }
312
-
313
- castToJsonType(value)
314
- {
315
- if(this.isNullOrEmpty(value)){
316
- return Prisma.DbNull;
317
- }
318
- if('object' === typeof value){
319
- return value;
320
- }
321
- if('string' === typeof value){
322
- if(value.startsWith('%') && value.endsWith('%')){
323
- return value;
324
- }
325
- try {
326
- return JSON.parse(value);
327
- } catch(error) {
328
- return value;
329
- }
330
- }
331
- return value;
332
- }
333
-
334
- castToStringType(value)
68
+ initModelMetadata()
335
69
  {
336
- if(null === value || undefined === value){
337
- return null;
338
- }
339
- if('string' === typeof value){
340
- if('null' === value){
341
- return null;
342
- }
343
- return value;
70
+ let metadata = this.metadataLoader.loadModelMetadata(this.tableName());
71
+ if(!metadata){
72
+ return;
344
73
  }
345
- return String(value);
74
+ this.requiredFields = metadata.requiredFields;
75
+ this.optionalFields = metadata.optionalFields;
76
+ this.fieldTypes = metadata.fieldTypes;
77
+ this.fieldDefaults = metadata.fieldDefaults;
78
+ this.idFieldType = metadata.idFieldType;
79
+ this.idFieldName = sc.get(metadata, 'idFieldName', 'id');
80
+ this.referenceFields = metadata.referenceFields;
81
+ this.relationMetadata = metadata.relationMetadata;
82
+ this.foreignKeyMappings = metadata.foreignKeyMappings;
83
+ this.foreignKeyTargetFields = sc.get(metadata, 'foreignKeyTargetFields', {});
84
+ this.jsonFields = metadata.jsonFields;
85
+ this.typeCaster.updateMetadata({
86
+ idFieldType: this.idFieldType,
87
+ jsonFields: this.jsonFields,
88
+ fieldTypes: this.fieldTypes,
89
+ fieldDefaults: this.fieldDefaults,
90
+ requiredFields: this.requiredFields,
91
+ optionalFields: this.optionalFields
92
+ });
93
+ this.relationResolver.updateMetadata({
94
+ relationMetadata: this.relationMetadata,
95
+ foreignKeyMappings: this.foreignKeyMappings,
96
+ foreignKeyTargetFields: this.foreignKeyTargetFields
97
+ });
98
+ this.filterProcessor.updateMetadata({
99
+ fieldTypes: this.fieldTypes,
100
+ referenceFields: this.referenceFields,
101
+ jsonFields: this.jsonFields
102
+ });
103
+ let currentModelMappings = sc.get(this.allRelationMappings, this.tableName(), {});
104
+ this.relationResolver.setCurrentModelMappings(currentModelMappings);
346
105
  }
347
106
 
348
107
  databaseName()
@@ -370,178 +129,103 @@ class PrismaDriver extends BaseDriver
370
129
  return this.rawModel[propertyName] || null;
371
130
  }
372
131
 
373
- prepareData(params)
132
+ getAllRelations()
374
133
  {
375
- let data = {};
376
- for(let key of Object.keys(params)){
377
- let fieldType = this.fieldTypes[key];
378
- if(!fieldType){
379
- data[key] = params[key];
380
- continue;
381
- }
382
- let castedValue = this.castValue(params[key], fieldType, key);
383
- if(undefined !== castedValue){
384
- data[key] = castedValue;
385
- }
134
+ return this.relationResolver.getAllRelations();
135
+ }
136
+
137
+ getQueryOptions()
138
+ {
139
+ return {
140
+ select: this.select,
141
+ limit: this.limit,
142
+ offset: this.offset,
143
+ sortBy: this.sortBy,
144
+ sortDirection: this.sortDirection
145
+ };
146
+ }
147
+
148
+ prepareData(params, options = {})
149
+ {
150
+ let skipObjects = sc.get(options, 'skipObjects', false);
151
+ let convertRelations = sc.get(options, 'convertRelations', false);
152
+ let isCreate = sc.get(options, 'isCreate', false);
153
+ if(convertRelations){
154
+ let converted = this.relationResolver.convertForeignKeysToRelations(params, isCreate);
155
+ return this.castDataFields(converted, skipObjects);
386
156
  }
387
- return data;
157
+ return this.castDataFields(params, skipObjects);
388
158
  }
389
159
 
390
- prepareDataWithRelations(params, isCreate = false)
160
+ castDataFields(params, skipObjects = false)
391
161
  {
392
162
  let data = {};
393
- let relations = {};
394
163
  for(let key of Object.keys(params)){
395
- if(sc.hasOwn(this.foreignKeyMappings, key)){
396
- let relationName = this.foreignKeyMappings[key];
397
- let value = params[key];
398
- if(!this.isNullOrEmpty(value)){
399
- relations[relationName] = {
400
- connect: {id: this.castToIdType(value)}
401
- };
402
- continue;
403
- }
404
- if(!isCreate){
405
- relations[relationName] = {
406
- disconnect: true
407
- };
408
- }
164
+ let value = params[key];
165
+ if(skipObjects && sc.isObject(value) && !sc.isArray(value)){
409
166
  continue;
410
167
  }
411
168
  let fieldType = this.fieldTypes[key];
412
169
  if(!fieldType){
413
- if(!this.isNullOrEmpty(params[key])){
414
- data[key] = params[key];
170
+ if(!skipObjects && !this.typeCaster.isNullOrEmpty(value)){
171
+ data[key] = value;
415
172
  }
416
173
  continue;
417
174
  }
418
- let castedValue = this.castValue(params[key], fieldType, key);
419
- if(undefined !== castedValue){
420
- if(this.isJsonFieldType(fieldType) && null === castedValue){
175
+ let castedValue = this.typeCaster.castValue(value, fieldType, key);
176
+ if(this.typeCaster.isValidCastedValue(castedValue)){
177
+ if(this.typeCaster.isJsonFieldType(fieldType) && null === castedValue && !skipObjects){
421
178
  continue;
422
179
  }
423
180
  data[key] = castedValue;
424
181
  }
425
182
  }
426
- return {...data, ...relations};
183
+ return data;
427
184
  }
428
185
 
429
186
  ensureRequiredFields(data)
430
187
  {
431
188
  let missingFields = [];
432
189
  for(let field of this.requiredFields){
433
- if(!sc.hasOwn(data, field)){
434
- missingFields.push(field);
435
- }
436
- }
437
- if(0 < missingFields.length){
438
- Logger.warning('Missing required fields for '+this.tableName()+': '+missingFields.join(', '));
439
- }
440
- return data;
441
- }
442
-
443
- processFilters(filters)
444
- {
445
- if(!sc.isObject(filters)){
446
- return filters;
447
- }
448
- let processedFilters = {};
449
- for(let key of Object.keys(filters)){
450
- let value = filters[key];
451
- if('OR' === key && sc.isArray(value)){
452
- processedFilters.OR = value.map(condition => this.processFilters(condition));
190
+ if(sc.hasOwn(data, field)){
453
191
  continue;
454
192
  }
455
- if(sc.hasOwn(value, 'operator') && sc.hasOwn(value, 'value')){
456
- let operatorValue = this.processFilterValue(value.value, key);
457
- processedFilters[key] = this.applyOperator(operatorValue, value.operator, key);
458
- continue;
193
+ if(sc.hasOwn(this.foreignKeyMappings, field)){
194
+ let relationName = this.foreignKeyMappings[field];
195
+ if(sc.hasOwn(data, relationName)){
196
+ continue;
197
+ }
459
198
  }
460
- if(sc.isObject(value) && !sc.isArray(value)){
461
- processedFilters[key] = this.processFilters(value);
199
+ if(sc.hasOwn(this.fieldDefaults, field)){
462
200
  continue;
463
201
  }
464
- processedFilters[key] = this.processFilterValue(value, key);
202
+ missingFields.push(field);
465
203
  }
466
- return processedFilters;
467
- }
468
-
469
- processFilterValue(value, key)
470
- {
471
- if(sc.isArray(value)){
472
- if('id' === key){
473
- return value.map(id => this.castToIdType(id));
474
- }
475
- let fieldType = this.fieldTypes[key];
476
- if(fieldType){
477
- return value.map(val => this.castValue(val, fieldType, key)).filter(val => undefined !== val);
478
- }
479
- return value;
480
- }
481
- if('id' === key){
482
- return this.castToIdType(value);
483
- }
484
- let fieldType = this.fieldTypes[key];
485
- if(fieldType){
486
- let castedValue = this.castValue(value, fieldType, key);
487
- return undefined !== castedValue ? castedValue : value;
488
- }
489
- return value;
490
- }
491
-
492
- transformRelationResults(result, includeConfig, relationMapping)
493
- {
494
- if(!result || !includeConfig){
495
- return result;
496
- }
497
- if(sc.isArray(result)){
498
- return result.map(item => this.transformSingleResult(item, includeConfig, relationMapping));
204
+ if(0 < missingFields.length){
205
+ Logger.warning('Missing required fields for '+this.tableName()+': '+missingFields.join(', '));
499
206
  }
500
- return this.transformSingleResult(result, includeConfig, relationMapping);
207
+ return data;
501
208
  }
502
209
 
503
- transformSingleResult(item, includeConfig, relationMapping)
210
+ async executeWithNormalization(operation, errorMessage, defaultReturn)
504
211
  {
505
- if(!item || !sc.isObject(item)){
506
- return item;
507
- }
508
- let transformed = {...item};
509
- for(let relationName of Object.keys(includeConfig)){
510
- if(!sc.hasOwn(transformed, relationName)){
511
- continue;
512
- }
513
- let relationMeta = this.relationMetadata[relationName];
514
- if(!relationMeta){
515
- continue;
516
- }
517
- let relationValue = transformed[relationName];
518
- if('one' === relationMeta.type && sc.isArray(relationValue)){
519
- relationValue = 0 < relationValue.length ? [...relationValue].shift() : null;
520
- }
521
- if('many' === relationMeta.type && !sc.isArray(relationValue)){
522
- relationValue = relationValue ? [relationValue] : [];
523
- }
524
- let originalName = sc.get(relationMapping, relationName, relationName);
525
- transformed[originalName] = relationValue;
526
- if(originalName !== relationName){
527
- delete transformed[relationName];
528
- }
212
+ try {
213
+ return this.typeCaster.normalizeReturnData(await operation());
214
+ } catch(error) {
215
+ Logger.error(errorMessage+error.message);
216
+ return defaultReturn;
529
217
  }
530
- return transformed;
531
218
  }
532
219
 
533
220
  async create(params)
534
221
  {
535
- let preparedData = this.prepareDataWithRelations(params, true);
222
+ let preparedData = this.prepareData(params, {convertRelations: true, isCreate: true});
536
223
  this.ensureRequiredFields(preparedData);
537
- try {
538
- return await this.model.create({
539
- data: preparedData
540
- });
541
- } catch(error) {
542
- Logger.error('Create error: '+error.message);
543
- return false;
544
- }
224
+ return this.executeWithNormalization(
225
+ () => this.model.create({data: preparedData}),
226
+ 'Create error: ',
227
+ false
228
+ );
545
229
  }
546
230
 
547
231
  async createWithRelations(params, relations)
@@ -553,121 +237,92 @@ class PrismaDriver extends BaseDriver
553
237
  this.ensureRequiredFields(preparedData);
554
238
  let createData = {data: preparedData};
555
239
  if(0 < relations.length){
556
- let includeData = this.buildIncludeObjectWithMapping(relations);
240
+ let includeData = this.relationResolver.buildIncludeObjectWithMapping(relations);
557
241
  createData.include = includeData.include;
558
- for(let relation of relations){
559
- let normalizedRelation = this.normalizeRelationName(relation);
560
- if(!sc.hasOwn(params, relation) && !sc.hasOwn(params, normalizedRelation)){
561
- continue;
562
- }
563
- let relationData = sc.get(params, relation, sc.get(params, normalizedRelation, null));
564
- if(sc.isArray(relationData)){
565
- createData.data[normalizedRelation] = {
566
- connect: relationData.map(item => ({id: this.castToIdType(item.id)}))
567
- };
568
- continue;
569
- }
570
- createData.data[normalizedRelation] = {
571
- connect: {id: this.castToIdType(relationData.id)}
572
- };
573
- }
574
- try {
575
- return this.transformRelationResults(await this.model.create(createData), includeData.include, includeData.mapping);
576
- } catch(error) {
577
- Logger.error('Create with relations error: '+error.message);
578
- return false;
579
- }
580
- }
581
- try {
582
- return await this.model.create(createData);
583
- } catch(error) {
584
- Logger.error('Create with relations error: '+error.message);
585
- return false;
586
- }
242
+ let relationData = this.relationResolver.buildCreateDataWithRelations(params, relations);
243
+ createData.data = {...createData.data, ...relationData};
244
+ return this.executeWithNormalization(
245
+ async () => this.relationResolver.transformRelationResults(
246
+ await this.model.create(createData),
247
+ includeData.include,
248
+ includeData.mapping
249
+ ),
250
+ 'Create with relations error: ',
251
+ false
252
+ );
253
+ }
254
+ return this.executeWithNormalization(
255
+ () => this.model.create(createData),
256
+ 'Create with relations error: ',
257
+ false
258
+ );
587
259
  }
588
260
 
589
261
  async update(filters, updatePatch)
590
262
  {
591
- let preparedData = this.prepareDataWithRelations(updatePatch);
592
- let processedFilters = this.processFilters(filters);
593
- try {
594
- return await this.model.updateMany({
595
- where: processedFilters,
596
- data: preparedData
597
- });
598
- } catch(error) {
599
- Logger.error('Update error: '+error.message);
600
- return false;
601
- }
263
+ let preparedData = this.prepareData(updatePatch, {skipObjects: true});
264
+ let processedFilters = this.filterProcessor.processFilters(filters);
265
+ return this.executeWithNormalization(
266
+ () => this.model.updateMany({where: processedFilters, data: preparedData}),
267
+ 'Update error: ',
268
+ false
269
+ );
602
270
  }
603
271
 
604
272
  async updateBy(field, fieldValue, updatePatch, operator = null)
605
273
  {
606
- let filter = this.createSingleFilter(field, fieldValue, operator);
607
- let preparedData = this.prepareDataWithRelations(updatePatch);
608
- try {
609
- return await this.model.updateMany({
610
- where: filter,
611
- data: preparedData
612
- });
613
- } catch(error) {
614
- Logger.error('Update by error: '+error.message);
615
- return false;
616
- }
274
+ let filter = this.filterProcessor.createSingleFilter(field, fieldValue, operator);
275
+ let preparedData = this.prepareData(updatePatch, {skipObjects: true});
276
+ return this.executeWithNormalization(
277
+ () => this.model.updateMany({where: filter, data: preparedData}),
278
+ 'Update by error: ',
279
+ false
280
+ );
617
281
  }
618
282
 
619
283
  async updateById(id, params)
620
284
  {
621
- let castedId = this.castToIdType(id);
285
+ let castedId = this.typeCaster.castToIdType(id);
622
286
  let found = await this.loadById(castedId);
623
287
  if(!found){
624
288
  return false;
625
289
  }
626
- let preparedData = this.prepareDataWithRelations(params);
627
- try {
628
- return await this.model.update({
629
- where: {id: castedId},
630
- data: preparedData
631
- });
632
- } catch(error) {
633
- Logger.error('Update by ID error: '+error.message);
634
- return false;
635
- }
290
+ let preparedData = this.prepareData(params, {convertRelations: true});
291
+ return this.executeWithNormalization(
292
+ () => this.model.update({where: {[this.idFieldName]: castedId}, data: preparedData}),
293
+ 'Update by ID error: ',
294
+ false
295
+ );
636
296
  }
637
297
 
638
298
  async upsert(params, filters)
639
299
  {
640
- let preparedData = this.prepareDataWithRelations(params);
641
- let existing = false;
642
- if(params.id){
643
- let castedId = this.castToIdType(params.id);
644
- existing = await this.loadById(castedId);
300
+ let preparedData = this.prepareData(params, {convertRelations: true});
301
+ let idValue = params[this.idFieldName];
302
+ if(idValue){
303
+ let castedId = this.typeCaster.castToIdType(idValue);
304
+ let existing = await this.loadById(castedId);
645
305
  if(existing){
646
306
  let patch = Object.assign({}, preparedData);
647
- delete patch.id;
648
- try {
649
- return await this.model.update({
650
- where: {id: castedId},
651
- data: patch
652
- });
653
- } catch(error) {
654
- Logger.error('Upsert update error: '+error.message);
655
- return false;
656
- }
307
+ delete patch[this.idFieldName];
308
+ return this.executeWithNormalization(
309
+ () => this.model.update({where: {[this.idFieldName]: castedId}, data: patch}),
310
+ 'Upsert update error: ',
311
+ false
312
+ );
657
313
  }
658
314
  }
659
315
  if(filters){
660
316
  let existing = await this.loadOne(filters);
661
317
  if(existing){
662
- try {
663
- return await this.model.update({
664
- where: {id: this.castToIdType(existing.id)},
318
+ return this.executeWithNormalization(
319
+ () => this.model.update({
320
+ where: {[this.idFieldName]: this.typeCaster.castToIdType(existing[this.idFieldName])},
665
321
  data: preparedData
666
- });
667
- } catch(error) {
668
- Logger.error('Upsert update by filter error: '+error.message);
669
- return false;
670
- }
322
+ }),
323
+ 'Upsert update by filter error: ',
324
+ false
325
+ );
671
326
  }
672
327
  }
673
328
  return await this.create(preparedData);
@@ -675,45 +330,36 @@ class PrismaDriver extends BaseDriver
675
330
 
676
331
  async delete(filters = {})
677
332
  {
678
- let processedFilters = this.processFilters(filters);
679
- try {
680
- return await this.model.deleteMany({
681
- where: processedFilters
682
- });
683
- } catch(error) {
684
- Logger.error('Delete error: '+error.message);
685
- return false;
686
- }
333
+ let processedFilters = this.filterProcessor.processFilters(filters);
334
+ return this.executeWithNormalization(
335
+ () => this.model.deleteMany({where: processedFilters}),
336
+ 'Delete error: ',
337
+ false
338
+ );
687
339
  }
688
340
 
689
341
  async deleteById(id)
690
342
  {
691
- let castedId = this.castToIdType(id);
343
+ let castedId = this.typeCaster.castToIdType(id);
692
344
  let found = await this.loadById(castedId);
693
345
  if(!found){
694
346
  return false;
695
347
  }
696
- try {
697
- return await this.model.delete({
698
- where: {id: castedId}
699
- });
700
- } catch(error) {
701
- Logger.error('Delete by ID error: '+error.message);
702
- return false;
703
- }
348
+ return this.executeWithNormalization(
349
+ () => this.model.delete({where: {[this.idFieldName]: castedId}}),
350
+ 'Delete by ID error: ',
351
+ false
352
+ );
704
353
  }
705
354
 
706
355
  async count(filters)
707
356
  {
708
- let processedFilters = this.processFilters(filters);
709
- try {
710
- return await this.model.count({
711
- where: processedFilters
712
- });
713
- } catch(error) {
714
- Logger.error('Count error: '+error.message);
715
- return 0;
716
- }
357
+ let processedFilters = this.filterProcessor.processFilters(filters);
358
+ return this.executeWithNormalization(
359
+ () => this.model.count({where: processedFilters}),
360
+ 'Count error: ',
361
+ 0
362
+ );
717
363
  }
718
364
 
719
365
  async countWithRelations(filters, relations)
@@ -721,36 +367,32 @@ class PrismaDriver extends BaseDriver
721
367
  if(!sc.isArray(relations) || 0 === relations.length){
722
368
  relations = this.getAllRelations();
723
369
  }
724
- let processedFilters = this.processFilters(filters);
725
- let query = {
726
- where: processedFilters
727
- };
370
+ let processedFilters = this.filterProcessor.processFilters(filters);
371
+ let query = {where: processedFilters};
728
372
  if(0 < relations.length){
729
- query.include = this.buildIncludeObjectWithMapping(relations).include;
730
- }
731
- try {
732
- return await this.model.count(query);
733
- } catch(error) {
734
- Logger.error('Count with relations error: '+error.message);
735
- return 0;
373
+ query.include = this.relationResolver.buildIncludeObjectWithMapping(relations).include;
736
374
  }
375
+ return this.executeWithNormalization(
376
+ () => this.model.count(query),
377
+ 'Count with relations error: ',
378
+ 0
379
+ );
737
380
  }
738
381
 
739
382
  async loadAll()
740
383
  {
741
- try {
742
- return await this.model.findMany(this.buildQueryOptions());
743
- } catch(error) {
744
- Logger.error('Load all error: '+error.message);
745
- return [];
746
- }
384
+ return this.executeWithNormalization(
385
+ () => this.model.findMany(this.queryBuilder.buildQueryOptions(this.getQueryOptions())),
386
+ 'Load all error: ',
387
+ []
388
+ );
747
389
  }
748
390
 
749
391
  async loadAllWithRelations(relations)
750
392
  {
751
393
  return this.executeQueryWithRelations(
752
394
  (q) => this.model.findMany(q),
753
- this.buildQueryOptions(),
395
+ this.queryBuilder.buildQueryOptions(this.getQueryOptions()),
754
396
  relations,
755
397
  'Load all with relations error: ',
756
398
  []
@@ -759,21 +401,20 @@ class PrismaDriver extends BaseDriver
759
401
 
760
402
  async load(filters)
761
403
  {
762
- let processedFilters = this.processFilters(filters);
763
- let query = this.buildQueryOptions();
404
+ let processedFilters = this.filterProcessor.processFilters(filters);
405
+ let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions());
764
406
  query.where = processedFilters;
765
- try {
766
- return await this.model.findMany(query);
767
- } catch(error) {
768
- Logger.error('Load error: '+error.message);
769
- return [];
770
- }
407
+ return this.executeWithNormalization(
408
+ () => this.model.findMany(query),
409
+ 'Load error: ',
410
+ []
411
+ );
771
412
  }
772
413
 
773
414
  async loadWithRelations(filters, relations)
774
415
  {
775
- let query = this.buildQueryOptions();
776
- query.where = this.processFilters(filters);
416
+ let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions());
417
+ query.where = this.filterProcessor.processFilters(filters);
777
418
  return this.executeQueryWithRelations(
778
419
  (q) => this.model.findMany(q),
779
420
  query,
@@ -785,21 +426,20 @@ class PrismaDriver extends BaseDriver
785
426
 
786
427
  async loadBy(field, fieldValue, operator = null)
787
428
  {
788
- let filter = this.createSingleFilter(field, fieldValue, operator);
789
- let query = this.buildQueryOptions();
429
+ let filter = this.filterProcessor.createSingleFilter(field, fieldValue, operator);
430
+ let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions());
790
431
  query.where = filter;
791
- try {
792
- return await this.model.findMany(query);
793
- } catch(error) {
794
- Logger.error('Load by error: '+error.message);
795
- return [];
796
- }
432
+ return this.executeWithNormalization(
433
+ () => this.model.findMany(query),
434
+ 'Load by error: ',
435
+ []
436
+ );
797
437
  }
798
438
 
799
439
  async loadByWithRelations(field, fieldValue, relations, operator = null)
800
440
  {
801
- let query = this.buildQueryOptions();
802
- query.where = this.createSingleFilter(field, fieldValue, operator);
441
+ let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions());
442
+ query.where = this.filterProcessor.createSingleFilter(field, fieldValue, operator);
803
443
  return this.executeQueryWithRelations(
804
444
  (q) => this.model.findMany(q),
805
445
  query,
@@ -811,22 +451,19 @@ class PrismaDriver extends BaseDriver
811
451
 
812
452
  async loadById(id)
813
453
  {
814
- let castedId = this.castToIdType(id);
815
- try {
816
- return await this.model.findUnique({
817
- where: {id: castedId}
818
- });
819
- } catch(error) {
820
- Logger.error('Load by ID error: '+error.message);
821
- return null;
822
- }
454
+ let castedId = this.typeCaster.castToIdType(id);
455
+ return this.executeWithNormalization(
456
+ () => this.model.findUnique({where: {[this.idFieldName]: castedId}}),
457
+ 'Load by ID error: ',
458
+ null
459
+ );
823
460
  }
824
461
 
825
462
  async loadByIdWithRelations(id, relations)
826
463
  {
827
464
  return this.executeQueryWithRelations(
828
465
  (q) => this.model.findUnique(q),
829
- {where: {id: this.castToIdType(id)}},
466
+ {where: {[this.idFieldName]: this.typeCaster.castToIdType(id)}},
830
467
  relations,
831
468
  'Load by ID with relations error: ',
832
469
  null
@@ -835,36 +472,33 @@ class PrismaDriver extends BaseDriver
835
472
 
836
473
  async loadByIds(ids)
837
474
  {
838
- let castedIds = ids.map(id => this.castToIdType(id));
839
- try {
840
- return await this.model.findMany({
841
- where: {
842
- id: {in: castedIds}
843
- }
844
- });
845
- } catch(error) {
846
- Logger.error('Load by IDs error: '+error.message);
475
+ let castedIds = ids.map(id => this.typeCaster.castToIdType(id)).filter(id => null !== id);
476
+ if(0 === castedIds.length){
847
477
  return [];
848
478
  }
479
+ return this.executeWithNormalization(
480
+ () => this.model.findMany({where: {[this.idFieldName]: {in: castedIds}}}),
481
+ 'Load by IDs error: ',
482
+ []
483
+ );
849
484
  }
850
485
 
851
486
  async loadOne(filters)
852
487
  {
853
- let processedFilters = this.processFilters(filters);
854
- let query = this.buildQueryOptions(false);
488
+ let processedFilters = this.filterProcessor.processFilters(filters);
489
+ let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions(), false);
855
490
  query.where = processedFilters;
856
- try {
857
- return await this.model.findFirst(query);
858
- } catch(error) {
859
- Logger.error('Load one error: '+error.message);
860
- return null;
861
- }
491
+ return this.executeWithNormalization(
492
+ () => this.model.findFirst(query),
493
+ 'Load one error: ',
494
+ null
495
+ );
862
496
  }
863
497
 
864
498
  async loadOneWithRelations(filters, relations)
865
499
  {
866
- let query = this.buildQueryOptions(false);
867
- query.where = this.processFilters(filters);
500
+ let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions(), false);
501
+ query.where = this.filterProcessor.processFilters(filters);
868
502
  return this.executeQueryWithRelations(
869
503
  (q) => this.model.findFirst(q),
870
504
  query,
@@ -876,21 +510,20 @@ class PrismaDriver extends BaseDriver
876
510
 
877
511
  async loadOneBy(field, fieldValue, operator = null)
878
512
  {
879
- let filter = this.createSingleFilter(field, fieldValue, operator);
880
- let query = this.buildQueryOptions(false);
513
+ let filter = this.filterProcessor.createSingleFilter(field, fieldValue, operator);
514
+ let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions(), false);
881
515
  query.where = filter;
882
- try {
883
- return await this.model.findFirst(query);
884
- } catch(error) {
885
- Logger.error('Load one by error: '+error.message);
886
- return null;
887
- }
516
+ return this.executeWithNormalization(
517
+ () => this.model.findFirst(query),
518
+ 'Load one by error: ',
519
+ null
520
+ );
888
521
  }
889
522
 
890
523
  async loadOneByWithRelations(field, fieldValue, relations, operator = null)
891
524
  {
892
- let query = this.buildQueryOptions(false);
893
- query.where = this.createSingleFilter(field, fieldValue, operator);
525
+ let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions(), false);
526
+ query.where = this.filterProcessor.createSingleFilter(field, fieldValue, operator);
894
527
  return this.executeQueryWithRelations(
895
528
  (q) => this.model.findFirst(q),
896
529
  query,
@@ -900,115 +533,24 @@ class PrismaDriver extends BaseDriver
900
533
  );
901
534
  }
902
535
 
903
- buildQueryOptions(useLimit = true)
904
- {
905
- let queryOptions = {};
906
- if(0 < this.select.length){
907
- queryOptions.select = {};
908
- for(let field of this.select){
909
- queryOptions.select[field] = true;
910
- }
911
- }
912
- if(useLimit && 0 !== this.limit){
913
- queryOptions.take = this.limit;
914
- }
915
- if(0 !== this.offset){
916
- queryOptions.skip = this.offset;
917
- }
918
- if(false !== this.sortBy && false !== this.sortDirection){
919
- queryOptions.orderBy = {
920
- [this.sortBy]: this.sortDirection.toLowerCase()
921
- };
922
- }
923
- return queryOptions;
924
- }
925
-
926
- createSingleFilter(field, fieldValue, operator = null)
927
- {
928
- let filter = {};
929
- let processedValue = this.processFilterValue(fieldValue, field);
930
- if(null === operator){
931
- filter[field] = processedValue;
932
- return filter;
933
- }
934
- filter[field] = {operator, value: processedValue};
935
- return this.processFilters(filter);
936
- }
937
-
938
- applyOperator(value, operator, fieldName = null)
939
- {
940
- let upperOperator = operator ? operator.toUpperCase() : '';
941
- if('LIKE' === upperOperator){
942
- return this.handleLikeOperator(value, fieldName);
943
- }
944
- let operatorsMap = {
945
- '=': value,
946
- '!=': {not: value},
947
- '>': {gt: value},
948
- '>=': {gte: value},
949
- '<': {lt: value},
950
- '<=': {lte: value},
951
- 'IN': {in: value},
952
- 'NOT IN': {notIn: value}
953
- };
954
- return operatorsMap[upperOperator] || value;
955
- }
956
-
957
- handleLikeOperator(value, fieldName)
958
- {
959
- let cleanValue = String(value).replace(/%/g, '');
960
- if(this.isJsonFieldByName(fieldName) || this.isJsonField(fieldName)){
961
- return this.handleJsonTextSearch(cleanValue);
962
- }
963
- if('id' === fieldName || sc.hasOwn(this.referenceFields, fieldName)){
964
- let numericValue = Number(cleanValue);
965
- if(!isNaN(numericValue)){
966
- return numericValue;
967
- }
968
- }
969
- return {contains: cleanValue};
970
- }
971
-
972
- handleJsonTextSearch(searchValue)
973
- {
974
- return {
975
- string_contains: searchValue
976
- };
977
- }
978
-
979
536
  async executeQueryWithRelations(modelMethod, query, relations, errorMessage, defaultReturn)
980
537
  {
981
538
  if(!sc.isArray(relations) || 0 === relations.length){
982
539
  relations = this.getAllRelations();
983
540
  }
984
- let includeData = this.buildIncludeObjectWithMapping(relations);
541
+ let includeData = this.relationResolver.buildIncludeObjectWithMapping(relations);
985
542
  if(0 < Object.keys(includeData.include).length){
986
543
  query.include = includeData.include;
987
544
  }
988
- try {
989
- return this.transformRelationResults(await modelMethod(query), includeData.include, includeData.mapping);
990
- } catch(error) {
991
- Logger.error(errorMessage+error.message);
992
- return defaultReturn;
993
- }
994
- }
995
-
996
- buildIncludeObjectWithMapping(relations)
997
- {
998
- let include = {};
999
- let mapping = {};
1000
- for(let relation of relations){
1001
- let normalizedRelation = this.normalizeRelationName(relation);
1002
- if(!sc.hasOwn(this.relationMetadata, normalizedRelation)){
1003
- //Logger.debug('Relation "'+relation+'" (normalized: "'+normalizedRelation+'") not found in model '+this.tableName());
1004
- continue;
1005
- }
1006
- include[normalizedRelation] = true;
1007
- if(relation !== normalizedRelation){
1008
- mapping[normalizedRelation] = relation;
1009
- }
1010
- }
1011
- return {include, mapping};
545
+ return this.executeWithNormalization(
546
+ async () => this.relationResolver.transformRelationResults(
547
+ await modelMethod(query),
548
+ includeData.include,
549
+ includeData.mapping
550
+ ),
551
+ errorMessage,
552
+ defaultReturn
553
+ );
1012
554
  }
1013
555
 
1014
556
  }