@reldens/storage 0.32.0 → 0.34.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.
@@ -0,0 +1,523 @@
1
+ /**
2
+ *
3
+ * Reldens - PrismaDriver
4
+ *
5
+ */
6
+
7
+ const { BaseDriver } = require('../base-driver');
8
+ const { ErrorManager, Logger, sc } = require('@reldens/utils');
9
+
10
+ class PrismaDriver extends BaseDriver
11
+ {
12
+
13
+ constructor(props)
14
+ {
15
+ super(props);
16
+ if(!props.prisma){
17
+ ErrorManager.error('Missing Prisma client on Prisma driver.');
18
+ }
19
+ if(!props.model){
20
+ ErrorManager.error('Missing Prisma model on Prisma driver.');
21
+ }
22
+ if(!props.server){
23
+ ErrorManager.error('Missing Server Driver on Prisma driver.');
24
+ }
25
+ this.prisma = props.prisma;
26
+ this.model = props.model;
27
+ this.server = props.server;
28
+ this.requiredFields = [];
29
+ this.fieldTypes = {};
30
+ this.initFieldMetadata();
31
+ }
32
+
33
+ async initFieldMetadata()
34
+ {
35
+ try {
36
+ let dmmf = this.prisma._baseDmmf;
37
+ if(!dmmf){
38
+ return;
39
+ }
40
+ let modelInfo = dmmf.modelMap[this.tableName().toLowerCase()];
41
+ if(!modelInfo){
42
+ return;
43
+ }
44
+ for(let field of modelInfo.fields){
45
+ if(field.isRequired && !field.hasDefaultValue && !field.isId){
46
+ this.requiredFields.push(field.name);
47
+ }
48
+ this.fieldTypes[field.name] = field.type;
49
+ }
50
+ } catch(error) {
51
+ Logger.warning('Could not initialize field metadata: '+error.message);
52
+ }
53
+ }
54
+
55
+ databaseName()
56
+ {
57
+ return this.config.database || '';
58
+ }
59
+
60
+ id()
61
+ {
62
+ return this.name || '';
63
+ }
64
+
65
+ name()
66
+ {
67
+ return this.rawName || '';
68
+ }
69
+
70
+ tableName()
71
+ {
72
+ return this.model._name || '';
73
+ }
74
+
75
+ property(propertyName)
76
+ {
77
+ return this.rawModel[propertyName] || null;
78
+ }
79
+
80
+ prepareData(params)
81
+ {
82
+ let data = {...params};
83
+ for(let key in data){
84
+ if('status' === key && 'number' === typeof data[key]){
85
+ data[key] = String(data[key]);
86
+ }
87
+ }
88
+ return data;
89
+ }
90
+
91
+ ensureRequiredFields(data)
92
+ {
93
+ let missingFields = [];
94
+ for(let field of this.requiredFields){
95
+ if(!sc.hasOwn(data, field)){
96
+ missingFields.push(field);
97
+ }
98
+ }
99
+ if(0 < missingFields.length){
100
+ Logger.warning('Missing required fields for '+this.tableName()+': '+missingFields.join(', '));
101
+ }
102
+ return data;
103
+ }
104
+
105
+ async create(params)
106
+ {
107
+ try {
108
+ let preparedData = this.prepareData(params);
109
+ this.ensureRequiredFields(preparedData);
110
+ return await this.model.create({
111
+ data: preparedData
112
+ });
113
+ } catch(error) {
114
+ Logger.error('Create error: '+error.message);
115
+ return false;
116
+ }
117
+ }
118
+
119
+ async createWithRelations(params, relations)
120
+ {
121
+ try {
122
+ let preparedData = this.prepareData(params);
123
+ this.ensureRequiredFields(preparedData);
124
+ let createData = {data: preparedData};
125
+ if(sc.isArray(relations) && 0 < relations.length){
126
+ for(let relation of relations){
127
+ if(!sc.hasOwn(params, relation)){
128
+ continue;
129
+ }
130
+ let relationData = params[relation];
131
+ if(sc.isArray(relationData)){
132
+ createData.data[relation] = {
133
+ connect: relationData.map(item => ({id: item.id}))
134
+ };
135
+ continue;
136
+ }
137
+ createData.data[relation] = {
138
+ connect: {id: relationData.id}
139
+ };
140
+ }
141
+ }
142
+ return await this.model.create(createData);
143
+ } catch(error) {
144
+ Logger.error('Create with relations error: '+error.message);
145
+ return false;
146
+ }
147
+ }
148
+
149
+ async update(filters, updatePatch)
150
+ {
151
+ try {
152
+ let preparedData = this.prepareData(updatePatch);
153
+ return await this.model.updateMany({
154
+ where: filters,
155
+ data: preparedData
156
+ });
157
+ } catch(error) {
158
+ Logger.error('Update error: '+error.message);
159
+ return false;
160
+ }
161
+ }
162
+
163
+ async updateBy(field, fieldValue, updatePatch, operator = null)
164
+ {
165
+ let filter = this.createSingleFilter(field, fieldValue, operator);
166
+ try {
167
+ let preparedData = this.prepareData(updatePatch);
168
+ return await this.model.updateMany({
169
+ where: filter,
170
+ data: preparedData
171
+ });
172
+ } catch(error) {
173
+ Logger.error('Update by error: '+error.message);
174
+ return false;
175
+ }
176
+ }
177
+
178
+ async updateById(id, params)
179
+ {
180
+ try {
181
+ let found = await this.loadById(id);
182
+ if(!found){
183
+ return false;
184
+ }
185
+ let preparedData = this.prepareData(params);
186
+ return await this.model.update({
187
+ where: {id},
188
+ data: preparedData
189
+ });
190
+ } catch(error) {
191
+ Logger.error('Update by ID error: '+error.message);
192
+ return false;
193
+ }
194
+ }
195
+
196
+ async upsert(params, filters)
197
+ {
198
+ try {
199
+ let preparedData = this.prepareData(params);
200
+ let existing = false;
201
+ if(params.id){
202
+ existing = await this.loadById(params.id);
203
+ if(existing){
204
+ let patch = Object.assign({}, preparedData);
205
+ delete patch.id;
206
+ return await this.model.update({
207
+ where: {id: params.id},
208
+ data: patch
209
+ });
210
+ }
211
+ }
212
+ if(filters){
213
+ let existing = await this.loadOne(filters);
214
+ if(existing){
215
+ return await this.model.update({
216
+ where: {id: existing.id},
217
+ data: preparedData
218
+ });
219
+ }
220
+ }
221
+ return await this.create(preparedData);
222
+ } catch(error) {
223
+ Logger.error('Upsert error: '+error.message);
224
+ return false;
225
+ }
226
+ }
227
+
228
+ async delete(filters = {})
229
+ {
230
+ try {
231
+ return await this.model.deleteMany({
232
+ where: filters
233
+ });
234
+ } catch(error) {
235
+ Logger.error('Delete error: '+error.message);
236
+ return false;
237
+ }
238
+ }
239
+
240
+ async deleteById(id)
241
+ {
242
+ try {
243
+ let found = await this.loadById(id);
244
+ if(!found){
245
+ return false;
246
+ }
247
+ return await this.model.delete({
248
+ where: {id}
249
+ });
250
+ } catch(error) {
251
+ Logger.error('Delete by ID error: '+error.message);
252
+ return false;
253
+ }
254
+ }
255
+
256
+ async count(filters)
257
+ {
258
+ try {
259
+ return await this.model.count({
260
+ where: filters
261
+ });
262
+ } catch(error) {
263
+ Logger.error('Count error: '+error.message);
264
+ return 0;
265
+ }
266
+ }
267
+
268
+ async countWithRelations(filters, relations)
269
+ {
270
+ try {
271
+ let query = {
272
+ where: filters
273
+ };
274
+ if(sc.isArray(relations) && 0 < relations.length){
275
+ query.include = this.buildIncludeObject(relations);
276
+ }
277
+ return await this.model.count(query);
278
+ } catch(error) {
279
+ Logger.error('Count with relations error: '+error.message);
280
+ return 0;
281
+ }
282
+ }
283
+
284
+ async loadAll()
285
+ {
286
+ try {
287
+ return await this.model.findMany(this.buildQueryOptions());
288
+ } catch(error) {
289
+ Logger.error('Load all error: '+error.message);
290
+ return [];
291
+ }
292
+ }
293
+
294
+ async loadAllWithRelations(relations)
295
+ {
296
+ try {
297
+ let query = this.buildQueryOptions();
298
+ if(sc.isArray(relations) && 0 < relations.length){
299
+ query.include = this.buildIncludeObject(relations);
300
+ }
301
+ return await this.model.findMany(query);
302
+ } catch(error) {
303
+ Logger.error('Load all with relations error: '+error.message);
304
+ return [];
305
+ }
306
+ }
307
+
308
+ async load(filters)
309
+ {
310
+ try {
311
+ let query = this.buildQueryOptions();
312
+ query.where = filters;
313
+ return await this.model.findMany(query);
314
+ } catch(error) {
315
+ Logger.error('Load error: '+error.message);
316
+ return [];
317
+ }
318
+ }
319
+
320
+ async loadWithRelations(filters, relations)
321
+ {
322
+ try {
323
+ let query = this.buildQueryOptions();
324
+ query.where = filters;
325
+ if(sc.isArray(relations) && 0 < relations.length){
326
+ query.include = this.buildIncludeObject(relations);
327
+ }
328
+ return await this.model.findMany(query);
329
+ } catch(error) {
330
+ Logger.error('Load with relations error: '+error.message);
331
+ return [];
332
+ }
333
+ }
334
+
335
+ async loadBy(field, fieldValue, operator = null)
336
+ {
337
+ try {
338
+ let filter = this.createSingleFilter(field, fieldValue, operator);
339
+ let query = this.buildQueryOptions();
340
+ query.where = filter;
341
+ return await this.model.findMany(query);
342
+ } catch(error) {
343
+ Logger.error('Load by error: '+error.message);
344
+ return [];
345
+ }
346
+ }
347
+
348
+ async loadByWithRelations(field, fieldValue, relations, operator = null)
349
+ {
350
+ try {
351
+ let filter = this.createSingleFilter(field, fieldValue, operator);
352
+ let query = this.buildQueryOptions();
353
+ query.where = filter;
354
+ if(sc.isArray(relations) && 0 < relations.length){
355
+ query.include = this.buildIncludeObject(relations);
356
+ }
357
+ return await this.model.findMany(query);
358
+ } catch(error) {
359
+ Logger.error('Load by with relations error: '+error.message);
360
+ return [];
361
+ }
362
+ }
363
+
364
+ async loadById(id)
365
+ {
366
+ try {
367
+ return await this.model.findUnique({
368
+ where: {id}
369
+ });
370
+ } catch(error) {
371
+ Logger.error('Load by ID error: '+error.message);
372
+ return null;
373
+ }
374
+ }
375
+
376
+ async loadByIdWithRelations(id, relations)
377
+ {
378
+ try {
379
+ let query = {
380
+ where: {id}
381
+ };
382
+ if(sc.isArray(relations) && 0 < relations.length){
383
+ query.include = this.buildIncludeObject(relations);
384
+ }
385
+ return await this.model.findUnique(query);
386
+ } catch(error) {
387
+ Logger.error('Load by ID with relations error: '+error.message);
388
+ return null;
389
+ }
390
+ }
391
+
392
+ async loadByIds(ids)
393
+ {
394
+ try {
395
+ return await this.model.findMany({
396
+ where: {
397
+ id: {in: ids}
398
+ }
399
+ });
400
+ } catch(error) {
401
+ Logger.error('Load by IDs error: '+error.message);
402
+ return [];
403
+ }
404
+ }
405
+
406
+ async loadOne(filters)
407
+ {
408
+ try {
409
+ let query = this.buildQueryOptions(false);
410
+ query.where = filters;
411
+ return await this.model.findFirst(query);
412
+ } catch(error) {
413
+ Logger.error('Load one error: '+error.message);
414
+ return null;
415
+ }
416
+ }
417
+
418
+ async loadOneWithRelations(filters, relations)
419
+ {
420
+ try {
421
+ let query = this.buildQueryOptions(false);
422
+ query.where = filters;
423
+ if(sc.isArray(relations) && 0 < relations.length){
424
+ query.include = this.buildIncludeObject(relations);
425
+ }
426
+ return await this.model.findFirst(query);
427
+ } catch(error) {
428
+ Logger.error('Load one with relations error: '+error.message);
429
+ return null;
430
+ }
431
+ }
432
+
433
+ async loadOneBy(field, fieldValue, operator = null)
434
+ {
435
+ try {
436
+ let filter = this.createSingleFilter(field, fieldValue, operator);
437
+ let query = this.buildQueryOptions(false);
438
+ query.where = filter;
439
+ return await this.model.findFirst(query);
440
+ } catch(error) {
441
+ Logger.error('Load one by error: '+error.message);
442
+ return null;
443
+ }
444
+ }
445
+
446
+ async loadOneByWithRelations(field, fieldValue, relations, operator = null)
447
+ {
448
+ try {
449
+ let filter = this.createSingleFilter(field, fieldValue, operator);
450
+ let query = this.buildQueryOptions(false);
451
+ query.where = filter;
452
+ if(sc.isArray(relations) && 0 < relations.length){
453
+ query.include = this.buildIncludeObject(relations);
454
+ }
455
+ return await this.model.findFirst(query);
456
+ } catch(error) {
457
+ Logger.error('Load one by with relations error: '+error.message);
458
+ return null;
459
+ }
460
+ }
461
+
462
+ buildQueryOptions(useLimit = true)
463
+ {
464
+ let queryOptions = {};
465
+ if(0 < this.select.length){
466
+ queryOptions.select = {};
467
+ for(let field of this.select){
468
+ queryOptions.select[field] = true;
469
+ }
470
+ }
471
+ if(useLimit && 0 !== this.limit){
472
+ queryOptions.take = this.limit;
473
+ }
474
+ if(0 !== this.offset){
475
+ queryOptions.skip = this.offset;
476
+ }
477
+ if(false !== this.sortBy && false !== this.sortDirection){
478
+ queryOptions.orderBy = {
479
+ [this.sortBy]: this.sortDirection.toLowerCase()
480
+ };
481
+ }
482
+ return queryOptions;
483
+ }
484
+
485
+ createSingleFilter(field, fieldValue, operator = null)
486
+ {
487
+ let filter = {};
488
+ if(null === operator){
489
+ filter[field] = fieldValue;
490
+ return filter;
491
+ }
492
+ filter[field] = this.applyOperator(fieldValue, operator);
493
+ return filter;
494
+ }
495
+
496
+ applyOperator(value, operator)
497
+ {
498
+ let operatorsMap = {
499
+ '=': value,
500
+ '!=': {not: value},
501
+ '>': {gt: value},
502
+ '>=': {gte: value},
503
+ '<': {lt: value},
504
+ '<=': {lte: value},
505
+ 'LIKE': {contains: value},
506
+ 'IN': {in: value},
507
+ 'NOT IN': {notIn: value}
508
+ };
509
+ return operatorsMap[operator] || value;
510
+ }
511
+
512
+ buildIncludeObject(relations)
513
+ {
514
+ let include = {};
515
+ for(let relation of relations){
516
+ include[relation] = true;
517
+ }
518
+ return include;
519
+ }
520
+
521
+ }
522
+
523
+ module.exports.PrismaDriver = PrismaDriver;
@@ -0,0 +1,71 @@
1
+ /**
2
+ *
3
+ * Reldens - PrismaSchemaGenerator
4
+ *
5
+ */
6
+
7
+ const { ErrorManager, Logger, sc } = require('@reldens/utils');
8
+ const { FileHandler } = require('@reldens/server-utils');
9
+ const { execSync } = require('child_process');
10
+
11
+ class PrismaSchemaGenerator
12
+ {
13
+
14
+ constructor(props)
15
+ {
16
+ this.config = sc.get(props, 'config', {});
17
+ this.client = sc.get(props, 'client', 'mysql');
18
+ this.debug = sc.get(props, 'debug', false);
19
+ this.prismaSchemaPath = sc.get(props, 'prismaSchemaPath',
20
+ FileHandler.joinPaths(process.cwd(), 'prisma'));
21
+ this.schemaFilePath = FileHandler.joinPaths(this.prismaSchemaPath,
22
+ 'schema.prisma');
23
+ }
24
+
25
+ async generate()
26
+ {
27
+ try {
28
+ FileHandler.createFolder(this.prismaSchemaPath, { recursive: true });
29
+ this.generateSchemaFile();
30
+ Logger.info('Running prisma introspect "npx prisma db pull"...');
31
+ execSync('npx prisma db pull', { stdio: 'inherit' });
32
+ Logger.info('Running prisma generate "npx prisma generate"...');
33
+ execSync('npx prisma generate', { stdio: 'inherit' });
34
+ return true;
35
+ } catch(error) {
36
+ ErrorManager.error('Failed to generate Prisma schema. '+error.message);
37
+ return false;
38
+ }
39
+ }
40
+
41
+ generateSchemaFile()
42
+ {
43
+ let datasourceProvider = 'mysql';
44
+ if('postgresql' === this.client || 'postgres' === this.client){
45
+ datasourceProvider = 'postgresql';
46
+ }
47
+ if('mongodb' === this.client){
48
+ datasourceProvider = 'mongodb';
49
+ }
50
+ let connectionString = datasourceProvider + '://'
51
+ + this.config.user + ':' + this.config.password
52
+ + '@' + this.config.host + ':' + this.config.port + '/'
53
+ + this.config.database;
54
+ let schemaContent = `
55
+ generator client {
56
+ provider = "prisma-client-js"
57
+ output = "../node_modules/.prisma/client"
58
+ }
59
+
60
+ datasource db {
61
+ provider = "${datasourceProvider}"
62
+ url = "${connectionString}"
63
+ }
64
+ `;
65
+ FileHandler.writeFile(this.schemaFilePath, schemaContent);
66
+ Logger.info('Generated Prisma schema file at: '+this.schemaFilePath);
67
+ }
68
+
69
+ }
70
+
71
+ module.exports.PrismaSchemaGenerator = PrismaSchemaGenerator;
@@ -0,0 +1,86 @@
1
+ /**
2
+ *
3
+ * Reldens - TypeMapper
4
+ *
5
+ */
6
+
7
+ class TypeMapper
8
+ {
9
+
10
+ mapDbTypeToJsType(dbType)
11
+ {
12
+ let typeMap = {
13
+ 'int': 'number',
14
+ 'integer': 'number',
15
+ 'tinyint': 'number',
16
+ 'smallint': 'number',
17
+ 'mediumint': 'number',
18
+ 'bigint': 'number',
19
+ 'decimal': 'number',
20
+ 'float': 'number',
21
+ 'double': 'number',
22
+ 'varchar': 'string',
23
+ 'char': 'string',
24
+ 'text': 'string',
25
+ 'tinytext': 'string',
26
+ 'mediumtext': 'string',
27
+ 'longtext': 'string',
28
+ 'date': 'Date',
29
+ 'datetime': 'Date',
30
+ 'timestamp': 'Date',
31
+ 'time': 'string',
32
+ 'year': 'number',
33
+ 'boolean': 'boolean',
34
+ 'bool': 'boolean',
35
+ 'bit': 'boolean',
36
+ 'json': 'object',
37
+ 'binary': 'Buffer',
38
+ 'varbinary': 'Buffer',
39
+ 'blob': 'Buffer',
40
+ 'tinyblob': 'Buffer',
41
+ 'mediumblob': 'Buffer',
42
+ 'longblob': 'Buffer'
43
+ };
44
+ return typeMap[dbType.toLowerCase()];
45
+ }
46
+
47
+ mapDbTypeToPrismaType(dbType)
48
+ {
49
+ let typeMap = {
50
+ 'int': 'Int',
51
+ 'integer': 'Int',
52
+ 'tinyint': 'Int',
53
+ 'smallint': 'Int',
54
+ 'mediumint': 'Int',
55
+ 'bigint': 'BigInt',
56
+ 'decimal': 'Decimal',
57
+ 'float': 'Float',
58
+ 'double': 'Float',
59
+ 'varchar': 'String',
60
+ 'char': 'String',
61
+ 'text': 'String',
62
+ 'tinytext': 'String',
63
+ 'mediumtext': 'String',
64
+ 'longtext': 'String',
65
+ 'date': 'DateTime',
66
+ 'datetime': 'DateTime',
67
+ 'timestamp': 'DateTime',
68
+ 'time': 'String',
69
+ 'year': 'Int',
70
+ 'boolean': 'Boolean',
71
+ 'bool': 'Boolean',
72
+ 'bit': 'Boolean',
73
+ 'json': 'Json',
74
+ 'binary': 'Bytes',
75
+ 'varbinary': 'Bytes',
76
+ 'blob': 'Bytes',
77
+ 'tinyblob': 'Bytes',
78
+ 'mediumblob': 'Bytes',
79
+ 'longblob': 'Bytes'
80
+ };
81
+ return typeMap[dbType.toLowerCase()] || 'String';
82
+ }
83
+
84
+ }
85
+
86
+ module.exports.TypeMapper = new TypeMapper();