@reldens/storage 0.103.0 → 0.105.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/CLAUDE.md +1 -1
- package/lib/entities-generator.js +6 -120
- package/lib/prisma/prisma-client-loader.js +1 -1
- package/lib/prisma/prisma-metadata-loader.js +3 -14
- package/lib/prisma/prisma-relations-metadata-extractor.js +137 -0
- package/lib/prisma/prisma-schema-generator.js +3 -2
- package/package.json +1 -1
- package/tests/utils/test-helpers.js +1 -1
package/CLAUDE.md
CHANGED
|
@@ -150,7 +150,7 @@ npx reldens-storage-prisma --host=<host> --database=<db> --user=<user> --passwor
|
|
|
150
150
|
- **Type caster isolation**: PrismaTypeCaster never requires `@prisma/client` directly, receives `prismaDbNull` as prop
|
|
151
151
|
- **Prisma v7 breaking changes applied:**
|
|
152
152
|
- Requires `@prisma/adapter-mariadb` for MySQL connections (WASM engine mandates a driver adapter)
|
|
153
|
-
- `datasource` block in `schema.prisma` no longer accepts `url` - connection URL is provided via `prisma.config.js` generated at project root using `{ datasource: { url: process.env.
|
|
153
|
+
- `datasource` block in `schema.prisma` no longer accepts `url` - connection URL is provided via `prisma.config.js` generated at project root using `process.loadEnvFile('.env')` + `{ datasource: { url: process.env.RELDENS_DB_URL } }`
|
|
154
154
|
- `PrismaClient` constructor no longer accepts `datasources` or `datasourceUrl` - use `adapter: new PrismaMariaDb(connectionString)` instead
|
|
155
155
|
- `provider = "prisma-client-js"` is kept (deprecated but functional); switching to `prisma-client` would require additional adapter changes
|
|
156
156
|
- `_runtimeDataModel` in Prisma 7 is pruned: fields only contain `{ name, kind, type, relationName, dbName }` - `isId`, `isRequired`, `hasDefaultValue` are stripped. ID field detection falls back to `field.name === 'id' && field.kind === 'scalar'`
|
|
@@ -9,6 +9,7 @@ const { Logger, sc } = require('@reldens/utils');
|
|
|
9
9
|
const { ObjectionJsDataServer } = require('./objection-js/objection-js-data-server');
|
|
10
10
|
const { MikroOrmDataServer } = require('./mikro-orm/mikro-orm-data-server');
|
|
11
11
|
const { PrismaDataServer } = require('./prisma/prisma-data-server');
|
|
12
|
+
const { PrismaRelationsMetadataExtractor } = require('./prisma/prisma-relations-metadata-extractor');
|
|
12
13
|
const { EntitiesGeneration } = require('./generators/entities-generation');
|
|
13
14
|
const { ModelsGeneration } = require('./generators/models-generation');
|
|
14
15
|
const { EntitiesConfigGeneration } = require('./generators/entities-config-generation');
|
|
@@ -165,125 +166,6 @@ class EntitiesGenerator
|
|
|
165
166
|
return false;
|
|
166
167
|
}
|
|
167
168
|
|
|
168
|
-
extractPrismaRelationsMetadata()
|
|
169
|
-
{
|
|
170
|
-
if(!this.prismaClient){
|
|
171
|
-
Logger.warning('Missing PrismaClient.');
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
if(!this.server || !(this.server instanceof PrismaDataServer)){
|
|
175
|
-
Logger.warning('Server is not a PrismaDataServer instance.');
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
try {
|
|
179
|
-
let dmmf = this.prismaClient._runtimeDataModel || this.prismaClient._dmmf?.datamodel;
|
|
180
|
-
if(!dmmf || !dmmf.models){
|
|
181
|
-
Logger.warning('Could not extract Prisma DMMF data for relations metadata.');
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
if(sc.isArray(dmmf.models)){
|
|
185
|
-
this.processArrayModels(dmmf.models);
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
this.processObjectModels(dmmf.models);
|
|
189
|
-
} catch(error) {
|
|
190
|
-
Logger.warning('Failed to extract Prisma relations metadata: ' + error.message);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
processArrayModels(models)
|
|
195
|
-
{
|
|
196
|
-
for(let model of models){
|
|
197
|
-
if(!model){
|
|
198
|
-
Logger.error('Missing model.');
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
if(!model.name){
|
|
202
|
-
Logger.error('Missing model name.', model);
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
if(!sc.isArray(model.fields)){
|
|
206
|
-
Logger.error('Model fields not an array.', model);
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
this.processModelForRelations(model.name, model, models);
|
|
210
|
-
}
|
|
211
|
-
Logger.info('Extracted relations metadata for '+Object.keys(this.prismaRelationsMetadata).length+' models.');
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
processObjectModels(models)
|
|
215
|
-
{
|
|
216
|
-
for(let modelName of Object.keys(models)){
|
|
217
|
-
let model = models[modelName];
|
|
218
|
-
if(!model){
|
|
219
|
-
Logger.error('Missing model.');
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
if(!sc.isArray(model.fields)){
|
|
223
|
-
Logger.error('Model fields not an array.', model);
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
this.processModelForRelations(modelName, model, models);
|
|
227
|
-
}
|
|
228
|
-
Logger.info('Extracted relations metadata for '+Object.keys(this.prismaRelationsMetadata).length+' models.');
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
processModelForRelations(modelName, model, allModels)
|
|
232
|
-
{
|
|
233
|
-
let tableName = modelName.toLowerCase();
|
|
234
|
-
this.prismaRelationsMetadata[tableName] = {};
|
|
235
|
-
for(let field of model.fields){
|
|
236
|
-
if('object' !== field.kind){
|
|
237
|
-
continue;
|
|
238
|
-
}
|
|
239
|
-
let relationType = 'many';
|
|
240
|
-
if(!field.isList){
|
|
241
|
-
relationType = this.inferRelationType(field, model, allModels, modelName);
|
|
242
|
-
}
|
|
243
|
-
this.prismaRelationsMetadata[tableName][field.name] = {
|
|
244
|
-
type: relationType,
|
|
245
|
-
model: field.type,
|
|
246
|
-
isList: field.isList,
|
|
247
|
-
isOptional: field.isOptional,
|
|
248
|
-
relationName: field.relationName
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
inferRelationType(field, model, allModels, modelName)
|
|
254
|
-
{
|
|
255
|
-
if(field.isList){
|
|
256
|
-
return 'many';
|
|
257
|
-
}
|
|
258
|
-
let relatedModel = this.findRelatedModel(allModels, field.type);
|
|
259
|
-
if(!relatedModel){
|
|
260
|
-
return 'one';
|
|
261
|
-
}
|
|
262
|
-
let backReference = relatedModel.fields.find(f =>
|
|
263
|
-
'object' === f.kind &&
|
|
264
|
-
f.type === modelName &&
|
|
265
|
-
f.relationName === field.relationName
|
|
266
|
-
);
|
|
267
|
-
if(!backReference){
|
|
268
|
-
return 'one';
|
|
269
|
-
}
|
|
270
|
-
if(field.relationFromFields && 0 < field.relationFromFields.length){
|
|
271
|
-
return 'one';
|
|
272
|
-
}
|
|
273
|
-
if(backReference.relationFromFields && 0 < backReference.relationFromFields.length){
|
|
274
|
-
return 'one';
|
|
275
|
-
}
|
|
276
|
-
return 'one';
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
findRelatedModel(allModels, fieldType)
|
|
280
|
-
{
|
|
281
|
-
if(sc.isArray(allModels)){
|
|
282
|
-
return allModels.find(m => m.name === fieldType);
|
|
283
|
-
}
|
|
284
|
-
return allModels[fieldType];
|
|
285
|
-
}
|
|
286
|
-
|
|
287
169
|
filterTablesToGenerate(tables, driverKey)
|
|
288
170
|
{
|
|
289
171
|
let filteredTables = {};
|
|
@@ -386,7 +268,11 @@ class EntitiesGenerator
|
|
|
386
268
|
this.prismaClient = this.server.prisma;
|
|
387
269
|
}
|
|
388
270
|
//Logger.debug('Extract Prisma relations metadata.');
|
|
389
|
-
this.
|
|
271
|
+
this.prismaRelationsMetadata = new PrismaRelationsMetadataExtractor({
|
|
272
|
+
prismaClient: this.prismaClient,
|
|
273
|
+
projectRoot: this.server ? this.server.projectRoot : false,
|
|
274
|
+
projectPath: this.projectPath
|
|
275
|
+
}).extract();
|
|
390
276
|
}
|
|
391
277
|
let tables = await this.server.fetchEntitiesFromDatabase();
|
|
392
278
|
if(!tables){
|
|
@@ -29,7 +29,7 @@ class PrismaClientLoader
|
|
|
29
29
|
}
|
|
30
30
|
if(!connectionData){
|
|
31
31
|
Logger.info('Creating PrismaClient with default connection from schema');
|
|
32
|
-
return PrismaClientLoader.createWithAdapter(prismaModule.PrismaClient, process.env.
|
|
32
|
+
return PrismaClientLoader.createWithAdapter(prismaModule.PrismaClient, process.env.RELDENS_DB_URL);
|
|
33
33
|
}
|
|
34
34
|
let connectionString = connectionData.client+'://'
|
|
35
35
|
+connectionData.user
|
|
@@ -17,21 +17,10 @@ class PrismaMetadataLoader
|
|
|
17
17
|
|
|
18
18
|
getDmmf()
|
|
19
19
|
{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return this.prisma._dmmf.datamodel;
|
|
23
|
-
}
|
|
24
|
-
if(this.prisma._getDmmf && sc.isFunction(this.prisma._getDmmf)){
|
|
25
|
-
return this.prisma._getDmmf().datamodel;
|
|
26
|
-
}
|
|
27
|
-
if(this.prisma._runtimeDataModel){
|
|
28
|
-
return this.prisma._runtimeDataModel;
|
|
29
|
-
}
|
|
30
|
-
return null;
|
|
31
|
-
} catch(error) {
|
|
32
|
-
Logger.warning('Failed to access DMMF: '+error.message);
|
|
33
|
-
return null;
|
|
20
|
+
if(this.prisma && this.prisma._runtimeDataModel){
|
|
21
|
+
return this.prisma._runtimeDataModel;
|
|
34
22
|
}
|
|
23
|
+
return null;
|
|
35
24
|
}
|
|
36
25
|
|
|
37
26
|
loadModelMetadata(tableName)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - PrismaRelationsMetadataExtractor
|
|
4
|
+
*
|
|
5
|
+
* Builds the per-model relation metadata ({type: 'many'|'one', model, isList, isOptional, relationName})
|
|
6
|
+
* consumed by ModelsGeneration to emit each Prisma model's relationTypes. The DMMF access is reused from
|
|
7
|
+
* PrismaMetadataLoader.getDmmf(). Cardinality (list vs single) is read from schema.prisma's list markers
|
|
8
|
+
* ("[]") because Prisma 7's pruned _runtimeDataModel no longer exposes field.isList.
|
|
9
|
+
*
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { PrismaMetadataLoader } = require('./prisma-metadata-loader');
|
|
13
|
+
const { FileHandler } = require('@reldens/server-utils');
|
|
14
|
+
const { Logger, sc } = require('@reldens/utils');
|
|
15
|
+
|
|
16
|
+
class PrismaRelationsMetadataExtractor
|
|
17
|
+
{
|
|
18
|
+
|
|
19
|
+
constructor(props)
|
|
20
|
+
{
|
|
21
|
+
this.prismaClient = sc.get(props, 'prismaClient', false);
|
|
22
|
+
this.projectRoot = sc.get(props, 'projectRoot', false);
|
|
23
|
+
this.projectPath = sc.get(props, 'projectPath', false);
|
|
24
|
+
this.metadataLoader = new PrismaMetadataLoader({prisma: this.prismaClient});
|
|
25
|
+
this.relationsMetadata = {};
|
|
26
|
+
this.schemaListRelations = {};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
extract()
|
|
30
|
+
{
|
|
31
|
+
if(!this.prismaClient){
|
|
32
|
+
Logger.warning('Missing PrismaClient for relations metadata extraction.');
|
|
33
|
+
return this.relationsMetadata;
|
|
34
|
+
}
|
|
35
|
+
let dmmf = this.metadataLoader.getDmmf();
|
|
36
|
+
if(!dmmf || !dmmf.models){
|
|
37
|
+
Logger.warning('Could not access Prisma DMMF for relations metadata.');
|
|
38
|
+
return this.relationsMetadata;
|
|
39
|
+
}
|
|
40
|
+
this.schemaListRelations = this.resolveSchemaListRelations();
|
|
41
|
+
this.processModels(dmmf.models);
|
|
42
|
+
Logger.info('Extracted relations metadata for '+Object.keys(this.relationsMetadata).length+' models.');
|
|
43
|
+
return this.relationsMetadata;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
processModels(models)
|
|
47
|
+
{
|
|
48
|
+
if(sc.isArray(models)){
|
|
49
|
+
for(let model of models){
|
|
50
|
+
this.processModel(sc.get(model, 'name', ''), model);
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
for(let modelName of Object.keys(models)){
|
|
55
|
+
this.processModel(modelName, models[modelName]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
processModel(modelName, model)
|
|
60
|
+
{
|
|
61
|
+
if('' === modelName || !sc.isArray(sc.get(model, 'fields', false))){
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
let tableName = modelName.toLowerCase();
|
|
65
|
+
this.relationsMetadata[tableName] = {};
|
|
66
|
+
for(let field of model.fields){
|
|
67
|
+
if('object' !== field.kind){
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
let isListRelation = true === sc.get(sc.get(this.schemaListRelations, tableName, {}), field.name, false);
|
|
71
|
+
this.relationsMetadata[tableName][field.name] = {
|
|
72
|
+
type: isListRelation ? 'many' : 'one',
|
|
73
|
+
model: field.type,
|
|
74
|
+
isList: isListRelation,
|
|
75
|
+
isOptional: field.isOptional,
|
|
76
|
+
relationName: field.relationName
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
resolveSchemaPath()
|
|
82
|
+
{
|
|
83
|
+
let candidatePaths = [];
|
|
84
|
+
if(this.projectRoot){
|
|
85
|
+
candidatePaths.push(FileHandler.joinPaths(this.projectRoot, 'prisma', 'schema.prisma'));
|
|
86
|
+
}
|
|
87
|
+
candidatePaths.push(FileHandler.joinPaths(process.cwd(), 'prisma', 'schema.prisma'));
|
|
88
|
+
if(this.projectPath){
|
|
89
|
+
candidatePaths.push(FileHandler.joinPaths(this.projectPath, 'prisma', 'schema.prisma'));
|
|
90
|
+
}
|
|
91
|
+
for(let candidatePath of candidatePaths){
|
|
92
|
+
if(FileHandler.exists(candidatePath)){
|
|
93
|
+
return candidatePath;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
resolveSchemaListRelations()
|
|
100
|
+
{
|
|
101
|
+
let listRelations = {};
|
|
102
|
+
let schemaPath = this.resolveSchemaPath();
|
|
103
|
+
if(!schemaPath){
|
|
104
|
+
Logger.warning('Prisma schema.prisma not found; relation cardinality may fall back to one.');
|
|
105
|
+
return listRelations;
|
|
106
|
+
}
|
|
107
|
+
let schemaContent = FileHandler.readFile(schemaPath);
|
|
108
|
+
if(!schemaContent){
|
|
109
|
+
return listRelations;
|
|
110
|
+
}
|
|
111
|
+
let currentModel = '';
|
|
112
|
+
for(let schemaLine of schemaContent.toString().split('\n')){
|
|
113
|
+
let line = schemaLine.trim();
|
|
114
|
+
let modelMatch = line.match(/^model\s+(\w+)\s*\{/);
|
|
115
|
+
if(modelMatch){
|
|
116
|
+
currentModel = modelMatch[1].toLowerCase();
|
|
117
|
+
listRelations[currentModel] = {};
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if('' === currentModel){
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if('}' === line){
|
|
124
|
+
currentModel = '';
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
let listFieldMatch = line.match(/^(\w+)\s+\w+\[\]/);
|
|
128
|
+
if(listFieldMatch){
|
|
129
|
+
listRelations[currentModel][listFieldMatch[1]] = true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return listRelations;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports.PrismaRelationsMetadataExtractor = PrismaRelationsMetadataExtractor;
|
|
@@ -96,7 +96,7 @@ class PrismaSchemaGenerator
|
|
|
96
96
|
setDatabaseEnvironmentVariables()
|
|
97
97
|
{
|
|
98
98
|
let datasourceProvider = this.getDatasourceProvider();
|
|
99
|
-
process.env.
|
|
99
|
+
process.env.RELDENS_DB_URL = this.buildConnectionString(datasourceProvider);
|
|
100
100
|
if(this.dataProxy){
|
|
101
101
|
process.env.DATABASE_DIRECT_URL = this.buildDirectConnectionString(datasourceProvider);
|
|
102
102
|
}
|
|
@@ -139,7 +139,8 @@ class PrismaSchemaGenerator
|
|
|
139
139
|
|
|
140
140
|
generateConfigFile()
|
|
141
141
|
{
|
|
142
|
-
let configContent = '
|
|
142
|
+
let configContent = 'process.loadEnvFile(\'.env\');\n'
|
|
143
|
+
+'module.exports = { datasource: { url: process.env.RELDENS_DB_URL } };\n';
|
|
143
144
|
let configPath = FileHandler.joinPaths(process.cwd(), 'prisma.config.js');
|
|
144
145
|
FileHandler.writeFile(configPath, configContent);
|
|
145
146
|
Logger.info('Generated Prisma config file at: '+configPath);
|
package/package.json
CHANGED
|
@@ -387,7 +387,7 @@ class TestHelpers
|
|
|
387
387
|
let { PrismaMariaDb } = require('@prisma/adapter-mariadb');
|
|
388
388
|
let adapterConfig = config
|
|
389
389
|
? { host: config.host, port: config.port, user: config.user, password: config.password, database: config.database }
|
|
390
|
-
: process.env.
|
|
390
|
+
: process.env.RELDENS_DB_URL;
|
|
391
391
|
let client = new PrismaClient({ adapter: new PrismaMariaDb(adapterConfig) });
|
|
392
392
|
await client.$connect();
|
|
393
393
|
return client;
|