@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 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.DATABASE_URL } }`
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.extractPrismaRelationsMetadata();
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.DATABASE_URL);
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
- try {
21
- if(this.prisma._dmmf){
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.DATABASE_URL = this.buildConnectionString(datasourceProvider);
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 = 'module.exports = { datasource: { url: process.env.DATABASE_URL } };\n';
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reldens/storage",
3
3
  "scope": "@reldens",
4
- "version": "0.103.0",
4
+ "version": "0.105.0",
5
5
  "description": "Reldens - Storage",
6
6
  "author": "Damian A. Pastorini",
7
7
  "license": "MIT",
@@ -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.DATABASE_URL;
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;