@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.
@@ -0,0 +1,146 @@
1
+ /**
2
+ *
3
+ * Reldens - PrismaFilterProcessor
4
+ *
5
+ */
6
+
7
+ const { sc } = require('@reldens/utils');
8
+
9
+ class PrismaFilterProcessor
10
+ {
11
+
12
+ constructor(props)
13
+ {
14
+ this.typeCaster = sc.get(props, 'typeCaster', null);
15
+ this.fieldTypes = sc.get(props, 'fieldTypes', {});
16
+ this.referenceFields = sc.get(props, 'referenceFields', {});
17
+ this.jsonFields = sc.get(props, 'jsonFields', new Set());
18
+ }
19
+
20
+ updateMetadata(metadata)
21
+ {
22
+ this.fieldTypes = sc.get(metadata, 'fieldTypes', this.fieldTypes);
23
+ this.referenceFields = sc.get(metadata, 'referenceFields', this.referenceFields);
24
+ this.jsonFields = sc.get(metadata, 'jsonFields', this.jsonFields);
25
+ }
26
+
27
+ processFilters(filters)
28
+ {
29
+ if(!sc.isObject(filters)){
30
+ return filters;
31
+ }
32
+ let processedFilters = {};
33
+ for(let key of Object.keys(filters)){
34
+ let value = filters[key];
35
+ if('OR' === key && sc.isArray(value)){
36
+ processedFilters.OR = value.map(condition => this.processFilters(condition));
37
+ continue;
38
+ }
39
+ if(sc.hasOwn(value, 'operator') && sc.hasOwn(value, 'value')){
40
+ let operatorValue = this.processFilterValue(value.value, key);
41
+ processedFilters[key] = this.applyOperator(operatorValue, value.operator, key);
42
+ continue;
43
+ }
44
+ if(sc.isObject(value) && !sc.isArray(value)){
45
+ processedFilters[key] = this.processFilters(value);
46
+ continue;
47
+ }
48
+ processedFilters[key] = this.processFilterValue(value, key);
49
+ }
50
+ return processedFilters;
51
+ }
52
+
53
+ processFilterValue(value, key)
54
+ {
55
+ if(sc.isArray(value)){
56
+ if('id' === key){
57
+ return value.map(id => this.typeCaster.castToIdType(id)).filter(id => null !== id);
58
+ }
59
+ let fieldType = this.fieldTypes[key];
60
+ if(fieldType){
61
+ return value.map(val => this.typeCaster.castValue(val, fieldType, key))
62
+ .filter(val => this.typeCaster.isValidCastedValue(val));
63
+ }
64
+ return value;
65
+ }
66
+ if('id' === key){
67
+ return this.typeCaster.castToIdType(value);
68
+ }
69
+ let fieldType = this.fieldTypes[key];
70
+ if(!fieldType){
71
+ return value;
72
+ }
73
+ let castedValue = this.typeCaster.castValue(value, fieldType, key);
74
+ if(this.typeCaster.isValidCastedValue(castedValue)){
75
+ return castedValue;
76
+ }
77
+ return value;
78
+ }
79
+
80
+ applyOperator(value, operator, fieldName = null)
81
+ {
82
+ let upperOperator = operator ? operator.toUpperCase() : '';
83
+ if('LIKE' === upperOperator){
84
+ return this.handleLikeOperator(value, fieldName);
85
+ }
86
+ let operatorsMap = {
87
+ '=': value,
88
+ '!=': {not: value},
89
+ '>': {gt: value},
90
+ '>=': {gte: value},
91
+ '<': {lt: value},
92
+ '<=': {lte: value},
93
+ 'IN': {in: value},
94
+ 'NOT IN': {notIn: value}
95
+ };
96
+ return operatorsMap[upperOperator] || value;
97
+ }
98
+
99
+ handleLikeOperator(value, fieldName)
100
+ {
101
+ let cleanValue = String(value).replace(/%/g, '');
102
+ if(this.isJsonField(fieldName)){
103
+ return this.handleJsonTextSearch(cleanValue);
104
+ }
105
+ if('id' === fieldName || sc.hasOwn(this.referenceFields, fieldName)){
106
+ let numericValue = Number(cleanValue);
107
+ if(!isNaN(numericValue)){
108
+ return numericValue;
109
+ }
110
+ }
111
+ return {contains: cleanValue};
112
+ }
113
+
114
+ handleJsonTextSearch(searchValue)
115
+ {
116
+ return {
117
+ string_contains: searchValue
118
+ };
119
+ }
120
+
121
+ isJsonField(fieldName)
122
+ {
123
+ if(!this.jsonFields){
124
+ return false;
125
+ }
126
+ if(this.jsonFields.has(fieldName)){
127
+ return true;
128
+ }
129
+ return this.typeCaster && this.typeCaster.isJsonFieldByName(fieldName);
130
+ }
131
+
132
+ createSingleFilter(field, fieldValue, operator = null)
133
+ {
134
+ let filter = {};
135
+ let processedValue = this.processFilterValue(fieldValue, field);
136
+ if(null === operator){
137
+ filter[field] = processedValue;
138
+ return filter;
139
+ }
140
+ filter[field] = {operator, value: processedValue};
141
+ return this.processFilters(filter);
142
+ }
143
+
144
+ }
145
+
146
+ module.exports.PrismaFilterProcessor = PrismaFilterProcessor;
@@ -0,0 +1,133 @@
1
+ /**
2
+ *
3
+ * Reldens - PrismaMetadataLoader
4
+ *
5
+ */
6
+
7
+ const { Logger, sc } = require('@reldens/utils');
8
+ const { Prisma } = require('@prisma/client');
9
+
10
+ class PrismaMetadataLoader
11
+ {
12
+
13
+ constructor(props)
14
+ {
15
+ this.prisma = sc.get(props, 'prisma', null);
16
+ this.rawModel = sc.get(props, 'rawModel', null);
17
+ }
18
+
19
+ getDmmf()
20
+ {
21
+ try {
22
+ if(this.prisma._dmmf){
23
+ return this.prisma._dmmf.datamodel;
24
+ }
25
+ if(this.prisma._getDmmf && sc.isFunction(this.prisma._getDmmf)){
26
+ return this.prisma._getDmmf().datamodel;
27
+ }
28
+ if(this.prisma._runtimeDataModel){
29
+ return this.prisma._runtimeDataModel;
30
+ }
31
+ if(Prisma.dmmf && Prisma.dmmf.datamodel){
32
+ return Prisma.dmmf.datamodel;
33
+ }
34
+ return null;
35
+ } catch(error) {
36
+ Logger.warning('Failed to access DMMF: '+error.message);
37
+ return null;
38
+ }
39
+ }
40
+
41
+ loadModelMetadata(tableName)
42
+ {
43
+ let dmmf = this.getDmmf();
44
+ if(!dmmf){
45
+ Logger.warning('Could not access Prisma DMMF metadata');
46
+ return null;
47
+ }
48
+ let modelName = tableName.toLowerCase();
49
+ let modelInfo = dmmf.models?.[modelName] || dmmf.models?.find(m => m.name.toLowerCase() === modelName);
50
+ if(!modelInfo){
51
+ Logger.warning('Could not find model info for: '+modelName);
52
+ return null;
53
+ }
54
+ let metadata = {
55
+ requiredFields: [],
56
+ optionalFields: [],
57
+ fieldTypes: {},
58
+ fieldDefaults: {},
59
+ idFieldType: 'String',
60
+ idFieldName: 'id',
61
+ referenceFields: {},
62
+ relationMetadata: {},
63
+ foreignKeyMappings: {},
64
+ foreignKeyTargetFields: {},
65
+ jsonFields: new Set()
66
+ };
67
+ for(let field of modelInfo.fields){
68
+ this.processFieldMetadata(field, metadata);
69
+ }
70
+ if(this.rawModel && this.rawModel.relationTypes){
71
+ for(let relationName of Object.keys(this.rawModel.relationTypes)){
72
+ if(sc.hasOwn(metadata.relationMetadata, relationName)){
73
+ metadata.relationMetadata[relationName].type = this.rawModel.relationTypes[relationName];
74
+ }
75
+ }
76
+ }
77
+ return metadata;
78
+ }
79
+
80
+ processFieldMetadata(field, metadata)
81
+ {
82
+ metadata.fieldTypes[field.name] = field.type;
83
+ if('Json' === field.type){
84
+ metadata.jsonFields.add(field.name);
85
+ }
86
+ if(field.isId){
87
+ metadata.idFieldType = field.type;
88
+ metadata.idFieldName = field.name;
89
+ }
90
+ if(field.hasDefaultValue){
91
+ metadata.fieldDefaults[field.name] = field.default;
92
+ }
93
+ if(field.isRequired && !field.hasDefaultValue && !field.isId && 'object' !== field.kind){
94
+ metadata.requiredFields.push(field.name);
95
+ }
96
+ if(field.isOptional || field.hasDefaultValue){
97
+ metadata.optionalFields.push(field.name);
98
+ }
99
+ if(field.kind === 'scalar' && field.isList === false){
100
+ let isNumericType = this.isNumericFieldType(field.type);
101
+ let isReferenceField = field.relationFromFields && 0 < field.relationFromFields.length;
102
+ if(isNumericType || isReferenceField){
103
+ metadata.referenceFields[field.name] = field.type;
104
+ }
105
+ }
106
+ if('object' === field.kind){
107
+ let relationType = field.isList ? 'many' : 'one';
108
+ metadata.relationMetadata[field.name] = {
109
+ type: relationType,
110
+ model: field.type,
111
+ isList: field.isList,
112
+ isOptional: field.isOptional
113
+ };
114
+ }
115
+ if('object' === field.kind && sc.hasOwn(field, 'relationFromFields') && sc.isArray(field.relationFromFields)){
116
+ let relationToFields = sc.get(field, 'relationToFields', []);
117
+ let index = 0;
118
+ for(let foreignKeyField of field.relationFromFields){
119
+ metadata.foreignKeyMappings[foreignKeyField] = field.name;
120
+ metadata.foreignKeyTargetFields[foreignKeyField] = relationToFields[index] || 'id';
121
+ index++;
122
+ }
123
+ }
124
+ }
125
+
126
+ isNumericFieldType(fieldType)
127
+ {
128
+ return ('Int' === fieldType || 'BigInt' === fieldType || 'Float' === fieldType || 'Decimal' === fieldType);
129
+ }
130
+
131
+ }
132
+
133
+ module.exports.PrismaMetadataLoader = PrismaMetadataLoader;
@@ -0,0 +1,42 @@
1
+ /**
2
+ *
3
+ * Reldens - PrismaQueryBuilder
4
+ *
5
+ */
6
+
7
+ const { sc } = require('@reldens/utils');
8
+
9
+ class PrismaQueryBuilder
10
+ {
11
+
12
+ buildQueryOptions(options, useLimit = true)
13
+ {
14
+ let select = sc.get(options, 'select', []);
15
+ let limit = sc.get(options, 'limit', 0);
16
+ let offset = sc.get(options, 'offset', 0);
17
+ let sortBy = sc.get(options, 'sortBy', false);
18
+ let sortDirection = sc.get(options, 'sortDirection', 'ASC');
19
+ let queryOptions = {};
20
+ if(0 < select.length){
21
+ queryOptions.select = {};
22
+ for(let field of select){
23
+ queryOptions.select[field] = true;
24
+ }
25
+ }
26
+ if(useLimit && 0 !== limit){
27
+ queryOptions.take = limit;
28
+ }
29
+ if(0 !== offset){
30
+ queryOptions.skip = offset;
31
+ }
32
+ if(false !== sortBy && false !== sortDirection){
33
+ queryOptions.orderBy = {
34
+ [sortBy]: sortDirection.toLowerCase()
35
+ };
36
+ }
37
+ return queryOptions;
38
+ }
39
+
40
+ }
41
+
42
+ module.exports.PrismaQueryBuilder = PrismaQueryBuilder;
@@ -0,0 +1,267 @@
1
+ /**
2
+ *
3
+ * Reldens - PrismaRelationResolver
4
+ *
5
+ */
6
+
7
+ const { sc } = require('@reldens/utils');
8
+
9
+ class PrismaRelationResolver
10
+ {
11
+
12
+ constructor(props)
13
+ {
14
+ this.relationMetadata = sc.get(props, 'relationMetadata', {});
15
+ this.metadataLoader = sc.get(props, 'metadataLoader', null);
16
+ this.allRelationMappings = sc.get(props, 'allRelationMappings', {});
17
+ this.foreignKeyMappings = sc.get(props, 'foreignKeyMappings', {});
18
+ this.foreignKeyTargetFields = sc.get(props, 'foreignKeyTargetFields', {});
19
+ this.typeCaster = sc.get(props, 'typeCaster', null);
20
+ this.currentModelMappings = {};
21
+ this.reverseModelMappings = {};
22
+ }
23
+
24
+ updateMetadata(metadata)
25
+ {
26
+ this.relationMetadata = sc.get(metadata, 'relationMetadata', this.relationMetadata);
27
+ this.foreignKeyMappings = sc.get(metadata, 'foreignKeyMappings', this.foreignKeyMappings);
28
+ this.foreignKeyTargetFields = sc.get(metadata, 'foreignKeyTargetFields', this.foreignKeyTargetFields);
29
+ }
30
+
31
+ setCurrentModelMappings(mappings)
32
+ {
33
+ this.currentModelMappings = mappings || {};
34
+ this.reverseModelMappings = this.buildReverseMappings(this.currentModelMappings);
35
+ }
36
+
37
+ buildReverseMappings(mappings)
38
+ {
39
+ let reverse = {};
40
+ for(let reldensName of Object.keys(mappings)){
41
+ let prismaName = mappings[reldensName];
42
+ reverse[prismaName] = reldensName;
43
+ }
44
+ return reverse;
45
+ }
46
+
47
+ getAllRelations()
48
+ {
49
+ return Object.keys(this.relationMetadata || {});
50
+ }
51
+
52
+ mapToPrismaName(reldensName, modelName)
53
+ {
54
+ if(!modelName){
55
+ if(sc.hasOwn(this.relationMetadata, reldensName)){
56
+ return reldensName;
57
+ }
58
+ if(sc.hasOwn(this.currentModelMappings, reldensName)){
59
+ return this.currentModelMappings[reldensName];
60
+ }
61
+ return null;
62
+ }
63
+ let modelMappings = sc.get(this.allRelationMappings, modelName.toLowerCase(), {});
64
+ if(sc.hasOwn(modelMappings, reldensName)){
65
+ return modelMappings[reldensName];
66
+ }
67
+ return null;
68
+ }
69
+
70
+ mapToReldensName(prismaName)
71
+ {
72
+ if(sc.hasOwn(this.reverseModelMappings, prismaName)){
73
+ return this.reverseModelMappings[prismaName];
74
+ }
75
+ return prismaName;
76
+ }
77
+
78
+ getRelatedModelName(prismaName, fromModelName)
79
+ {
80
+ if(!fromModelName){
81
+ let meta = sc.get(this.relationMetadata, prismaName, null);
82
+ if(meta){
83
+ return meta.model;
84
+ }
85
+ return null;
86
+ }
87
+ if(!this.metadataLoader){
88
+ return null;
89
+ }
90
+ let dmmf = this.metadataLoader.getDmmf();
91
+ if(!dmmf){
92
+ return null;
93
+ }
94
+ let modelInfo = this.findModelInDmmf(dmmf, fromModelName);
95
+ if(!modelInfo){
96
+ return null;
97
+ }
98
+ for(let field of modelInfo.fields){
99
+ if(field.name === prismaName && 'object' === field.kind){
100
+ return field.type;
101
+ }
102
+ }
103
+ return null;
104
+ }
105
+
106
+ findModelInDmmf(dmmf, modelName)
107
+ {
108
+ let lowerName = modelName.toLowerCase();
109
+ if(sc.hasOwn(dmmf.models, lowerName)){
110
+ return dmmf.models[lowerName];
111
+ }
112
+ if(sc.isArray(dmmf.models)){
113
+ for(let model of dmmf.models){
114
+ if(model.name.toLowerCase() === lowerName){
115
+ return model;
116
+ }
117
+ }
118
+ }
119
+ return null;
120
+ }
121
+
122
+ convertForeignKeysToRelations(params, isCreate = false)
123
+ {
124
+ let data = {};
125
+ let relations = {};
126
+ for(let key of Object.keys(params)){
127
+ if(!sc.hasOwn(this.foreignKeyMappings, key)){
128
+ data[key] = params[key];
129
+ continue;
130
+ }
131
+ let relationName = this.foreignKeyMappings[key];
132
+ let value = params[key];
133
+ let targetField = sc.get(this.foreignKeyTargetFields, key, 'id');
134
+ if(!this.typeCaster){
135
+ relations[relationName] = {connect: {[targetField]: value}};
136
+ continue;
137
+ }
138
+ if(this.typeCaster.isNullOrEmpty(value)){
139
+ if(isCreate){
140
+ continue;
141
+ }
142
+ relations[relationName] = {disconnect: true};
143
+ continue;
144
+ }
145
+ let fieldType = sc.get(this.typeCaster.fieldTypes, key, null);
146
+ let connectValue = fieldType
147
+ ? this.typeCaster.castValue(value, fieldType, key)
148
+ : this.typeCaster.castToIdType(value);
149
+ relations[relationName] = {
150
+ connect: {[targetField]: connectValue}
151
+ };
152
+ }
153
+ return {...data, ...relations};
154
+ }
155
+
156
+ buildIncludeObjectWithMapping(relations)
157
+ {
158
+ let include = {};
159
+ let mapping = {};
160
+ for(let relation of relations){
161
+ this.processRelationPath(relation, include, mapping);
162
+ }
163
+ return {include, mapping};
164
+ }
165
+
166
+ processRelationPath(relationPath, include, mapping)
167
+ {
168
+ let parts = relationPath.split('.');
169
+ let currentInclude = include;
170
+ let currentModelName = null;
171
+ for(let i = 0; i < parts.length; i++){
172
+ let reldensName = parts[i];
173
+ let prismaName = this.mapToPrismaName(reldensName, currentModelName);
174
+ if(!prismaName){
175
+ return;
176
+ }
177
+ if(reldensName !== prismaName){
178
+ mapping[prismaName] = reldensName;
179
+ }
180
+ let isLastPart = (i === parts.length - 1);
181
+ if(isLastPart){
182
+ currentInclude[prismaName] = true;
183
+ return;
184
+ }
185
+ if(!sc.hasOwn(currentInclude, prismaName) || true === currentInclude[prismaName]){
186
+ currentInclude[prismaName] = {include: {}};
187
+ }
188
+ currentModelName = this.getRelatedModelName(prismaName, currentModelName);
189
+ if(!currentModelName){
190
+ return;
191
+ }
192
+ currentInclude = currentInclude[prismaName].include;
193
+ }
194
+ }
195
+
196
+ transformRelationResults(result, includeConfig, relationMapping)
197
+ {
198
+ if(!result || !includeConfig){
199
+ return result;
200
+ }
201
+ if(sc.isArray(result)){
202
+ return result.map(item => this.transformSingleResult(item, includeConfig, relationMapping));
203
+ }
204
+ return this.transformSingleResult(result, includeConfig, relationMapping);
205
+ }
206
+
207
+ transformSingleResult(item, includeConfig, relationMapping)
208
+ {
209
+ if(!item || !sc.isObject(item)){
210
+ return item;
211
+ }
212
+ let transformed = {...item};
213
+ for(let prismaName of Object.keys(includeConfig)){
214
+ if(!sc.hasOwn(transformed, prismaName)){
215
+ continue;
216
+ }
217
+ let relationValue = transformed[prismaName];
218
+ let nestedConfig = includeConfig[prismaName];
219
+ if(sc.isObject(nestedConfig) && sc.hasOwn(nestedConfig, 'include')){
220
+ relationValue = this.transformRelationResults(relationValue, nestedConfig.include, relationMapping);
221
+ }
222
+ let reldensName = sc.get(relationMapping, prismaName, null);
223
+ if(!reldensName){
224
+ reldensName = this.mapToReldensName(prismaName);
225
+ }
226
+ transformed[reldensName] = relationValue;
227
+ if(reldensName !== prismaName){
228
+ delete transformed[prismaName];
229
+ }
230
+ }
231
+ return transformed;
232
+ }
233
+
234
+ buildCreateDataWithRelations(params, relations)
235
+ {
236
+ let createData = {};
237
+ for(let relation of relations){
238
+ let prismaName = this.mapToPrismaName(relation, null);
239
+ if(!prismaName){
240
+ continue;
241
+ }
242
+ if(!sc.hasOwn(params, relation)){
243
+ continue;
244
+ }
245
+ let relationData = params[relation];
246
+ if(!this.typeCaster){
247
+ createData[prismaName] = sc.isArray(relationData)
248
+ ? {connect: relationData.map(item => ({id: item.id}))}
249
+ : {connect: {id: relationData.id}};
250
+ continue;
251
+ }
252
+ if(sc.isArray(relationData)){
253
+ createData[prismaName] = {
254
+ connect: relationData.map(item => ({id: this.typeCaster.castToIdType(item.id)}))
255
+ };
256
+ continue;
257
+ }
258
+ createData[prismaName] = {
259
+ connect: {id: this.typeCaster.castToIdType(relationData.id)}
260
+ };
261
+ }
262
+ return createData;
263
+ }
264
+
265
+ }
266
+
267
+ module.exports.PrismaRelationResolver = PrismaRelationResolver;