@rapidd/build 1.0.0 β 1.0.1
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 +90 -7
- package/bin/cli.js +2 -0
- package/package.json +4 -4
- package/src/commands/build.js +218 -46
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ Dynamic code generator that transforms Prisma schemas into complete Express.js C
|
|
|
11
11
|
- π₯ **Role-Based Access Control** - Properly handles role checks in filters
|
|
12
12
|
- π **Model Generation** - Creates CRUD model classes with capitalized filenames
|
|
13
13
|
- πΊοΈ **Relationships JSON** - Generates complete relationship mappings with foreign keys
|
|
14
|
+
- β‘ **Selective Generation** - Update only specific models or components
|
|
14
15
|
|
|
15
16
|
## Installation
|
|
16
17
|
|
|
@@ -21,16 +22,77 @@ npm install @rapidd/build
|
|
|
21
22
|
## Quick Start
|
|
22
23
|
|
|
23
24
|
```bash
|
|
24
|
-
# Generate in current directory (default)
|
|
25
|
+
# Generate everything in current directory (default)
|
|
25
26
|
npx rapidd build
|
|
26
27
|
|
|
27
28
|
# Generate in specific directory
|
|
28
29
|
npx rapidd build --output ./generated
|
|
29
30
|
|
|
31
|
+
# Generate only specific model
|
|
32
|
+
npx rapidd build --model user
|
|
33
|
+
|
|
34
|
+
# Generate only specific component
|
|
35
|
+
npx rapidd build --only model
|
|
36
|
+
npx rapidd build --only route
|
|
37
|
+
npx rapidd build --only rls
|
|
38
|
+
npx rapidd build --only relationship
|
|
39
|
+
|
|
40
|
+
# Combine model and component filters
|
|
41
|
+
npx rapidd build --model account --only route
|
|
42
|
+
|
|
30
43
|
# Specify custom user table
|
|
31
44
|
npx rapidd build --user-table accounts
|
|
32
45
|
```
|
|
33
46
|
|
|
47
|
+
## CLI Options
|
|
48
|
+
|
|
49
|
+
- `-o, --output <path>` - Output directory (default: `./`)
|
|
50
|
+
- `-s, --schema <path>` - Prisma schema file (default: `./prisma/schema.prisma`)
|
|
51
|
+
- `-m, --model <name>` - Generate/update only specific model (e.g., "account", "user")
|
|
52
|
+
- `--only <component>` - Generate only specific component: "model", "route", "rls", or "relationship"
|
|
53
|
+
- `--user-table <name>` - User table name for RLS (default: auto-detected)
|
|
54
|
+
|
|
55
|
+
## Selective Generation
|
|
56
|
+
|
|
57
|
+
### Update Single Model
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Update only the account model across all components
|
|
61
|
+
npx rapidd build --model account
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This will:
|
|
65
|
+
- Generate/update `src/Model/Account.js`
|
|
66
|
+
- Generate/update `routes/api/v1/account.js`
|
|
67
|
+
- Update the `account` entry in `rapidd/relationships.json`
|
|
68
|
+
- Update the `account` entry in `rapidd/rls.js`
|
|
69
|
+
|
|
70
|
+
### Update Single Component
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Regenerate all routes
|
|
74
|
+
npx rapidd build --only route
|
|
75
|
+
|
|
76
|
+
# Regenerate all RLS configs
|
|
77
|
+
npx rapidd build --only rls
|
|
78
|
+
|
|
79
|
+
# Regenerate all models
|
|
80
|
+
npx rapidd build --only model
|
|
81
|
+
|
|
82
|
+
# Regenerate relationships
|
|
83
|
+
npx rapidd build --only relationship
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Combine Filters
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# Update only the route for a specific model
|
|
90
|
+
npx rapidd build --model user --only route
|
|
91
|
+
|
|
92
|
+
# Update RLS for account model
|
|
93
|
+
npx rapidd build --model account --only rls
|
|
94
|
+
```
|
|
95
|
+
|
|
34
96
|
## Generated Structure
|
|
35
97
|
|
|
36
98
|
```
|
|
@@ -69,18 +131,39 @@ getAccessFilter: (user) => {
|
|
|
69
131
|
}
|
|
70
132
|
```
|
|
71
133
|
|
|
72
|
-
## CLI Options
|
|
73
|
-
|
|
74
|
-
- `-o, --output <path>` - Output directory (default: `./`)
|
|
75
|
-
- `-s, --schema <path>` - Prisma schema file (default: `./prisma/schema.prisma`)
|
|
76
|
-
- `-u, --user-table <name>` - User table name for RLS (default: auto-detected)
|
|
77
|
-
|
|
78
134
|
## Usage with PostgreSQL RLS
|
|
79
135
|
|
|
80
136
|
```bash
|
|
81
137
|
DATABASE_URL="postgresql://user:pass@localhost:5432/mydb" npx rapidd build
|
|
82
138
|
```
|
|
83
139
|
|
|
140
|
+
## Use Cases
|
|
141
|
+
|
|
142
|
+
### During Development
|
|
143
|
+
```bash
|
|
144
|
+
# After adding a new model to schema
|
|
145
|
+
npx rapidd build --model newModel
|
|
146
|
+
|
|
147
|
+
# After changing relationships
|
|
148
|
+
npx rapidd build --only relationship
|
|
149
|
+
|
|
150
|
+
# After updating RLS policies
|
|
151
|
+
npx rapidd build --only rls
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Continuous Integration
|
|
155
|
+
```bash
|
|
156
|
+
# Full rebuild for CI/CD
|
|
157
|
+
npx rapidd build --output ./generated
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Incremental Updates
|
|
161
|
+
```bash
|
|
162
|
+
# Update specific model after schema changes
|
|
163
|
+
npx rapidd build --model user --only model
|
|
164
|
+
npx rapidd build --model user --only rls
|
|
165
|
+
```
|
|
166
|
+
|
|
84
167
|
## License
|
|
85
168
|
|
|
86
169
|
MIT
|
package/bin/cli.js
CHANGED
|
@@ -16,6 +16,8 @@ program
|
|
|
16
16
|
.description('Build model files from Prisma schema')
|
|
17
17
|
.option('-s, --schema <path>', 'Path to Prisma schema file', process.env.PRISMA_SCHEMA_PATH || './prisma/schema.prisma')
|
|
18
18
|
.option('-o, --output <path>', 'Output base directory', './')
|
|
19
|
+
.option('-m, --model <name>', 'Generate/update only specific model (e.g., "account", "user")')
|
|
20
|
+
.option('--only <component>', 'Generate only specific component: "model", "route", "rls", or "relationship"')
|
|
19
21
|
.option('--user-table <name>', 'Name of the user table for RLS (default: auto-detect from user/users)')
|
|
20
22
|
.action(async (options) => {
|
|
21
23
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rapidd/build",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
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": {
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
},
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
15
|
-
"url": "https://github.com/rapidd
|
|
15
|
+
"url": "https://github.com/MertDalbudak/rapidd-build"
|
|
16
16
|
},
|
|
17
17
|
"bugs": {
|
|
18
|
-
"url": "https://github.com/rapidd
|
|
18
|
+
"url": "https://github.com/MertDalbudak/rapidd-build/issues"
|
|
19
19
|
},
|
|
20
|
-
"homepage": "https://github.com/rapidd
|
|
20
|
+
"homepage": "https://github.com/MertDalbudak/rapidd-build#readme",
|
|
21
21
|
"keywords": [
|
|
22
22
|
"rapidd",
|
|
23
23
|
"prisma",
|
package/src/commands/build.js
CHANGED
|
@@ -302,12 +302,131 @@ module.exports = {prisma, Prisma, rls};
|
|
|
302
302
|
console.log('β Generated rapidd/rapidd.js');
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
+
/**
|
|
306
|
+
* Update relationships.json for a specific model
|
|
307
|
+
*/
|
|
308
|
+
async function updateRelationshipsForModel(filteredModels, relationshipsPath, prismaClientPath, schemaPath, usedDMMF) {
|
|
309
|
+
let existingRelationships = {};
|
|
310
|
+
|
|
311
|
+
// Load existing relationships if file exists
|
|
312
|
+
if (fs.existsSync(relationshipsPath)) {
|
|
313
|
+
try {
|
|
314
|
+
existingRelationships = JSON.parse(fs.readFileSync(relationshipsPath, 'utf8'));
|
|
315
|
+
} catch (error) {
|
|
316
|
+
console.warn('Could not parse existing relationships.json, will create new');
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Generate relationships for the filtered model(s)
|
|
321
|
+
let newRelationships = {};
|
|
322
|
+
if (usedDMMF) {
|
|
323
|
+
// Use DMMF to get relationships for specific model
|
|
324
|
+
const { generateRelationshipsFromDMMF } = require('../generators/relationshipsGenerator');
|
|
325
|
+
const tempPath = relationshipsPath + '.tmp';
|
|
326
|
+
await generateRelationshipsFromDMMF(prismaClientPath, tempPath);
|
|
327
|
+
const allRelationships = JSON.parse(fs.readFileSync(tempPath, 'utf8'));
|
|
328
|
+
fs.unlinkSync(tempPath);
|
|
329
|
+
|
|
330
|
+
// Extract only the filtered model's relationships
|
|
331
|
+
for (const modelName of Object.keys(filteredModels)) {
|
|
332
|
+
if (allRelationships[modelName]) {
|
|
333
|
+
newRelationships[modelName] = allRelationships[modelName];
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
// Use schema parser
|
|
338
|
+
const { generateRelationshipsFromSchema } = require('../generators/relationshipsGenerator');
|
|
339
|
+
const tempPath = relationshipsPath + '.tmp';
|
|
340
|
+
generateRelationshipsFromSchema(schemaPath, tempPath);
|
|
341
|
+
const allRelationships = JSON.parse(fs.readFileSync(tempPath, 'utf8'));
|
|
342
|
+
fs.unlinkSync(tempPath);
|
|
343
|
+
|
|
344
|
+
// Extract only the filtered model's relationships
|
|
345
|
+
for (const modelName of Object.keys(filteredModels)) {
|
|
346
|
+
if (allRelationships[modelName]) {
|
|
347
|
+
newRelationships[modelName] = allRelationships[modelName];
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Merge with existing relationships
|
|
353
|
+
const updatedRelationships = { ...existingRelationships, ...newRelationships };
|
|
354
|
+
|
|
355
|
+
// Write back to file
|
|
356
|
+
fs.writeFileSync(relationshipsPath, JSON.stringify(updatedRelationships, null, 2));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Update rls.js for a specific model
|
|
361
|
+
*/
|
|
362
|
+
async function updateRLSForModel(filteredModels, rlsPath, datasource, userTable, relationships) {
|
|
363
|
+
const { generateRLS } = require('../generators/rlsGeneratorV2');
|
|
364
|
+
|
|
365
|
+
// Generate RLS for the filtered model
|
|
366
|
+
const tempPath = rlsPath + '.tmp';
|
|
367
|
+
await generateRLS(
|
|
368
|
+
filteredModels,
|
|
369
|
+
tempPath,
|
|
370
|
+
datasource.url,
|
|
371
|
+
datasource.isPostgreSQL,
|
|
372
|
+
userTable,
|
|
373
|
+
relationships
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
// Read the generated RLS for the specific model
|
|
377
|
+
const tempContent = fs.readFileSync(tempPath, 'utf8');
|
|
378
|
+
fs.unlinkSync(tempPath);
|
|
379
|
+
|
|
380
|
+
// Extract the model's RLS configuration
|
|
381
|
+
const modelName = Object.keys(filteredModels)[0];
|
|
382
|
+
const modelRlsMatch = tempContent.match(new RegExp(`${modelName}:\\s*\\{[\\s\\S]*?\\n \\}(?=,|\\n)`));
|
|
383
|
+
|
|
384
|
+
if (!modelRlsMatch) {
|
|
385
|
+
throw new Error(`Could not extract RLS for model ${modelName}`);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const modelRls = modelRlsMatch[0];
|
|
389
|
+
|
|
390
|
+
// Read existing rls.js
|
|
391
|
+
if (fs.existsSync(rlsPath)) {
|
|
392
|
+
let existingContent = fs.readFileSync(rlsPath, 'utf8');
|
|
393
|
+
|
|
394
|
+
// Check if model already exists in RLS
|
|
395
|
+
const existingModelPattern = new RegExp(`${modelName}:\\s*\\{[\\s\\S]*?\\n \\}(?=,|\\n)`);
|
|
396
|
+
|
|
397
|
+
if (existingModelPattern.test(existingContent)) {
|
|
398
|
+
// Replace existing model RLS
|
|
399
|
+
existingContent = existingContent.replace(existingModelPattern, modelRls);
|
|
400
|
+
} else {
|
|
401
|
+
// Add new model RLS before the closing of rls.model
|
|
402
|
+
existingContent = existingContent.replace(
|
|
403
|
+
/(\n};[\s]*\n[\s]*module\.exports)/,
|
|
404
|
+
`,\n ${modelRls}\n};$1`
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
fs.writeFileSync(rlsPath, existingContent);
|
|
409
|
+
console.log(`β Updated RLS for model: ${modelName}`);
|
|
410
|
+
} else {
|
|
411
|
+
// If rls.js doesn't exist, create it with just this model
|
|
412
|
+
await generateRLS(
|
|
413
|
+
filteredModels,
|
|
414
|
+
rlsPath,
|
|
415
|
+
datasource.url,
|
|
416
|
+
datasource.isPostgreSQL,
|
|
417
|
+
userTable,
|
|
418
|
+
relationships
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
305
423
|
/**
|
|
306
424
|
* Build models from Prisma schema
|
|
307
425
|
* @param {Object} options - Build options
|
|
308
426
|
* @param {string} options.schema - Path to Prisma schema file
|
|
309
427
|
* @param {string} options.output - Output directory for generated models
|
|
310
|
-
* @param {string} options.
|
|
428
|
+
* @param {string} options.model - Optional: specific model to generate
|
|
429
|
+
* @param {string} options.only - Optional: specific component to generate
|
|
311
430
|
*/
|
|
312
431
|
async function buildModels(options) {
|
|
313
432
|
const schemaPath = path.resolve(process.cwd(), options.schema);
|
|
@@ -378,67 +497,120 @@ async function buildModels(options) {
|
|
|
378
497
|
|
|
379
498
|
const { models, enums } = parsedData;
|
|
380
499
|
|
|
381
|
-
|
|
500
|
+
// Filter models if --model option is provided
|
|
501
|
+
let filteredModels = models;
|
|
502
|
+
if (options.model) {
|
|
503
|
+
const modelName = options.model.toLowerCase();
|
|
504
|
+
const matchedModel = Object.keys(models).find(m => m.toLowerCase() === modelName);
|
|
382
505
|
|
|
383
|
-
|
|
384
|
-
|
|
506
|
+
if (!matchedModel) {
|
|
507
|
+
throw new Error(`Model "${options.model}" not found in schema. Available models: ${Object.keys(models).join(', ')}`);
|
|
508
|
+
}
|
|
385
509
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
510
|
+
filteredModels = { [matchedModel]: models[matchedModel] };
|
|
511
|
+
console.log(`Filtering to model: ${matchedModel}`);
|
|
512
|
+
}
|
|
389
513
|
|
|
390
|
-
|
|
391
|
-
console.log('Generating rapidd/rapidd.js...');
|
|
392
|
-
generateRapiddFile(rapiddJsPath);
|
|
514
|
+
console.log(`Found ${Object.keys(models).length} models${options.model ? ` (generating ${Object.keys(filteredModels).length})` : ''}`);
|
|
393
515
|
|
|
394
|
-
//
|
|
395
|
-
|
|
516
|
+
// Determine which components to generate
|
|
517
|
+
const shouldGenerate = {
|
|
518
|
+
model: !options.only || options.only === 'model',
|
|
519
|
+
route: !options.only || options.only === 'route',
|
|
520
|
+
rls: !options.only || options.only === 'rls',
|
|
521
|
+
relationship: !options.only || options.only === 'relationship'
|
|
522
|
+
};
|
|
396
523
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
524
|
+
// Validate --only option
|
|
525
|
+
if (options.only && !['model', 'route', 'rls', 'relationship'].includes(options.only)) {
|
|
526
|
+
throw new Error(`Invalid --only value "${options.only}". Must be one of: model, route, rls, relationship`);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Generate model files
|
|
530
|
+
if (shouldGenerate.model) {
|
|
531
|
+
generateAllModels(filteredModels, modelDir, modelJsPath);
|
|
532
|
+
|
|
533
|
+
// Generate src/Model.js (base Model class) - only if not filtering by model
|
|
534
|
+
if (!options.model) {
|
|
535
|
+
console.log('\nGenerating src/Model.js...');
|
|
536
|
+
generateBaseModelFile(modelJsPath);
|
|
402
537
|
}
|
|
403
|
-
console.log(`β Relationships file generated at: ${relationshipsPath}`);
|
|
404
|
-
} catch (error) {
|
|
405
|
-
console.error('Failed to generate relationships.json:', error.message);
|
|
406
|
-
console.log('Note: You may need to create relationships.json manually.');
|
|
407
538
|
}
|
|
408
539
|
|
|
409
|
-
// Generate
|
|
410
|
-
|
|
540
|
+
// Generate rapidd/rapidd.js - only if not filtering by model
|
|
541
|
+
if (!options.model && !options.only) {
|
|
542
|
+
console.log('Generating rapidd/rapidd.js...');
|
|
543
|
+
generateRapiddFile(rapiddJsPath);
|
|
544
|
+
}
|
|
411
545
|
|
|
412
|
-
//
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
546
|
+
// Generate relationships.json
|
|
547
|
+
if (shouldGenerate.relationship) {
|
|
548
|
+
console.log(`\nGenerating relationships.json...`);
|
|
549
|
+
|
|
550
|
+
try {
|
|
551
|
+
if (options.model) {
|
|
552
|
+
// Update only specific model in relationships.json
|
|
553
|
+
await updateRelationshipsForModel(filteredModels, relationshipsPath, prismaClientPath, schemaPath, usedDMMF);
|
|
554
|
+
} else {
|
|
555
|
+
// Generate all relationships
|
|
556
|
+
if (usedDMMF) {
|
|
557
|
+
await generateRelationshipsFromDMMF(prismaClientPath, relationshipsPath);
|
|
558
|
+
} else {
|
|
559
|
+
generateRelationshipsFromSchema(schemaPath, relationshipsPath);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
console.log(`β Relationships file generated at: ${relationshipsPath}`);
|
|
563
|
+
} catch (error) {
|
|
564
|
+
console.error('Failed to generate relationships.json:', error.message);
|
|
565
|
+
console.log('Note: You may need to create relationships.json manually.');
|
|
417
566
|
}
|
|
418
|
-
} catch (error) {
|
|
419
|
-
console.warn('Could not load relationships.json:', error.message);
|
|
420
567
|
}
|
|
421
568
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
569
|
+
// Generate RLS configuration
|
|
570
|
+
if (shouldGenerate.rls) {
|
|
571
|
+
console.log(`\nGenerating RLS configuration...`);
|
|
572
|
+
|
|
573
|
+
// Load relationships for Prisma filter building
|
|
574
|
+
let relationships = {};
|
|
575
|
+
try {
|
|
576
|
+
if (fs.existsSync(relationshipsPath)) {
|
|
577
|
+
relationships = JSON.parse(fs.readFileSync(relationshipsPath, 'utf8'));
|
|
578
|
+
}
|
|
579
|
+
} catch (error) {
|
|
580
|
+
console.warn('Could not load relationships.json:', error.message);
|
|
581
|
+
}
|
|
425
582
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
583
|
+
try {
|
|
584
|
+
// Parse datasource from Prisma schema to get database URL
|
|
585
|
+
const datasource = parseDatasource(schemaPath);
|
|
586
|
+
|
|
587
|
+
if (options.model) {
|
|
588
|
+
// Update only specific model in rls.js
|
|
589
|
+
await updateRLSForModel(filteredModels, rlsPath, datasource, options.userTable, relationships);
|
|
590
|
+
} else {
|
|
591
|
+
// Generate RLS for all models
|
|
592
|
+
await generateRLS(
|
|
593
|
+
models,
|
|
594
|
+
rlsPath,
|
|
595
|
+
datasource.url,
|
|
596
|
+
datasource.isPostgreSQL,
|
|
597
|
+
options.userTable,
|
|
598
|
+
relationships
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
} catch (error) {
|
|
602
|
+
console.error('Failed to generate RLS:', error.message);
|
|
603
|
+
if (!options.model) {
|
|
604
|
+
console.log('Generating permissive RLS fallback...');
|
|
605
|
+
await generateRLS(models, rlsPath, null, false, options.userTable, relationships);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
438
608
|
}
|
|
439
609
|
|
|
440
610
|
// Generate routes
|
|
441
|
-
|
|
611
|
+
if (shouldGenerate.route) {
|
|
612
|
+
generateAllRoutes(filteredModels, routesDir);
|
|
613
|
+
}
|
|
442
614
|
|
|
443
615
|
return { models, enums };
|
|
444
616
|
}
|