@rapidd/build 1.0.6 → 1.0.8

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/bin/cli.js CHANGED
@@ -17,9 +17,9 @@ program
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
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"')
21
- .option('--user-table <name>', 'Name of the user table for RLS (default: auto-detect from user/users)')
22
- .option('--debug', 'Enable debug mode (generates rls-mappings.json)')
20
+ .option('--only <component>', 'Generate only specific component: "model", "route", "acl", or "relationship"')
21
+ .option('--user-table <name>', 'Name of the user table for ACL (default: auto-detect from user/users)')
22
+ .option('--debug', 'Enable debug mode (generates acl-mappings.json)')
23
23
  .action(async (options) => {
24
24
  try {
25
25
  await buildModels(options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rapidd/build",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
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": {
@@ -3,7 +3,7 @@ const path = require('path');
3
3
  const { parsePrismaSchema, parsePrismaDMMF } = require('../parsers/prismaParser');
4
4
  const { generateAllModels } = require('../generators/modelGenerator');
5
5
  const { generateRelationshipsFromDMMF, generateRelationshipsFromSchema } = require('../generators/relationshipsGenerator');
6
- const { generateRLS } = require('../generators/rlsGeneratorV2');
6
+ const { generateACL } = require('../generators/aclGenerator');
7
7
  const { parseDatasource } = require('../parsers/datasourceParser');
8
8
  const { generateAllRoutes } = require('../generators/routeGenerator');
9
9
 
@@ -11,8 +11,8 @@ const { generateAllRoutes } = require('../generators/routeGenerator');
11
11
  * Generate src/Model.js base class file
12
12
  */
13
13
  function generateBaseModelFile(modelJsPath) {
14
- const content = `const { QueryBuilder, prisma } = require("./QueryBuilder");
15
- const {rls} = require('../rapidd/rapidd');
14
+ const content = `const { QueryBuilder, prisma, prismaTransaction } = require("./QueryBuilder");
15
+ const {acl} = require('../rapidd/rapidd');
16
16
  const {ErrorResponse} = require('./Api');
17
17
 
18
18
  class Model {
@@ -23,7 +23,7 @@ class Model {
23
23
  constructor(name, options){
24
24
  this.modelName = name;
25
25
  this.queryBuilder = new QueryBuilder(name);
26
- this.rls = rls.model[name] || {};
26
+ this.acl = acl.model[name] || {};
27
27
  this.options = options || {}
28
28
  this.user = this.options.user || {'id': 1, 'role': 'application'};
29
29
  this.user_id = this.user ? this.user.id : null;
@@ -32,12 +32,11 @@ class Model {
32
32
  _select = (fields) => this.queryBuilder.select(fields);
33
33
  _filter = (q) => this.queryBuilder.filter(q);
34
34
  _include = (include) => this.queryBuilder.include(include, this.user);
35
- // RLS METHODS
36
- _canCreate = () => this.rls?.canCreate?.(this.user);
37
- _hasAccess = (data) => this.rls?.hasAccess?.(data, this.user) || false;
38
- _getAccessFilter = () => this.rls?.getAccessFilter?.(this.user);
39
- _getUpdateFilter = () => this.rls?.getUpdateFilter?.(this.user);
40
- _getDeleteFilter = () => this.rls?.getDeleteFilter?.(this.user);
35
+ // ACL METHODS
36
+ _canCreate = () => this.acl.canCreate(this.user);
37
+ _getAccessFilter = () => this.acl.getAccessFilter?.(this.user);
38
+ _getUpdateFilter = () => this.acl.getUpdateFilter(this.user);
39
+ _getDeleteFilter = () => this.acl.getDeleteFilter(this.user);
41
40
  _omit = () => this.queryBuilder.omit(this.user);
42
41
 
43
42
  /**
@@ -61,15 +60,21 @@ class Model {
61
60
  }
62
61
 
63
62
  // Query the database using Prisma with filters, pagination, and limits
64
- return await this.prisma.findMany({
65
- 'where': this.filter(q),
66
- 'include': this.include(include),
67
- 'take': take,
68
- 'skip': skip,
69
- 'orderBy': this.sort(sortBy, sortOrder),
70
- 'omit': this._omit(),
71
- ...options
72
- });
63
+ const [data, total] = await prismaTransaction([
64
+ (tx) => tx[this.name].findMany({
65
+ 'where': this.filter(q),
66
+ 'include': this.include(include),
67
+ 'take': take,
68
+ 'skip': skip,
69
+ 'orderBy': this.sort(sortBy, sortOrder),
70
+ 'omit': this._omit(),
71
+ ...options
72
+ }),
73
+ (tx) => tx[this.name].count({
74
+ 'where': this.filter(q)
75
+ })
76
+ ]);
77
+ return {data, total};
73
78
  }
74
79
  /**
75
80
  * @param {number} id
@@ -288,15 +293,6 @@ class Model {
288
293
  return this._getAccessFilter();
289
294
  }
290
295
 
291
- /**
292
- *
293
- * @param {*} data
294
- * @returns {boolean}
295
- */
296
- hasAccess(data) {
297
- return this.user.role == "application" ? true : this._hasAccess(data, this.user);
298
- }
299
-
300
296
  /**
301
297
  * Check if user can create records
302
298
  * @returns {boolean}
@@ -307,7 +303,7 @@ class Model {
307
303
  }
308
304
 
309
305
  /**
310
- * Get update filter for RLS
306
+ * Get update filter for ACL
311
307
  * @returns {Object|false}
312
308
  */
313
309
  getUpdateFilter(){
@@ -319,7 +315,7 @@ class Model {
319
315
  }
320
316
 
321
317
  /**
322
- * Get delete filter for RLS
318
+ * Get delete filter for ACL
323
319
  * @returns {Object|false}
324
320
  */
325
321
  getDeleteFilter(){
@@ -355,99 +351,259 @@ module.exports = {Model, QueryBuilder, prisma};
355
351
 
356
352
  /**
357
353
  * Generate rapidd/rapidd.js file
354
+ * @param {string} rapiddJsPath - Path to rapidd.js
355
+ * @param {boolean} isPostgreSQL - Whether the database is PostgreSQL
358
356
  */
359
- function generateRapiddFile(rapiddJsPath) {
360
- const content = `const { PrismaClient } = require('../prisma/client');
357
+ function generateRapiddFile(rapiddJsPath, isPostgreSQL = true) {
358
+ let content;
359
+
360
+ if (isPostgreSQL) {
361
+ // PostgreSQL version with RLS support
362
+ content = `const { PrismaClient } = require('../prisma/client');
361
363
  const { AsyncLocalStorage } = require('async_hooks');
362
- const rls = require('./rls');
364
+ const acl = require('./acl');
363
365
 
364
366
  // Request Context Storage
365
367
  const requestContext = new AsyncLocalStorage();
366
368
 
367
369
  // RLS Configuration aus Environment Variables
368
370
  const RLS_CONFIG = {
369
- namespace: process.env.RLS_NAMESPACE || 'app',
370
- userId: process.env.RLS_USER_ID || 'current_user_id',
371
- userRole: process.env.RLS_USER_ROLE || 'current_user_role',
371
+ namespace: process.env.RLS_NAMESPACE || 'app',
372
+ userId: process.env.RLS_USER_ID || 'current_user_id',
373
+ userRole: process.env.RLS_USER_ROLE || 'current_user_role',
372
374
  };
373
375
 
374
376
  // Basis Prisma Client
375
377
  const basePrisma = new PrismaClient({
376
- log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
378
+ log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
377
379
  });
378
380
 
379
381
  /**
380
- * Setze RLS Session Variables in PostgreSQL
382
+ * FIXED: Setze RLS Session Variables in PostgreSQL
383
+ * Execute each SET command separately to avoid prepared statement error
381
384
  */
382
385
  async function setRLSVariables(tx, userId, userRole) {
383
386
  const namespace = RLS_CONFIG.namespace;
384
387
  const userIdVar = RLS_CONFIG.userId;
385
388
  const userRoleVar = RLS_CONFIG.userRole;
386
389
 
387
- await tx.$executeRawUnsafe(\`SET LOCAL \${namespace}.\${userIdVar} = '\${userId}'\`);
388
- await tx.$executeRawUnsafe(\`SET LOCAL \${namespace}.\${userRoleVar} = '\${userRole}'\`);
390
+ // Execute SET commands separately (PostgreSQL doesn't allow multiple commands in prepared statements)
391
+ await tx.$executeRawUnsafe(\`SET LOCAL "\${namespace}"."\${userIdVar}" = '\${userId}'\`);
392
+ await tx.$executeRawUnsafe(\`SET LOCAL "\${namespace}"."\${userRoleVar}" = '\${userRole}'\`);
389
393
  }
390
394
 
391
- // Erweiterter Prisma mit automatischer RLS
395
+ // FIXED: Erweiterter Prisma mit automatischer RLS
392
396
  const prisma = basePrisma.$extends({
393
- query: {
394
- async $allOperations({ args, query }) {
395
- const context = requestContext.getStore();
397
+ query: {
398
+ async $allOperations({ operation, args, query, model }) {
399
+ const context = requestContext.getStore();
396
400
 
397
- // Kein Context = keine RLS (z.B. System-Operationen)
398
- if (!context?.userId || !context?.userRole) {
399
- return query(args);
400
- }
401
+ // Kein Context = keine RLS (z.B. System-Operationen)
402
+ if (!context?.userId || !context?.userRole) {
403
+ return query(args);
404
+ }
405
+
406
+ const { userId, userRole } = context;
401
407
 
402
- const { userId, userRole } = context;
408
+ // IMPORTANT: The entire operation must happen in ONE transaction
409
+ // We need to wrap the ENTIRE query execution in a single transaction
403
410
 
404
- // Query in Transaction mit RLS ausführen
405
- return basePrisma.$transaction(async (tx) => {
406
- // Session-Variablen setzen
407
- await setRLSVariables(tx, userId, userRole);
411
+ // For operations that are already transactions, just set the variables
412
+ if (operation === '$transaction') {
413
+ return basePrisma.$transaction(async (tx) => {
414
+ await setRLSVariables(tx, userId, userRole);
415
+ return query(args);
416
+ });
417
+ }
408
418
 
409
- // Original Query ausführen
410
- return query(args);
411
- });
419
+ // For regular operations, wrap in transaction with RLS
420
+ return basePrisma.$transaction(async (tx) => {
421
+ // Set session variables
422
+ await setRLSVariables(tx, userId, userRole);
423
+
424
+ // Execute the original query using the transaction client
425
+ // This is the key: we need to use the transaction client for the query
426
+ if (model) {
427
+ // Model query (e.g., user.findMany())
428
+ return tx[model][operation](args);
429
+ } else {
430
+ // Raw query or special operation
431
+ return tx[operation](args);
432
+ }
433
+ });
434
+ },
412
435
  },
413
- },
414
436
  });
415
437
 
438
+ // Helper for batch operations in single transaction
439
+ async function prismaTransaction(operations) {
440
+ const context = requestContext.getStore();
441
+
442
+ if (!context?.userId || !context?.userRole) {
443
+ return Promise.all(operations);
444
+ }
445
+
446
+ return basePrisma.$transaction(async (tx) => {
447
+ await setRLSVariables(tx, context.userId, context.userRole);
448
+ return Promise.all(operations.map(op => op(tx)));
449
+ });
450
+ }
451
+
452
+ // Alternative approach: Manual transaction wrapper
453
+ class PrismaWithRLS {
454
+ constructor() {
455
+ this.client = basePrisma;
456
+ }
457
+
458
+ /**
459
+ * Execute any Prisma operation with RLS context
460
+ */
461
+ async withRLS(userId, userRole, callback) {
462
+ return this.client.$transaction(async (tx) => {
463
+ // Execute SET commands separately to avoid prepared statement error
464
+ await tx.$executeRawUnsafe(\`SET LOCAL app.current_user_id = '\${userId}'\`);
465
+ await tx.$executeRawUnsafe(\`SET LOCAL app.current_user_role = '\${userRole}'\`);
466
+
467
+ // Execute callback with transaction client
468
+ return callback(tx);
469
+ });
470
+ }
471
+
472
+ /**
473
+ * Get a proxy client for a specific user
474
+ * This wraps ALL operations in RLS context
475
+ */
476
+ forUser(userId, userRole) {
477
+ const withRLS = this.withRLS.bind(this);
478
+ const client = this.client;
479
+
480
+ return new Proxy({}, {
481
+ get(target, model) {
482
+ // Return a proxy for the model
483
+ return new Proxy({}, {
484
+ get(modelTarget, operation) {
485
+ // Return a function that wraps the operation
486
+ return async (args) => {
487
+ return withRLS(userId, userRole, async (tx) => {
488
+ return tx[model][operation](args);
489
+ });
490
+ };
491
+ }
492
+ });
493
+ }
494
+ });
495
+ }
496
+ }
497
+
498
+ const prismaWithRLS = new PrismaWithRLS();
499
+
500
+ /**
501
+ * Express Middleware: Set RLS context from authenticated user
502
+ */
503
+ function setRLSContext(req, res, next) {
504
+ if (req.user) {
505
+ // Set context for async operations
506
+ requestContext.run(
507
+ {
508
+ userId: req.user.id,
509
+ userRole: req.user.role
510
+ },
511
+ () => next()
512
+ );
513
+ } else {
514
+ next();
515
+ }
516
+ }
517
+
416
518
  /**
417
519
  * Helper: System-Operationen ohne RLS (für Cron-Jobs, etc.)
418
520
  */
419
521
  async function withSystemAccess(callback) {
420
- return requestContext.run(
421
- { userId: 'system', userRole: 'ADMIN' },
422
- callback
423
- );
522
+ // For system access, we might not want RLS at all
523
+ // So we use the base client directly
524
+ return callback(basePrisma);
424
525
  }
425
526
 
426
527
  /**
427
528
  * Helper: Als bestimmter User ausführen (für Tests)
428
529
  */
429
530
  async function withUser(userId, userRole, callback) {
430
- return requestContext.run({ userId, userRole }, callback);
531
+ return requestContext.run({ userId, userRole }, () => callback());
532
+ }
533
+
534
+ /**
535
+ * Helper: Direct transaction with RLS for complex operations
536
+ */
537
+ async function transactionWithRLS(userId, userRole, callback) {
538
+ return basePrisma.$transaction(async (tx) => {
539
+ // Set RLS context for this transaction - execute separately
540
+ await tx.$executeRawUnsafe(\`SET LOCAL app.current_user_id = '\${userId}'\`);
541
+ await tx.$executeRawUnsafe(\`SET LOCAL app.current_user_role = '\${userRole}'\`);
542
+
543
+ // Execute callback with transaction client
544
+ return callback(tx);
545
+ });
431
546
  }
432
547
 
433
548
  /**
434
549
  * Helper: Hole RLS Config (für SQL Generation)
435
550
  */
436
551
  function getRLSConfig() {
437
- return RLS_CONFIG;
552
+ return RLS_CONFIG;
438
553
  }
439
554
 
555
+ // Example usage in route
556
+ /*
557
+ app.get('/api/users', authenticateUser, setRLSContext, async (req, res) => {
558
+ // Option 1: Using extended prisma (automatic RLS)
559
+ const users = await prisma.user.findMany();
560
+
561
+ // Option 2: Using manual transaction
562
+ const users = await transactionWithRLS(req.user.id, req.user.role, async (tx) => {
563
+ return tx.user.findMany();
564
+ });
565
+
566
+ // Option 3: Using forUser helper
567
+ const userPrisma = prismaWithRLS.forUser(req.user.id, req.user.role);
568
+ const users = await userPrisma.user.findMany();
569
+
570
+ res.json(users);
571
+ });
572
+ */
573
+
574
+ module.exports = {
575
+ prisma,
576
+ prismaTransaction,
577
+ basePrisma, // Export base for auth operations that don't need RLS
578
+ PrismaClient,
579
+ requestContext,
580
+ setRLSContext,
581
+ withSystemAccess,
582
+ withUser,
583
+ transactionWithRLS,
584
+ prismaWithRLS,
585
+ getRLSConfig,
586
+ setRLSVariables,
587
+ acl
588
+ };
589
+ `;
590
+ } else {
591
+ // Non-PostgreSQL version (MySQL, SQLite, etc.) - simplified without RLS
592
+ content = `const { PrismaClient } = require('../prisma/client');
593
+ const acl = require('./acl');
594
+
595
+ // Standard Prisma Client
596
+ const prisma = new PrismaClient({
597
+ log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
598
+ });
599
+
440
600
  module.exports = {
441
- prisma,
442
- PrismaClient,
443
- requestContext,
444
- withSystemAccess,
445
- withUser,
446
- getRLSConfig,
447
- setRLSVariables,
448
- rls
601
+ prisma,
602
+ PrismaClient,
603
+ acl
449
604
  };
450
605
  `;
606
+ }
451
607
 
452
608
  // Ensure rapidd directory exists
453
609
  const rapiddDir = path.dirname(rapiddJsPath);
@@ -514,14 +670,14 @@ async function updateRelationshipsForModel(filteredModels, relationshipsPath, pr
514
670
  }
515
671
 
516
672
  /**
517
- * Update rls.js for a specific model
673
+ * Update acl.js for a specific model
518
674
  */
519
- async function updateRLSForModel(filteredModels, allModels, rlsPath, datasource, userTable, relationships, debug = false) {
520
- const { generateRLS } = require('../generators/rlsGeneratorV2');
675
+ async function updateACLForModel(filteredModels, allModels, aclPath, datasource, userTable, relationships, debug = false) {
676
+ const { generateACL } = require('../generators/aclGenerator');
521
677
 
522
- // Generate RLS for the filtered model (but pass all models for user table detection)
523
- const tempPath = rlsPath + '.tmp';
524
- await generateRLS(
678
+ // Generate ACL for the filtered model (but pass all models for user table detection)
679
+ const tempPath = aclPath + '.tmp';
680
+ await generateACL(
525
681
  filteredModels,
526
682
  tempPath,
527
683
  datasource.url,
@@ -532,11 +688,11 @@ async function updateRLSForModel(filteredModels, allModels, rlsPath, datasource,
532
688
  allModels
533
689
  );
534
690
 
535
- // Read the generated RLS for the specific model
691
+ // Read the generated ACL for the specific model
536
692
  const tempContent = fs.readFileSync(tempPath, 'utf8');
537
693
  fs.unlinkSync(tempPath);
538
694
 
539
- // Extract the model's RLS configuration
695
+ // Extract the model's ACL configuration
540
696
  const modelName = Object.keys(filteredModels)[0];
541
697
 
542
698
  // Find the start of the model definition
@@ -573,44 +729,43 @@ async function updateRLSForModel(filteredModels, allModels, rlsPath, datasource,
573
729
 
574
730
  if (braceCount === 0) {
575
731
  // Found the closing brace
576
- const modelRls = tempContent.substring(modelStart, i + 1);
577
732
  break;
578
733
  }
579
734
  }
580
735
  }
581
736
 
582
737
  if (braceCount !== 0) {
583
- throw new Error(`Could not extract RLS for model ${modelName} - unmatched braces`);
738
+ throw new Error(`Could not extract ACL for model ${modelName} - unmatched braces`);
584
739
  }
585
740
 
586
- const modelRls = tempContent.substring(modelStart, i + 1);
741
+ const modelAcl = tempContent.substring(modelStart, i + 1);
587
742
 
588
- // Read existing rls.js
589
- if (fs.existsSync(rlsPath)) {
590
- let existingContent = fs.readFileSync(rlsPath, 'utf8');
743
+ // Read existing acl.js
744
+ if (fs.existsSync(aclPath)) {
745
+ let existingContent = fs.readFileSync(aclPath, 'utf8');
591
746
 
592
- // Check if model already exists in RLS
747
+ // Check if model already exists in ACL
593
748
  const existingModelPattern = new RegExp(`${modelName}:\\s*\\{[\\s\\S]*?\\n \\}(?=,|\\n)`);
594
749
 
595
750
  if (existingModelPattern.test(existingContent)) {
596
- // Replace existing model RLS
597
- existingContent = existingContent.replace(existingModelPattern, modelRls);
751
+ // Replace existing model ACL
752
+ existingContent = existingContent.replace(existingModelPattern, modelAcl);
598
753
  } else {
599
- // Add new model RLS before the closing of rls.model
754
+ // Add new model ACL before the closing of acl.model
600
755
  // Find the last closing brace of a model object and add comma after it
601
756
  existingContent = existingContent.replace(
602
757
  /(\n \})\n(\};)/,
603
- `$1,\n ${modelRls}\n$2`
758
+ `$1,\n ${modelAcl}\n$2`
604
759
  );
605
760
  }
606
761
 
607
- fs.writeFileSync(rlsPath, existingContent);
762
+ fs.writeFileSync(aclPath, existingContent);
608
763
  console.log(`✓ Updated RLS for model: ${modelName}`);
609
764
  } else {
610
- // If rls.js doesn't exist, create it with just this model
611
- await generateRLS(
765
+ // If acl.js doesn't exist, create it with just this model
766
+ await generateACL(
612
767
  filteredModels,
613
- rlsPath,
768
+ aclPath,
614
769
  datasource.url,
615
770
  datasource.isPostgreSQL,
616
771
  userTable,
@@ -642,7 +797,7 @@ async function buildModels(options) {
642
797
  const modelJsPath = path.join(srcDir, 'Model.js');
643
798
  const rapiddDir = path.join(baseDir, 'rapidd');
644
799
  const relationshipsPath = path.join(rapiddDir, 'relationships.json');
645
- const rlsPath = path.join(rapiddDir, 'rls.js');
800
+ const aclPath = path.join(rapiddDir, 'acl.js');
646
801
  const rapiddJsPath = path.join(rapiddDir, 'rapidd.js');
647
802
  const routesDir = path.join(baseDir, 'routes', 'api', 'v1');
648
803
  const logsDir = path.join(baseDir, 'logs');
@@ -718,13 +873,13 @@ async function buildModels(options) {
718
873
  const shouldGenerate = {
719
874
  model: !options.only || options.only === 'model',
720
875
  route: !options.only || options.only === 'route',
721
- rls: !options.only || options.only === 'rls',
876
+ acl: !options.only || options.only === 'acl',
722
877
  relationship: !options.only || options.only === 'relationship'
723
878
  };
724
879
 
725
880
  // Validate --only option
726
- if (options.only && !['model', 'route', 'rls', 'relationship'].includes(options.only)) {
727
- throw new Error(`Invalid --only value "${options.only}". Must be one of: model, route, rls, relationship`);
881
+ if (options.only && !['model', 'route', 'acl', 'relationship'].includes(options.only)) {
882
+ throw new Error(`Invalid --only value "${options.only}". Must be one of: model, route, acl, relationship`);
728
883
  }
729
884
 
730
885
  // Generate model files
@@ -738,10 +893,18 @@ async function buildModels(options) {
738
893
  generateBaseModelFile(modelJsPath);
739
894
  }
740
895
 
896
+ // Parse datasource to determine database type
897
+ let datasource = { isPostgreSQL: true }; // Default to PostgreSQL
898
+ try {
899
+ datasource = parseDatasource(schemaPath);
900
+ } catch (error) {
901
+ console.warn('Could not parse datasource, assuming PostgreSQL:', error.message);
902
+ }
903
+
741
904
  // Generate rapidd/rapidd.js if it doesn't exist
742
905
  if (!fs.existsSync(rapiddJsPath)) {
743
906
  console.log('Generating rapidd/rapidd.js...');
744
- generateRapiddFile(rapiddJsPath);
907
+ generateRapiddFile(rapiddJsPath, datasource.isPostgreSQL);
745
908
  }
746
909
 
747
910
  // Generate relationships.json
@@ -767,9 +930,9 @@ async function buildModels(options) {
767
930
  }
768
931
  }
769
932
 
770
- // Generate RLS configuration
771
- if (shouldGenerate.rls) {
772
- console.log(`\nGenerating RLS configuration...`);
933
+ // Generate ACL configuration
934
+ if (shouldGenerate.acl) {
935
+ console.log(`\nGenerating ACL configuration...`);
773
936
 
774
937
  // Load relationships for Prisma filter building
775
938
  let relationships = {};
@@ -782,21 +945,19 @@ async function buildModels(options) {
782
945
  }
783
946
 
784
947
  try {
785
- // Parse datasource from Prisma schema to get database URL
786
- const datasource = parseDatasource(schemaPath);
787
948
 
788
- // For non-PostgreSQL databases (MySQL, SQLite, etc.), generate permissive RLS
949
+ // For non-PostgreSQL databases (MySQL, SQLite, etc.), generate permissive ACL
789
950
  if (!datasource.isPostgreSQL) {
790
- console.log(`${datasource.provider || 'Non-PostgreSQL'} database detected - generating permissive RLS...`);
791
- await generateRLS(models, rlsPath, null, false, options.userTable, relationships, options.debug);
951
+ console.log(`${datasource.provider || 'Non-PostgreSQL'} database detected - generating permissive ACL...`);
952
+ await generateACL(models, aclPath, null, false, options.userTable, relationships, options.debug);
792
953
  } else if (options.model) {
793
- // Update only specific model in rls.js
794
- await updateRLSForModel(filteredModels, models, rlsPath, datasource, options.userTable, relationships, options.debug);
954
+ // Update only specific model in acl.js
955
+ await updateACLForModel(filteredModels, models, aclPath, datasource, options.userTable, relationships, options.debug);
795
956
  } else {
796
- // Generate RLS for all models
797
- await generateRLS(
957
+ // Generate ACL for all models
958
+ await generateACL(
798
959
  models,
799
- rlsPath,
960
+ aclPath,
800
961
  datasource.url,
801
962
  datasource.isPostgreSQL,
802
963
  options.userTable,
@@ -805,10 +966,10 @@ async function buildModels(options) {
805
966
  );
806
967
  }
807
968
  } catch (error) {
808
- console.error('Failed to generate RLS:', error.message);
809
- console.log('Generating permissive RLS fallback...');
969
+ console.error('Failed to generate ACL:', error.message);
970
+ console.log('Generating permissive ACL fallback...');
810
971
  // Pass null for URL and false for isPostgreSQL to skip database connection
811
- await generateRLS(models, rlsPath, null, false, options.userTable, relationships, options.debug);
972
+ await generateACL(models, aclPath, null, false, options.userTable, relationships, options.debug);
812
973
  }
813
974
  }
814
975
 
@@ -30,7 +30,7 @@ function detectUserTable(models, userTableOption) {
30
30
  }
31
31
 
32
32
  /**
33
- * Extract RLS policies from PostgreSQL
33
+ * Extract ACL policies from PostgreSQL RLS
34
34
  */
35
35
  async function extractPostgreSQLPolicies(databaseUrl, models) {
36
36
  const client = new Client({ connectionString: databaseUrl });
@@ -48,7 +48,7 @@ async function extractPostgreSQLPolicies(databaseUrl, models) {
48
48
  policies[modelName] = [];
49
49
  }
50
50
 
51
- // Query all RLS policies from pg_policies
51
+ // Query all policies from PostgreSQL RLS (pg_policies)
52
52
  const result = await client.query(`
53
53
  SELECT
54
54
  tablename,
@@ -92,16 +92,15 @@ async function extractPostgreSQLPolicies(databaseUrl, models) {
92
92
  }
93
93
 
94
94
  /**
95
- * Generate RLS functions for a single model from PostgreSQL policies
95
+ * Generate ACL functions for a single model from PostgreSQL policies
96
96
  */
97
- function generateModelRLS(modelName, policies, converter) {
97
+ function generateModelACL(modelName, policies, converter) {
98
98
  const hasPolicies = policies && policies.length > 0;
99
99
 
100
100
  if (!hasPolicies) {
101
- // No RLS policies - generate permissive access
101
+ // No policies - generate permissive access
102
102
  return ` ${modelName}: {
103
103
  canCreate: (user) => true,
104
- hasAccess: (data, user) => true,
105
104
  getAccessFilter: (user) => ({}),
106
105
  getUpdateFilter: (user) => ({}),
107
106
  getDeleteFilter: (user) => ({}),
@@ -117,7 +116,6 @@ function generateModelRLS(modelName, policies, converter) {
117
116
 
118
117
  // Generate each function
119
118
  const canCreateCode = generateFunction(insertPolicies, 'withCheck', converter, modelName);
120
- const hasAccessCode = generateFunction(selectPolicies, 'using', converter, modelName);
121
119
  const accessFilterCode = generateFilter(selectPolicies, 'using', converter, modelName);
122
120
  let updateFilterCode = generateFilter(updatePolicies, 'using', converter, modelName);
123
121
  let deleteFilterCode = generateFilter(deletePolicies, 'using', converter, modelName);
@@ -136,9 +134,6 @@ function generateModelRLS(modelName, policies, converter) {
136
134
  canCreate: (user) => {
137
135
  ${canCreateCode}
138
136
  },
139
- hasAccess: (data, user) => {
140
- ${hasAccessCode}
141
- },
142
137
  getAccessFilter: (user) => {
143
138
  ${accessFilterCode}
144
139
  },
@@ -173,7 +168,7 @@ function generateFunction(policies, expressionField, converter, modelName) {
173
168
  console.log(`✓ Policy '${policy.name}': ${expr.substring(0, 50)}... -> ${jsExpr.substring(0, 80)}`);
174
169
  conditions.push(jsExpr);
175
170
  } catch (e) {
176
- console.warn(`⚠ Failed to convert RLS policy '${policy.name}' for ${modelName}: ${e.message}`);
171
+ console.warn(`⚠ Failed to convert policy '${policy.name}' for ${modelName}: ${e.message}`);
177
172
  console.warn(` SQL: ${expr}`);
178
173
  conditions.push(`true /* TODO: Manual conversion needed for policy '${policy.name}' */`);
179
174
  }
@@ -221,7 +216,7 @@ function generateFilter(policies, expressionField, converter, modelName) {
221
216
  hasDataFilter: prismaFilter !== '{}'
222
217
  });
223
218
  } catch (e) {
224
- console.warn(`⚠ Failed to convert RLS filter policy '${policy.name}' for ${modelName}: ${e.message}`);
219
+ console.warn(`⚠ Failed to convert filter policy '${policy.name}' for ${modelName}: ${e.message}`);
225
220
  console.warn(` SQL: ${expr}`);
226
221
  // On error, skip filter (fail-safe - no access)
227
222
  }
@@ -298,9 +293,9 @@ function buildConditionalFilter(filtersWithRoles) {
298
293
  }
299
294
 
300
295
  /**
301
- * Generate complete rls.js file
296
+ * Generate complete acl.js file
302
297
  */
303
- async function generateRLS(models, outputPath, databaseUrl, isPostgreSQL, userTableOption, relationships = {}, debug = false, allModels = null) {
298
+ async function generateACL(models, outputPath, databaseUrl, isPostgreSQL, userTableOption, relationships = {}, debug = false, allModels = null) {
304
299
  // Use allModels for user table detection if provided (when filtering by model)
305
300
  const modelsForUserDetection = allModels || models;
306
301
  const userTable = detectUserTable(modelsForUserDetection, userTableOption);
@@ -309,7 +304,7 @@ async function generateRLS(models, outputPath, databaseUrl, isPostgreSQL, userTa
309
304
  let policies = {};
310
305
  const timestamp = new Date().toISOString();
311
306
 
312
- let rlsCode = `const rls = {\n model: {},\n lastUpdateDate: '${timestamp}'\n};\n\n`;
307
+ let aclCode = `const acl = {\n model: {},\n lastUpdateDate: '${timestamp}'\n};\n\n`;
313
308
 
314
309
  // Create enhanced converter with analyzed functions, models, and relationships
315
310
  let converter = createEnhancedConverter({}, {}, models, relationships);
@@ -332,15 +327,15 @@ async function generateRLS(models, outputPath, databaseUrl, isPostgreSQL, userTa
332
327
 
333
328
  // Save function analysis for debugging (only if --debug flag is set)
334
329
  if (debug) {
335
- const configPath = path.join(path.dirname(outputPath), 'rls-mappings.json');
330
+ const configPath = path.join(path.dirname(outputPath), 'acl-mappings.json');
336
331
  const mappingConfig = generateMappingConfig(functionAnalysis);
337
332
  fs.writeFileSync(configPath, JSON.stringify(mappingConfig, null, 2));
338
333
  console.log(`✓ Function mappings saved to ${configPath}`);
339
334
  }
340
335
 
341
- // Also add user context requirements as a comment in rls.js
336
+ // Also add user context requirements as a comment in acl.js
342
337
  if (Object.keys(functionAnalysis.userContextRequirements).length > 0) {
343
- rlsCode = `/**
338
+ aclCode = `/**
344
339
  * User Context Requirements:
345
340
  * The user object should contain:
346
341
  ${Object.entries(functionAnalysis.userContextRequirements)
@@ -348,7 +343,7 @@ ${Object.entries(functionAnalysis.userContextRequirements)
348
343
  .join('\n')}
349
344
  */
350
345
 
351
- ` + rlsCode;
346
+ ` + aclCode;
352
347
  }
353
348
  } catch (error) {
354
349
  console.warn(`⚠ Could not analyze functions: ${error.message}`);
@@ -358,32 +353,32 @@ ${Object.entries(functionAnalysis.userContextRequirements)
358
353
  try {
359
354
  policies = await extractPostgreSQLPolicies(databaseUrl, models);
360
355
  const totalPolicies = Object.values(policies).reduce((sum, p) => sum + p.length, 0);
361
- console.log(`✓ Extracted ${totalPolicies} RLS policies from PostgreSQL`);
356
+ console.log(`✓ Extracted ${totalPolicies} policies from PostgreSQL RLS`);
362
357
  } catch (error) {
363
- console.warn(`⚠ Failed to extract PostgreSQL RLS: ${error.message}`);
364
- console.log('Generating permissive RLS for all models...');
358
+ console.warn(`⚠ Failed to extract PostgreSQL policies: ${error.message}`);
359
+ console.log('Generating permissive ACL for all models...');
365
360
  for (const modelName of modelNames) {
366
361
  policies[modelName] = [];
367
362
  }
368
363
  }
369
364
  } else {
370
365
  if (!isPostgreSQL) {
371
- console.log('Non-PostgreSQL database detected - RLS not supported');
366
+ console.log('Non-PostgreSQL database detected - generating permissive ACL');
372
367
  }
373
- console.log('Generating permissive RLS for all models...');
368
+ console.log('Generating permissive ACL for all models...');
374
369
  for (const modelName of modelNames) {
375
370
  policies[modelName] = [];
376
371
  }
377
372
  }
378
373
 
379
- // Generate RLS for each model
380
- rlsCode += 'rls.model = {\n';
381
- const modelRLSCode = modelNames.map(modelName => {
382
- return generateModelRLS(modelName, policies[modelName], converter);
374
+ // Generate ACL for each model
375
+ aclCode += 'acl.model = {\n';
376
+ const modelACLCode = modelNames.map(modelName => {
377
+ return generateModelACL(modelName, policies[modelName], converter);
383
378
  });
384
- rlsCode += modelRLSCode.join(',\n');
385
- rlsCode += '\n};\n\n';
386
- rlsCode += 'module.exports = rls;\n';
379
+ aclCode += modelACLCode.join(',\n');
380
+ aclCode += '\n};\n\n';
381
+ aclCode += 'module.exports = acl;\n';
387
382
 
388
383
  // Ensure output directory exists
389
384
  const outputDir = path.dirname(outputPath);
@@ -391,10 +386,10 @@ ${Object.entries(functionAnalysis.userContextRequirements)
391
386
  fs.mkdirSync(outputDir, { recursive: true });
392
387
  }
393
388
 
394
- fs.writeFileSync(outputPath, rlsCode);
395
- console.log('✓ Generated rls.js with dynamic function mappings');
389
+ fs.writeFileSync(outputPath, aclCode);
390
+ console.log('✓ Generated acl.js with dynamic function mappings');
396
391
  }
397
392
 
398
393
  module.exports = {
399
- generateRLS
394
+ generateACL
400
395
  };
@@ -26,12 +26,8 @@ router.all('*', async (req, res, next) => {
26
26
  router.get('/', async function(req, res) {
27
27
  try {
28
28
  const { q = {}, include = "", limit = 25, offset = 0, sortBy = "id", sortOrder = "asc" } = req.query;
29
-
30
- const _data = req.${className}.getMany(q, include, limit, offset, sortBy, sortOrder);
31
- const _count = req.${className}.count(q);
32
- const [data, count] = await Promise.all([_data, _count]);
33
-
34
- return res.sendList(data, {'take': req.${className}.take(Number(limit)), 'skip': req.${className}.skip(Number(offset)), 'total': count});
29
+ const results = await req.${className}.getMany(q, include, limit, offset, sortBy, sortOrder);
30
+ return res.sendList(results.data, {'take': req.${className}.take(Number(limit)), 'skip': req.${className}.skip(Number(offset)), 'total': results.total});
35
31
  }
36
32
  catch(error){
37
33
  const response = QueryBuilder.errorHandler(error);
@@ -1,341 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const { Client } = require('pg');
4
- const { createConverter } = require('../parsers/autoRLSConverter');
5
- const { analyzeFunctions, generateMappingConfig } = require('../parsers/functionAnalyzer');
6
-
7
- /**
8
- * Auto-detect user table name (case-insensitive search for user/users)
9
- * @param {Object} models - Models object from parser
10
- * @param {string} userTableOption - User-specified table name (optional)
11
- * @returns {string} - Name of the user table
12
- */
13
- function detectUserTable(models, userTableOption) {
14
- if (userTableOption) {
15
- return userTableOption;
16
- }
17
-
18
- const modelNames = Object.keys(models);
19
- const userTables = modelNames.filter(name =>
20
- name.toLowerCase() === 'user' || name.toLowerCase() === 'users'
21
- );
22
-
23
- if (userTables.length === 0) {
24
- throw new Error('No user table found (user/users). Please specify --user-table option.');
25
- }
26
-
27
- if (userTables.length > 1) {
28
- throw new Error(`Multiple user tables found: ${userTables.join(', ')}. Please specify --user-table option.`);
29
- }
30
-
31
- return userTables[0];
32
- }
33
-
34
- /**
35
- * Extract RLS policies from PostgreSQL
36
- * @param {string} databaseUrl - PostgreSQL connection URL
37
- * @param {Object} models - Models object with dbName mapping
38
- * @returns {Object} - RLS policies for each model
39
- */
40
- async function extractPostgreSQLPolicies(databaseUrl, models) {
41
- const client = new Client({ connectionString: databaseUrl });
42
-
43
- try {
44
- await client.connect();
45
-
46
- const policies = {};
47
-
48
- // Create mapping from database table name to model name
49
- const tableToModelMap = {};
50
- for (const [modelName, modelData] of Object.entries(models)) {
51
- const dbName = modelData.dbName || modelName.toLowerCase();
52
- tableToModelMap[dbName] = modelName;
53
- policies[modelName] = [];
54
- }
55
-
56
- // Query all RLS policies from pg_policies
57
- const result = await client.query(`
58
- SELECT
59
- tablename,
60
- policyname,
61
- permissive,
62
- roles,
63
- cmd,
64
- qual,
65
- with_check
66
- FROM pg_policies
67
- WHERE schemaname = 'public'
68
- ORDER BY tablename, policyname
69
- `);
70
-
71
- // Group policies by model (using table to model mapping)
72
- for (const row of result.rows) {
73
- const tableName = row.tablename;
74
- const modelName = tableToModelMap[tableName];
75
-
76
- if (modelName && policies[modelName] !== undefined) {
77
- policies[modelName].push({
78
- name: row.policyname,
79
- permissive: row.permissive === 'PERMISSIVE',
80
- roles: row.roles,
81
- command: row.cmd, // SELECT, INSERT, UPDATE, DELETE, ALL
82
- using: row.qual, // USING expression
83
- withCheck: row.with_check // WITH CHECK expression
84
- });
85
- }
86
- }
87
-
88
- await client.end();
89
- return policies;
90
-
91
- } catch (error) {
92
- try {
93
- await client.end();
94
- } catch (e) {
95
- // Ignore cleanup errors
96
- }
97
- throw error;
98
- }
99
- }
100
-
101
- /**
102
- * Generate RLS functions for a single model from PostgreSQL policies
103
- * @param {string} modelName - Name of the model
104
- * @param {Array} policies - Array of policy objects for this model
105
- * @param {string} userTable - Name of the user table
106
- * @returns {string} - JavaScript code for RLS functions
107
- */
108
- function generateModelRLS(modelName, policies, userTable) {
109
- const hasPolicies = policies && policies.length > 0;
110
-
111
- if (!hasPolicies) {
112
- // No RLS policies - generate permissive access
113
- return ` ${modelName}: {
114
- canCreate: (user) => true,
115
- hasAccess: (data, user) => true,
116
- getAccessFilter: (user) => ({}),
117
- getUpdateFilter: (user) => ({}),
118
- getDeleteFilter: (user) => ({}),
119
- getOmitFields: (user) => []
120
- }`;
121
- }
122
-
123
- // Find policies by command type
124
- const selectPolicies = policies.filter(p => p.command === 'SELECT' || p.command === 'ALL');
125
- const insertPolicies = policies.filter(p => p.command === 'INSERT' || p.command === 'ALL');
126
- const updatePolicies = policies.filter(p => p.command === 'UPDATE' || p.command === 'ALL');
127
- const deletePolicies = policies.filter(p => p.command === 'DELETE' || p.command === 'ALL');
128
-
129
- // Generate canCreate (INSERT policies with WITH CHECK)
130
- const canCreateCode = generateCanCreate(insertPolicies);
131
-
132
- // Generate hasAccess (SELECT policies with USING)
133
- const hasAccessCode = generateHasAccess(selectPolicies);
134
-
135
- // Generate getAccessFilter (SELECT policies)
136
- const accessFilterCode = generateFilter(selectPolicies, 'using');
137
-
138
- // Generate getUpdateFilter (UPDATE policies)
139
- const updateFilterCode = generateFilter(updatePolicies, 'using');
140
-
141
- // Generate getDeleteFilter (DELETE policies)
142
- const deleteFilterCode = generateFilter(deletePolicies, 'using');
143
-
144
- return ` ${modelName}: {
145
- canCreate: (user) => {
146
- ${canCreateCode}
147
- },
148
- hasAccess: (data, user) => {
149
- ${hasAccessCode}
150
- },
151
- getAccessFilter: (user) => {
152
- ${accessFilterCode}
153
- },
154
- getUpdateFilter: (user) => {
155
- ${updateFilterCode}
156
- },
157
- getDeleteFilter: (user) => {
158
- ${deleteFilterCode}
159
- },
160
- getOmitFields: (user) => []
161
- }`;
162
- }
163
-
164
- /**
165
- * Generate canCreate function from INSERT policies
166
- */
167
- function generateCanCreate(insertPolicies, converter) {
168
- if (insertPolicies.length === 0) {
169
- return 'return true;';
170
- }
171
-
172
- const conditions = [];
173
-
174
- for (const policy of insertPolicies) {
175
- const expr = policy.withCheck || policy.using;
176
- if (expr) {
177
- try {
178
- const jsExpr = converter.convertToJavaScript(expr, 'data', 'user');
179
- conditions.push(jsExpr);
180
- } catch (e) {
181
- conditions.push(`true /* Error parsing: ${expr.substring(0, 50)}... */`);
182
- }
183
- }
184
- }
185
-
186
- if (conditions.length === 0) {
187
- return 'return true;';
188
- }
189
-
190
- // Policies are OR'd together (any policy allows)
191
- return `return ${conditions.join(' || ')};`;
192
- }
193
-
194
- /**
195
- * Generate hasAccess function from SELECT policies
196
- */
197
- function generateHasAccess(selectPolicies) {
198
- if (selectPolicies.length === 0) {
199
- return 'return true;';
200
- }
201
-
202
- const conditions = [];
203
-
204
- for (const policy of selectPolicies) {
205
- if (policy.using) {
206
- try {
207
- const jsExpr = convertToJavaScript(policy.using, 'data', 'user');
208
- conditions.push(jsExpr);
209
- } catch (e) {
210
- conditions.push(`true /* Error parsing: ${policy.using.substring(0, 50)}... */`);
211
- }
212
- }
213
- }
214
-
215
- if (conditions.length === 0) {
216
- return 'return true;';
217
- }
218
-
219
- // Policies are OR'd together (any policy allows)
220
- return `return ${conditions.join(' || ')};`;
221
- }
222
-
223
- /**
224
- * Generate Prisma filter function
225
- */
226
- function generateFilter(policies, expressionField) {
227
- if (policies.length === 0) {
228
- return 'return {};';
229
- }
230
-
231
- const filters = [];
232
-
233
- for (const policy of policies) {
234
- const expr = policy[expressionField];
235
- if (expr) {
236
- try {
237
- const prismaFilter = convertToPrismaFilter(expr, 'user');
238
- if (prismaFilter !== '{}') {
239
- filters.push(prismaFilter);
240
- }
241
- } catch (e) {
242
- // On error, return empty filter (permissive)
243
- }
244
- }
245
- }
246
-
247
- if (filters.length === 0) {
248
- return 'return {};';
249
- }
250
-
251
- if (filters.length === 1) {
252
- return `return ${filters[0]};`;
253
- }
254
-
255
- // Multiple policies are OR'd together
256
- return `return { OR: [${filters.join(', ')}] };`;
257
- }
258
-
259
- /**
260
- * Generate complete rls.js file
261
- * @param {Object} models - Models object
262
- * @param {string} outputPath - Path to output rls.js
263
- * @param {string} databaseUrl - Database connection URL
264
- * @param {boolean} isPostgreSQL - Whether database is PostgreSQL
265
- * @param {string} userTableOption - User-specified table name
266
- */
267
- async function generateRLS(models, outputPath, databaseUrl, isPostgreSQL, userTableOption, debug = false) {
268
- const userTable = detectUserTable(models, userTableOption);
269
- const modelNames = Object.keys(models);
270
-
271
- let policies = {};
272
- const timestamp = new Date().toISOString();
273
-
274
- let rlsCode = `const rls = {\n model: {},\n lastUpdateDate: '${timestamp}'\n};\n\n`;
275
-
276
- // Analyze PostgreSQL functions if available
277
- let functionAnalysis = null;
278
-
279
- if (isPostgreSQL && databaseUrl) {
280
- console.log('PostgreSQL detected - analyzing database functions...');
281
- try {
282
- functionAnalysis = await analyzeFunctions(databaseUrl);
283
- console.log(`✓ Analyzed ${Object.keys(functionAnalysis.functionMappings).length} PostgreSQL functions`);
284
-
285
- // Save function analysis for debugging/manual adjustment (only if --debug flag is set)
286
- if (debug) {
287
- const configPath = path.join(path.dirname(outputPath), 'rls-mappings.json');
288
- const mappingConfig = generateMappingConfig(functionAnalysis);
289
- fs.writeFileSync(configPath, JSON.stringify(mappingConfig, null, 2));
290
- console.log(`✓ Function mappings saved to ${configPath}`);
291
- }
292
- } catch (error) {
293
- console.warn(`⚠ Could not analyze functions: ${error.message}`);
294
- }
295
-
296
- console.log('Extracting RLS policies from database...');
297
- try {
298
- policies = await extractPostgreSQLPolicies(databaseUrl, models);
299
- const totalPolicies = Object.values(policies).reduce((sum, p) => sum + p.length, 0);
300
- console.log(`✓ Extracted ${totalPolicies} RLS policies from PostgreSQL`);
301
- } catch (error) {
302
- console.warn(`⚠ Failed to extract PostgreSQL RLS: ${error.message}`);
303
- console.log('Generating permissive RLS for all models...');
304
- // Initialize empty policies for all models
305
- for (const modelName of modelNames) {
306
- policies[modelName] = [];
307
- }
308
- }
309
- } else {
310
- if (!isPostgreSQL) {
311
- console.log('Non-PostgreSQL database detected (MySQL/SQLite/etc) - RLS not supported');
312
- }
313
- console.log('Generating permissive RLS for all models...');
314
- // Initialize empty policies for all models
315
- for (const modelName of modelNames) {
316
- policies[modelName] = [];
317
- }
318
- }
319
-
320
- // Generate RLS for each model
321
- rlsCode += 'rls.model = {\n';
322
- const modelRLSCode = modelNames.map(modelName => {
323
- return generateModelRLS(modelName, policies[modelName], userTable);
324
- });
325
- rlsCode += modelRLSCode.join(',\n');
326
- rlsCode += '\n};\n\n';
327
- rlsCode += 'module.exports = rls;\n';
328
-
329
- // Ensure output directory exists
330
- const outputDir = path.dirname(outputPath);
331
- if (!fs.existsSync(outputDir)) {
332
- fs.mkdirSync(outputDir, { recursive: true });
333
- }
334
-
335
- fs.writeFileSync(outputPath, rlsCode);
336
- console.log('Generated rls.js');
337
- }
338
-
339
- module.exports = {
340
- generateRLS
341
- };