@rapidd/build 1.1.3 β†’ 1.2.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/README.md CHANGED
@@ -13,6 +13,11 @@ Dynamic code generator that transforms Prisma schemas into complete Express.js C
13
13
  - πŸ—ΊοΈ **Relationships JSON** - Generates complete relationship mappings with foreign keys
14
14
  - ⚑ **Selective Generation** - Update only specific models or components
15
15
 
16
+ ## Requirements
17
+
18
+ - **Prisma 7+** (recommended) - Full support for Prisma 7's new architecture
19
+ - Node.js 14.0.0 or higher
20
+
16
21
  ## Installation
17
22
 
18
23
  ```bash
@@ -155,6 +160,27 @@ npx rapidd build --model user --only model
155
160
  npx rapidd build --model user --only acl
156
161
  ```
157
162
 
163
+ ## Migration from Prisma 6 to 7
164
+
165
+ If you're upgrading from Prisma 6, this package now automatically:
166
+
167
+ 1. **Uses `@prisma/internals`** for DMMF access (no longer relies on generated client)
168
+ 2. **Reads database URL** from multiple sources in order:
169
+ - `prisma.config.ts` (Prisma 7 default)
170
+ - Schema file `datasource.url` (Prisma 5/6 style)
171
+ - `DATABASE_URL` environment variable
172
+ 3. **Maintains full compatibility** - no changes needed to your workflow
173
+
174
+ Simply update your dependencies and rebuild:
175
+
176
+ ```bash
177
+ npm install @rapidd/build@latest
178
+ npm install prisma@^7.0.0 @prisma/client@^7.0.0
179
+ npx rapidd build
180
+ ```
181
+
182
+ For more details on Prisma 7 migration, see the [official Prisma upgrade guide](https://www.prisma.io/docs/orm/more/upgrade-guides/upgrading-versions/upgrading-to-prisma-7).
183
+
158
184
  ## License
159
185
 
160
186
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rapidd/build",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "description": "Dynamic code generator that transforms Prisma schemas into Express.js CRUD APIs with PostgreSQL RLS-to-JavaScript translation",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -49,15 +49,18 @@
49
49
  "README.md"
50
50
  ],
51
51
  "dependencies": {
52
- "@prisma/client": "^5.0.0",
52
+ "@prisma/adapter-planetscale": "^7.0.1",
53
+ "@prisma/client": "^7.0.0",
54
+ "@prisma/internals": "^7.0.0",
53
55
  "commander": "^11.0.0",
54
56
  "dotenv": "^16.0.0",
55
- "pg": "^8.16.3"
57
+ "pg": "^8.16.3",
58
+ "undici": "^7.16.0"
56
59
  },
57
60
  "devDependencies": {
58
- "prisma": "^5.0.0"
61
+ "prisma": "^7.0.0"
59
62
  },
60
63
  "peerDependencies": {
61
- "@prisma/client": "^5.0.0"
64
+ "@prisma/client": "^7.0.0"
62
65
  }
63
66
  }
@@ -34,6 +34,10 @@ class Model {
34
34
  return Array.isArray(pkey) ? pkey.join('_') : pkey;
35
35
  }
36
36
 
37
+ get fields(){
38
+ return this.queryBuilder.fields;
39
+ }
40
+
37
41
  _select = (fields) => this.queryBuilder.select(fields);
38
42
  _filter = (q) => this.queryBuilder.filter(q);
39
43
  _include = (include) => this.queryBuilder.include(include, this.user);
@@ -54,6 +58,7 @@ class Model {
54
58
  * @param {number} offset
55
59
  * @param {string} sortBy
56
60
  * @param {'asc'|'desc'} sortOrder
61
+ * @param {{}} [options={}]
57
62
  * @returns {Promise<Object[]>}
58
63
  */
59
64
  _getMany = async (q = {}, include = "", limit = 25, offset = 0, sortBy = this.primaryKey, sortOrder = "asc", options = {})=>{
@@ -86,6 +91,7 @@ class Model {
86
91
  /**
87
92
  * @param {number} id
88
93
  * @param {string | Object} include
94
+ * @param {{}} [options={}]
89
95
  * @returns {Promise<{} | null>}
90
96
  */
91
97
  _get = async (id, include, options = {}) =>{
@@ -129,6 +135,7 @@ class Model {
129
135
  }
130
136
  /**
131
137
  * @param {{}} data
138
+ * @param {{}} [options={}]
132
139
  * @returns {Promise<Object>}
133
140
  */
134
141
  _create = async (data, options = {}) => {
@@ -151,6 +158,7 @@ class Model {
151
158
  /**
152
159
  * @param {number} id
153
160
  * @param {{}} data
161
+ * @param {{}} [options={}]
154
162
  * @returns {Promise<Object>}
155
163
  */
156
164
  _update = async (id, data, options = {}) => {
@@ -179,6 +187,28 @@ class Model {
179
187
  throw new ErrorResponse(403, "no_permission");
180
188
  }
181
189
 
190
+ /**
191
+ * @param {{}} data
192
+ * @param {string} [unique_key=this.primaryKey]
193
+ * @param {{}} [options={}]
194
+ * @returns {Promise<Object>}
195
+ */
196
+ async _upsert(data, unique_key = this.primaryKey, options = {}){
197
+ const createData = data;
198
+ const updateData = JSON.parse(JSON.stringify(data));
199
+ this.queryBuilder.create(createData, this.user);
200
+ this.queryBuilder.update(updateData, this.user);
201
+ return await this.prisma.upsert({
202
+ 'where': {
203
+ [unique_key]: data[unique_key]
204
+ },
205
+ 'create': createData,
206
+ 'update': updateData,
207
+ 'include': this.include('ALL'),
208
+ ...options
209
+ });
210
+ }
211
+
182
212
  /**
183
213
  *
184
214
  * @param {string} q
@@ -192,6 +222,7 @@ class Model {
192
222
 
193
223
  /**
194
224
  * @param {number} id
225
+ * @param {{}} [options={}]
195
226
  * @returns {Promise<Object>}
196
227
  */
197
228
  _delete = async (id, options = {}) => {
@@ -231,6 +262,7 @@ class Model {
231
262
  /**
232
263
  * @param {number} id
233
264
  * @param {string | Object} include
265
+ * @param {{}} [options={}]
234
266
  * @returns {Promise<{} | null>}
235
267
  */
236
268
  async get(id, include, options = {}){
@@ -240,12 +272,23 @@ class Model {
240
272
  /**
241
273
  * @param {number} id
242
274
  * @param {{}} data
275
+ * @param {{}} [options={}]
243
276
  * @returns {Promise<Object>}
244
277
  */
245
278
  async update(id, data, options = {}){
246
279
  return await this._update(id, data, options);
247
280
  }
248
281
 
282
+ /**
283
+ * @param {{}} data
284
+ * @param {string} [unique_key=this.primaryKey]
285
+ * @param {{}} [options={}]
286
+ * @returns {Promise<Object>}
287
+ */
288
+ async upsert(data, unique_key = this.primaryKey, options = {}){
289
+ return await this._upsert(data, unique_key, options);
290
+ }
291
+
249
292
  /**
250
293
  *
251
294
  * @param {string} q
@@ -334,15 +377,13 @@ class Model {
334
377
  set modelName (name){
335
378
  this.name = name;
336
379
  this.prisma = prisma[name];
337
- this.fields = this.prisma.fields;
338
380
  }
339
381
 
340
382
  static relatedObjects = [];
341
383
  static Error = ErrorResponse;
342
384
  }
343
385
 
344
- module.exports = {Model, QueryBuilder, prisma};
345
- `;
386
+ module.exports = {Model, QueryBuilder, prisma};`;
346
387
 
347
388
  // Ensure src directory exists
348
389
  const srcDir = path.dirname(modelJsPath);
@@ -506,12 +547,10 @@ const prisma = basePrisma.$extends({
506
547
  async function prismaTransaction(operations) {
507
548
  const context = requestContext.getStore();
508
549
 
509
- if (!context?.userId || !context?.userRole) {
510
- return Promise.all(operations);
511
- }
512
-
513
550
  return basePrisma.$transaction(async (tx) => {
514
- await setRLSVariables(tx, context.userId, context.userRole);
551
+ if (context?.userId && context?.userRole) {
552
+ await setRLSVariables(tx, context.userId, context.userRole);
553
+ }
515
554
  return Promise.all(operations.map(op => op(tx)));
516
555
  });
517
556
  }
@@ -623,7 +662,7 @@ module.exports = {
623
662
  /**
624
663
  * Update relationships.json for a specific model
625
664
  */
626
- async function updateRelationshipsForModel(filteredModels, relationshipsPath, prismaClientPath, schemaPath, usedDMMF) {
665
+ async function updateRelationshipsForModel(filteredModels, relationshipsPath, schemaPath, usedDMMF) {
627
666
  let existingRelationships = {};
628
667
 
629
668
  // Load existing relationships if file exists
@@ -641,7 +680,7 @@ async function updateRelationshipsForModel(filteredModels, relationshipsPath, pr
641
680
  // Use DMMF to get relationships for specific model
642
681
  const { generateRelationshipsFromDMMF } = require('../generators/relationshipsGenerator');
643
682
  const tempPath = relationshipsPath + '.tmp';
644
- await generateRelationshipsFromDMMF(prismaClientPath, tempPath);
683
+ await generateRelationshipsFromDMMF(schemaPath, tempPath);
645
684
  const allRelationships = JSON.parse(fs.readFileSync(tempPath, 'utf8'));
646
685
  fs.unlinkSync(tempPath);
647
686
 
@@ -835,15 +874,14 @@ async function buildModels(options) {
835
874
  console.warn('Continuing with schema parsing fallback...\n');
836
875
  }
837
876
 
838
- // Try to use Prisma DMMF first (if prisma generate has been run)
877
+ // Try to use Prisma DMMF first (using @prisma/internals getDMMF)
839
878
  let parsedData = null;
840
- const prismaClientPath = path.join(process.cwd(), 'prisma', 'client');
841
879
  let usedDMMF = false;
842
880
 
843
881
  try {
844
- parsedData = await parsePrismaDMMF(prismaClientPath);
882
+ parsedData = await parsePrismaDMMF(schemaPath);
845
883
  if (parsedData) {
846
- console.log('Using Prisma generated client (DMMF)');
884
+ console.log('Using Prisma DMMF (via @prisma/internals)');
847
885
  usedDMMF = true;
848
886
  }
849
887
  } catch (error) {
@@ -899,11 +937,14 @@ async function buildModels(options) {
899
937
  }
900
938
 
901
939
  // Parse datasource to determine database type
902
- let datasource = { isPostgreSQL: true }; // Default to PostgreSQL
940
+ let datasource = { isPostgreSQL: true, url: null }; // Default to PostgreSQL
903
941
  try {
904
942
  datasource = parseDatasource(schemaPath);
905
943
  } catch (error) {
906
- console.warn('Could not parse datasource, assuming PostgreSQL:', error.message);
944
+ // Only warn if it's not the expected "No url found" error in Prisma 7
945
+ if (!error.message.includes('No url found')) {
946
+ console.warn('Could not parse datasource, assuming PostgreSQL:', error.message);
947
+ }
907
948
  }
908
949
 
909
950
  // Generate rapidd/rapidd.js if it doesn't exist
@@ -919,11 +960,11 @@ async function buildModels(options) {
919
960
  try {
920
961
  if (options.model) {
921
962
  // Update only specific model in relationships.json
922
- await updateRelationshipsForModel(filteredModels, relationshipsPath, prismaClientPath, schemaPath, usedDMMF);
963
+ await updateRelationshipsForModel(filteredModels, relationshipsPath, schemaPath, usedDMMF);
923
964
  } else {
924
965
  // Generate all relationships
925
966
  if (usedDMMF) {
926
- await generateRelationshipsFromDMMF(prismaClientPath, relationshipsPath);
967
+ await generateRelationshipsFromDMMF(schemaPath, relationshipsPath);
927
968
  } else {
928
969
  generateRelationshipsFromSchema(schemaPath, relationshipsPath);
929
970
  }
@@ -184,12 +184,12 @@ function generateRelationshipsFromSchema(schemaPath, outputPath) {
184
184
 
185
185
  /**
186
186
  * Generate relationships.json from DMMF
187
- * @param {string} prismaClientPath - Path to Prisma client
187
+ * @param {string} schemaPath - Path to Prisma schema file
188
188
  * @param {string} outputPath - Path to output relationships.json
189
189
  */
190
- async function generateRelationshipsFromDMMF(prismaClientPath, outputPath) {
190
+ async function generateRelationshipsFromDMMF(schemaPath, outputPath) {
191
191
  const { parsePrismaDMMF } = require('../parsers/prismaParser');
192
- const parsedData = await parsePrismaDMMF(prismaClientPath);
192
+ const parsedData = await parsePrismaDMMF(schemaPath);
193
193
  generateRelationships(parsedData.models, outputPath);
194
194
  }
195
195
 
@@ -8,6 +8,39 @@ try {
8
8
  // dotenv not available, skip
9
9
  }
10
10
 
11
+ /**
12
+ * Try to load DATABASE_URL from prisma.config.ts (Prisma 7)
13
+ * @returns {string|null} - Database URL or null if not found
14
+ */
15
+ function loadUrlFromPrismaConfig() {
16
+ const configPath = path.join(process.cwd(), 'prisma.config.ts');
17
+
18
+ if (!fs.existsSync(configPath)) {
19
+ return null;
20
+ }
21
+
22
+ try {
23
+ const configContent = fs.readFileSync(configPath, 'utf-8');
24
+
25
+ // Look for env('DATABASE_URL') or similar patterns
26
+ const envMatch = configContent.match(/env\(['"]([^'"]+)['"]\)/);
27
+ if (envMatch) {
28
+ const envVar = envMatch[1];
29
+ return process.env[envVar] || null;
30
+ }
31
+
32
+ // Look for direct URL assignment
33
+ const urlMatch = configContent.match(/url:\s*['"]([^'"]+)['"]/);
34
+ if (urlMatch) {
35
+ return urlMatch[1];
36
+ }
37
+ } catch (e) {
38
+ // Failed to read config, return null
39
+ }
40
+
41
+ return null;
42
+ }
43
+
11
44
  /**
12
45
  * Parse datasource configuration from Prisma schema
13
46
  * @param {string} schemaPath - Path to Prisma schema file
@@ -30,26 +63,32 @@ function parseDatasource(schemaPath) {
30
63
  const providerMatch = datasourceBlock.match(/provider\s*=\s*"([^"]+)"/);
31
64
  const provider = providerMatch ? providerMatch[1] : null;
32
65
 
33
- // Extract url
66
+ // Try to extract url from schema first
67
+ let url = null;
34
68
  const urlMatch = datasourceBlock.match(/url\s*=\s*(.+)/);
35
- if (!urlMatch) {
36
- throw new Error('No url found in datasource block');
37
- }
38
69
 
39
- let url = urlMatch[1].trim();
70
+ if (urlMatch) {
71
+ url = urlMatch[1].trim();
72
+
73
+ // Handle env() function
74
+ const envMatch = url.match(/env\(["']([^"']+)["']\)/);
75
+ if (envMatch) {
76
+ const envVar = envMatch[1];
77
+ url = process.env[envVar];
78
+ } else {
79
+ // Remove quotes if present
80
+ url = url.replace(/^["']|["']$/g, '');
81
+ }
82
+ }
40
83
 
41
- // Handle env() function
42
- const envMatch = url.match(/env\(["']([^"']+)["']\)/);
43
- if (envMatch) {
44
- const envVar = envMatch[1];
45
- url = process.env[envVar];
84
+ // If no URL in schema, try prisma.config.ts (Prisma 7)
85
+ if (!url) {
86
+ url = loadUrlFromPrismaConfig();
87
+ }
46
88
 
47
- if (!url) {
48
- throw new Error(`Environment variable ${envVar} is not defined`);
49
- }
50
- } else {
51
- // Remove quotes if present
52
- url = url.replace(/^["']|["']$/g, '');
89
+ // If still no URL, check DATABASE_URL environment variable directly
90
+ if (!url) {
91
+ url = process.env.DATABASE_URL || null;
53
92
  }
54
93
 
55
94
  // Detect PostgreSQL from provider OR from the actual connection URL
@@ -1,5 +1,4 @@
1
1
  const fs = require('fs');
2
- const path = require('path');
3
2
 
4
3
  /**
5
4
  * Extract blocks (model or enum) with proper brace matching
@@ -204,16 +203,26 @@ function parseEnumValues(enumBody) {
204
203
  }
205
204
 
206
205
  /**
207
- * Use Prisma's generated DMMF (Data Model Meta Format) to get model information
206
+ * Use Prisma's DMMF (Data Model Meta Format) to get model information
208
207
  * This is an alternative approach that uses Prisma's own abstraction
209
- * @param {string} prismaClientPath - Path to generated Prisma Client
208
+ * In Prisma 7, we use getDMMF from @prisma/internals instead of accessing it from the client
209
+ * @param {string} schemaPath - Path to Prisma schema file
210
210
  * @returns {Object} - Models extracted from DMMF
211
211
  */
212
- async function parsePrismaDMMF(prismaClientPath) {
212
+ async function parsePrismaDMMF(schemaPath) {
213
213
  try {
214
- // Try to load the generated Prisma Client
215
- const prismaClient = require(prismaClientPath);
216
- const dmmf = prismaClient.Prisma.dmmf;
214
+ // In Prisma 7, DMMF is no longer exposed on the client
215
+ // We need to use getDMMF from @prisma/internals instead
216
+ const { getDMMF } = require('@prisma/internals');
217
+ const fs = require('fs');
218
+
219
+ // Read the schema file
220
+ const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
221
+
222
+ // Get DMMF from the schema
223
+ const dmmf = await getDMMF({
224
+ datamodel: schemaContent
225
+ });
217
226
 
218
227
  const models = {};
219
228
 
@@ -266,7 +275,8 @@ async function parsePrismaDMMF(prismaClientPath) {
266
275
 
267
276
  return { models, enums: dmmf.datamodel.enums };
268
277
  } catch (error) {
269
- console.warn('Could not load Prisma Client DMMF, falling back to schema parsing');
278
+ console.warn('Could not use getDMMF from @prisma/internals, falling back to schema parsing');
279
+ console.warn('Error:', error.message);
270
280
  return null;
271
281
  }
272
282
  }