@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 +26 -0
- package/package.json +8 -5
- package/src/commands/build.js +59 -18
- package/src/generators/relationshipsGenerator.js +3 -3
- package/src/parsers/datasourceParser.js +55 -16
- package/src/parsers/prismaParser.js +18 -8
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.
|
|
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/
|
|
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": "^
|
|
61
|
+
"prisma": "^7.0.0"
|
|
59
62
|
},
|
|
60
63
|
"peerDependencies": {
|
|
61
|
-
"@prisma/client": "^
|
|
64
|
+
"@prisma/client": "^7.0.0"
|
|
62
65
|
}
|
|
63
66
|
}
|
package/src/commands/build.js
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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(
|
|
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 (
|
|
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(
|
|
882
|
+
parsedData = await parsePrismaDMMF(schemaPath);
|
|
845
883
|
if (parsedData) {
|
|
846
|
-
console.log('Using Prisma
|
|
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
|
-
|
|
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,
|
|
963
|
+
await updateRelationshipsForModel(filteredModels, relationshipsPath, schemaPath, usedDMMF);
|
|
923
964
|
} else {
|
|
924
965
|
// Generate all relationships
|
|
925
966
|
if (usedDMMF) {
|
|
926
|
-
await generateRelationshipsFromDMMF(
|
|
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}
|
|
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(
|
|
190
|
+
async function generateRelationshipsFromDMMF(schemaPath, outputPath) {
|
|
191
191
|
const { parsePrismaDMMF } = require('../parsers/prismaParser');
|
|
192
|
-
const parsedData = await parsePrismaDMMF(
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
48
|
-
|
|
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
|
|
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
|
-
* @
|
|
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(
|
|
212
|
+
async function parsePrismaDMMF(schemaPath) {
|
|
213
213
|
try {
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
const
|
|
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
|
|
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
|
}
|