@reldens/storage 0.79.0 → 0.81.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,126 @@
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
+ referenceFields: {},
61
+ relationMetadata: {},
62
+ foreignKeyMappings: {},
63
+ jsonFields: new Set()
64
+ };
65
+ for(let field of modelInfo.fields){
66
+ this.processFieldMetadata(field, metadata);
67
+ }
68
+ if(this.rawModel && this.rawModel.relationTypes){
69
+ for(let relationName of Object.keys(this.rawModel.relationTypes)){
70
+ if(sc.hasOwn(metadata.relationMetadata, relationName)){
71
+ metadata.relationMetadata[relationName].type = this.rawModel.relationTypes[relationName];
72
+ }
73
+ }
74
+ }
75
+ return metadata;
76
+ }
77
+
78
+ processFieldMetadata(field, metadata)
79
+ {
80
+ metadata.fieldTypes[field.name] = field.type;
81
+ if('Json' === field.type){
82
+ metadata.jsonFields.add(field.name);
83
+ }
84
+ if(field.isId){
85
+ metadata.idFieldType = field.type;
86
+ }
87
+ if(field.hasDefaultValue){
88
+ metadata.fieldDefaults[field.name] = field.default;
89
+ }
90
+ if(field.isRequired && !field.hasDefaultValue && !field.isId && 'object' !== field.kind){
91
+ metadata.requiredFields.push(field.name);
92
+ }
93
+ if(field.isOptional || field.hasDefaultValue){
94
+ metadata.optionalFields.push(field.name);
95
+ }
96
+ if(field.kind === 'scalar' && field.isList === false){
97
+ let isNumericType = this.isNumericFieldType(field.type);
98
+ let isReferenceField = field.relationFromFields && 0 < field.relationFromFields.length;
99
+ if(isNumericType || isReferenceField){
100
+ metadata.referenceFields[field.name] = field.type;
101
+ }
102
+ }
103
+ if('object' === field.kind){
104
+ let relationType = field.isList ? 'many' : 'one';
105
+ metadata.relationMetadata[field.name] = {
106
+ type: relationType,
107
+ model: field.type,
108
+ isList: field.isList,
109
+ isOptional: field.isOptional
110
+ };
111
+ }
112
+ if('object' === field.kind && sc.hasOwn(field, 'relationFromFields') && sc.isArray(field.relationFromFields)){
113
+ for(let foreignKeyField of field.relationFromFields){
114
+ metadata.foreignKeyMappings[foreignKeyField] = field.name;
115
+ }
116
+ }
117
+ }
118
+
119
+ isNumericFieldType(fieldType)
120
+ {
121
+ return ('Int' === fieldType || 'BigInt' === fieldType || 'Float' === fieldType || 'Decimal' === fieldType);
122
+ }
123
+
124
+ }
125
+
126
+ module.exports.PrismaMetadataLoader = PrismaMetadataLoader;
@@ -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.relationAliases = sc.get(props, 'relationAliases', {});
16
+ this.metadataLoader = sc.get(props, 'metadataLoader', null);
17
+ }
18
+
19
+ updateMetadata(metadata)
20
+ {
21
+ this.relationMetadata = sc.get(metadata, 'relationMetadata', this.relationMetadata);
22
+ this.relationAliases = sc.get(metadata, 'relationAliases', this.relationAliases);
23
+ }
24
+
25
+ buildRelationAliases(tableName)
26
+ {
27
+ let normalizedTableName = tableName.toLowerCase();
28
+ for(let prismaRelation of Object.keys(this.relationMetadata)){
29
+ let meta = this.relationMetadata[prismaRelation];
30
+ let relatedModel = meta.model.toLowerCase();
31
+ let aliases = this.generateRelationAliases(prismaRelation, relatedModel, normalizedTableName);
32
+ for(let alias of aliases){
33
+ this.relationAliases[alias] = prismaRelation;
34
+ }
35
+ }
36
+ return this.relationAliases;
37
+ }
38
+
39
+ generateRelationAliases(prismaRelation, relatedModel, tableName)
40
+ {
41
+ let aliases = [];
42
+ aliases.push('related_'+relatedModel);
43
+ aliases.push(relatedModel);
44
+ let fkHint = this.extractForeignKeyHint(prismaRelation, relatedModel, tableName);
45
+ if(fkHint){
46
+ aliases.push('related_'+relatedModel+'_'+fkHint);
47
+ aliases.push(relatedModel+'_'+fkHint);
48
+ }
49
+ return aliases;
50
+ }
51
+
52
+ extractForeignKeyHint(prismaRelation, relatedModel, tableName)
53
+ {
54
+ let toSuffix = 'To'+tableName;
55
+ if(!prismaRelation.toLowerCase().endsWith(toSuffix.toLowerCase())){
56
+ return null;
57
+ }
58
+ let withoutToSuffix = prismaRelation.slice(0, -toSuffix.length);
59
+ let prefix = relatedModel+'_';
60
+ if(withoutToSuffix.toLowerCase().startsWith(prefix.toLowerCase())){
61
+ withoutToSuffix = withoutToSuffix.slice(prefix.length);
62
+ if(withoutToSuffix.toLowerCase().startsWith(prefix.toLowerCase())){
63
+ withoutToSuffix = withoutToSuffix.slice(prefix.length);
64
+ }
65
+ }
66
+ if(withoutToSuffix.endsWith('_id')){
67
+ return withoutToSuffix.slice(0, -3);
68
+ }
69
+ return withoutToSuffix;
70
+ }
71
+
72
+ getAllRelations()
73
+ {
74
+ return Object.keys(this.relationMetadata || {});
75
+ }
76
+
77
+ normalizeRelationName(relationName)
78
+ {
79
+ if(sc.hasOwn(this.relationMetadata, relationName)){
80
+ return relationName;
81
+ }
82
+ if(sc.hasOwn(this.relationAliases, relationName)){
83
+ return this.relationAliases[relationName];
84
+ }
85
+ if(relationName.startsWith('related_')){
86
+ let withoutPrefix = relationName.substring(8);
87
+ if(sc.hasOwn(this.relationMetadata, withoutPrefix)){
88
+ return withoutPrefix;
89
+ }
90
+ if(sc.hasOwn(this.relationAliases, withoutPrefix)){
91
+ return this.relationAliases[withoutPrefix];
92
+ }
93
+ }
94
+ let availableRelations = Object.keys(this.relationMetadata);
95
+ for(let availableRelation of availableRelations){
96
+ if(relationName.endsWith(availableRelation)){
97
+ return availableRelation;
98
+ }
99
+ if(availableRelation.endsWith(relationName.replace('related_', ''))){
100
+ return availableRelation;
101
+ }
102
+ }
103
+ return relationName;
104
+ }
105
+
106
+ buildIncludeObjectWithMapping(relations)
107
+ {
108
+ let include = {};
109
+ let mapping = {};
110
+ for(let relation of relations){
111
+ if(-1 !== relation.indexOf('.')){
112
+ this.buildNestedInclude(relation, include, mapping);
113
+ continue;
114
+ }
115
+ let normalizedRelation = this.normalizeRelationName(relation);
116
+ if(!sc.hasOwn(this.relationMetadata, normalizedRelation)){
117
+ continue;
118
+ }
119
+ include[normalizedRelation] = true;
120
+ if(relation !== normalizedRelation){
121
+ mapping[normalizedRelation] = relation;
122
+ }
123
+ }
124
+ return {include, mapping};
125
+ }
126
+
127
+ buildNestedInclude(relationPath, include, mapping)
128
+ {
129
+ let parts = relationPath.split('.');
130
+ let rootPart = parts.shift();
131
+ let normalizedRoot = this.normalizeRelationName(rootPart);
132
+ if(!sc.hasOwn(this.relationMetadata, normalizedRoot)){
133
+ return;
134
+ }
135
+ if(!sc.hasOwn(include, normalizedRoot) || true === include[normalizedRoot]){
136
+ include[normalizedRoot] = {include: {}};
137
+ }
138
+ if(rootPart !== normalizedRoot){
139
+ mapping[normalizedRoot] = rootPart;
140
+ }
141
+ if(0 === parts.length){
142
+ return;
143
+ }
144
+ let nestedPath = parts.join('.');
145
+ let relatedModel = this.relationMetadata[normalizedRoot].model;
146
+ this.buildNestedIncludeForModel(nestedPath, include[normalizedRoot].include, mapping, relatedModel);
147
+ }
148
+
149
+ buildNestedIncludeForModel(relationPath, include, mapping, modelName)
150
+ {
151
+ if(!this.metadataLoader){
152
+ return;
153
+ }
154
+ let dmmf = this.metadataLoader.getDmmf();
155
+ if(!dmmf){
156
+ return;
157
+ }
158
+ let modelInfo = dmmf.models?.[modelName.toLowerCase()]
159
+ || dmmf.models?.find(m => m.name.toLowerCase() === modelName.toLowerCase());
160
+ if(!modelInfo){
161
+ return;
162
+ }
163
+ let relationFields = {};
164
+ let tableName = modelName.toLowerCase();
165
+ for(let field of modelInfo.fields){
166
+ if('object' === field.kind){
167
+ relationFields[field.name] = {
168
+ model: field.type,
169
+ isList: field.isList
170
+ };
171
+ }
172
+ }
173
+ let parts = relationPath.split('.');
174
+ let currentPart = parts.shift();
175
+ let normalizedPart = this.normalizeNestedRelation(currentPart, relationFields, tableName);
176
+ if(!normalizedPart){
177
+ return;
178
+ }
179
+ if(0 === parts.length){
180
+ include[normalizedPart] = true;
181
+ if(currentPart !== normalizedPart){
182
+ mapping[normalizedPart] = currentPart;
183
+ }
184
+ return;
185
+ }
186
+ if(!sc.hasOwn(include, normalizedPart) || true === include[normalizedPart]){
187
+ include[normalizedPart] = {include: {}};
188
+ }
189
+ if(currentPart !== normalizedPart){
190
+ mapping[normalizedPart] = currentPart;
191
+ }
192
+ let nestedPath = parts.join('.');
193
+ let nestedModel = relationFields[normalizedPart].model;
194
+ this.buildNestedIncludeForModel(nestedPath, include[normalizedPart].include, mapping, nestedModel);
195
+ }
196
+
197
+ normalizeNestedRelation(relationName, relationFields, tableName)
198
+ {
199
+ if(sc.hasOwn(relationFields, relationName)){
200
+ return relationName;
201
+ }
202
+ let cleanName = relationName.replace('related_', '');
203
+ if(sc.hasOwn(relationFields, cleanName)){
204
+ return cleanName;
205
+ }
206
+ for(let fieldName of Object.keys(relationFields)){
207
+ let relatedModel = relationFields[fieldName].model.toLowerCase();
208
+ let fkHint = this.extractForeignKeyHint(fieldName, relatedModel, tableName);
209
+ if(fkHint){
210
+ let possibleNames = [
211
+ 'related_'+relatedModel+'_'+fkHint,
212
+ relatedModel+'_'+fkHint,
213
+ 'related_'+relatedModel,
214
+ relatedModel
215
+ ];
216
+ if(-1 !== possibleNames.indexOf(cleanName) || -1 !== possibleNames.indexOf(relationName)){
217
+ return fieldName;
218
+ }
219
+ }
220
+ }
221
+ return null;
222
+ }
223
+
224
+ transformRelationResults(result, includeConfig, relationMapping)
225
+ {
226
+ if(!result || !includeConfig){
227
+ return result;
228
+ }
229
+ if(sc.isArray(result)){
230
+ return result.map(item => this.transformSingleResult(item, includeConfig, relationMapping));
231
+ }
232
+ return this.transformSingleResult(result, includeConfig, relationMapping);
233
+ }
234
+
235
+ transformSingleResult(item, includeConfig, relationMapping)
236
+ {
237
+ if(!item || !sc.isObject(item)){
238
+ return item;
239
+ }
240
+ let transformed = {...item};
241
+ for(let relationName of Object.keys(includeConfig)){
242
+ if(!sc.hasOwn(transformed, relationName)){
243
+ continue;
244
+ }
245
+ let relationMeta = this.relationMetadata[relationName];
246
+ if(!relationMeta){
247
+ continue;
248
+ }
249
+ let relationValue = transformed[relationName];
250
+ if('one' === relationMeta.type && sc.isArray(relationValue)){
251
+ relationValue = 0 < relationValue.length ? [...relationValue].shift() : null;
252
+ }
253
+ if('many' === relationMeta.type && !sc.isArray(relationValue)){
254
+ relationValue = relationValue ? [relationValue] : [];
255
+ }
256
+ let originalName = sc.get(relationMapping, relationName, relationName);
257
+ transformed[originalName] = relationValue;
258
+ if(originalName !== relationName){
259
+ delete transformed[relationName];
260
+ }
261
+ }
262
+ return transformed;
263
+ }
264
+
265
+ }
266
+
267
+ module.exports.PrismaRelationResolver = PrismaRelationResolver;