@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 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.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/build"
15
+ "url": "https://github.com/MertDalbudak/rapidd-build"
16
16
  },
17
17
  "bugs": {
18
- "url": "https://github.com/rapidd/build/issues"
18
+ "url": "https://github.com/MertDalbudak/rapidd-build/issues"
19
19
  },
20
- "homepage": "https://github.com/rapidd/build#readme",
20
+ "homepage": "https://github.com/MertDalbudak/rapidd-build#readme",
21
21
  "keywords": [
22
22
  "rapidd",
23
23
  "prisma",
@@ -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.relationships - Path to relationships.json file
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
- console.log(`Found ${Object.keys(models).length} models`);
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
- // Generate model files
384
- generateAllModels(models, modelDir, modelJsPath);
506
+ if (!matchedModel) {
507
+ throw new Error(`Model "${options.model}" not found in schema. Available models: ${Object.keys(models).join(', ')}`);
508
+ }
385
509
 
386
- // Generate src/Model.js (base Model class)
387
- console.log('\nGenerating src/Model.js...');
388
- generateBaseModelFile(modelJsPath);
510
+ filteredModels = { [matchedModel]: models[matchedModel] };
511
+ console.log(`Filtering to model: ${matchedModel}`);
512
+ }
389
513
 
390
- // Generate rapidd/rapidd.js
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
- // Generate relationships.json
395
- console.log(`\nGenerating relationships.json...`);
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
- try {
398
- if (usedDMMF) {
399
- await generateRelationshipsFromDMMF(prismaClientPath, relationshipsPath);
400
- } else {
401
- generateRelationshipsFromSchema(schemaPath, relationshipsPath);
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 RLS configuration
410
- console.log(`\nGenerating RLS configuration...`);
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
- // Load relationships for Prisma filter building
413
- let relationships = {};
414
- try {
415
- if (fs.existsSync(relationshipsPath)) {
416
- relationships = JSON.parse(fs.readFileSync(relationshipsPath, 'utf8'));
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
- try {
423
- // Parse datasource from Prisma schema to get database URL
424
- const datasource = parseDatasource(schemaPath);
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
- await generateRLS(
427
- models,
428
- rlsPath,
429
- datasource.url,
430
- datasource.isPostgreSQL,
431
- options.userTable,
432
- relationships
433
- );
434
- } catch (error) {
435
- console.error('Failed to generate RLS:', error.message);
436
- console.log('Generating permissive RLS fallback...');
437
- await generateRLS(models, rlsPath, null, false, options.userTable, relationships);
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
- generateAllRoutes(models, routesDir);
611
+ if (shouldGenerate.route) {
612
+ generateAllRoutes(filteredModels, routesDir);
613
+ }
442
614
 
443
615
  return { models, enums };
444
616
  }