@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.
- package/lib/entities-generator.js +1 -1
- package/lib/entity-templates/objection-js-model.template +1 -1
- package/lib/generators/models-generation.js +26 -1
- package/lib/prisma/prisma-data-server.js +1 -1
- package/lib/prisma/prisma-driver.js +187 -490
- package/lib/prisma/prisma-metadata-loader.js +126 -0
- package/lib/prisma/prisma-relation-resolver.js +267 -0
- package/lib/prisma/prisma-schema-generator.js +182 -182
- package/lib/prisma/prisma-type-caster.js +231 -0
- package/package.json +1 -1
|
@@ -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;
|