@rapidd/build 1.0.1 → 1.0.3

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
@@ -19,6 +19,7 @@ program
19
19
  .option('-m, --model <name>', 'Generate/update only specific model (e.g., "account", "user")')
20
20
  .option('--only <component>', 'Generate only specific component: "model", "route", "rls", or "relationship"')
21
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)')
22
23
  .action(async (options) => {
23
24
  try {
24
25
  await buildModels(options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rapidd/build",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
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,7 +12,7 @@ const { generateAllRoutes } = require('../generators/routeGenerator');
12
12
  */
13
13
  function generateBaseModelFile(modelJsPath) {
14
14
  const content = `const { QueryBuilder, prisma } = require("./QueryBuilder");
15
- const {ErrorResponse} = require('./Api');
15
+ const {ErrorResponse, getTranslation} = require('./Api');
16
16
 
17
17
  class Model {
18
18
  /**
@@ -50,7 +50,8 @@ class Model {
50
50
  sortBy = sortBy.trim();
51
51
  sortOrder = sortOrder.trim();
52
52
  if (!sortBy.includes('.') && this.fields[sortBy] == undefined) {
53
- throw new ErrorResponse(\`Parameter sortBy '\${sortBy}' is not a valid field of \${this.constructor.name}\`, 400);
53
+ const message = getTranslation("invalid_sort_field", {sortBy, modelName: this.constructor.name});
54
+ throw new ErrorResponse(message, 400);
54
55
  }
55
56
 
56
57
  // Query the database using Prisma with filters, pagination, and limits
@@ -71,37 +72,40 @@ class Model {
71
72
  */
72
73
  _get = async (id, include, options = {}) =>{
73
74
  const {omit, ..._options} = options;
74
- id = Number(id)
75
+ id = Number(id);
75
76
  // To determine if the record is inaccessible, either due to non-existence or insufficient permissions, two simultaneous queries are performed.
76
77
  const _response = this.prisma.findUnique({
77
78
  'where': {
78
- 'id': id,
79
- ...this.getAccessFilter()
79
+ 'id': id
80
80
  },
81
81
  'include': this.include(include),
82
82
  'omit': {...this._omit(), ...omit},
83
83
  ..._options
84
84
  });
85
85
 
86
- const _checkExistence = this.prisma.findUnique({
86
+ const _checkPermission = this.prisma.findUnique({
87
87
  'where': {
88
- 'id': id
88
+ 'id': id,
89
+ ...this.getAccessFilter()
89
90
  },
90
91
  'select': {
91
92
  'id': true
92
93
  }
93
94
  });
94
95
 
95
- const [response, checkExistence] = await Promise.all([_response, _checkExistence]);
96
-
97
- if(response == null){
98
- if(checkExistence == null){
99
- throw new ErrorResponse("Record not found", 404);
96
+ const [response, checkPermission] = await Promise.all([_response, _checkPermission]);
97
+ if(response){
98
+ if(checkPermission){
99
+ if(response.id != checkExistence?.id){ // IN CASE access_filter CONTAINS id FIELD
100
+ throw new ErrorResponse(getTranslation("no_permission"), 403);
101
+ }
102
+ }
103
+ else{
104
+ throw new ErrorResponse(getTranslation("no_permission"), 403);
100
105
  }
101
- throw new ErrorResponse("No permission", 403);
102
106
  }
103
- if(response.id != checkExistence?.id){ // IN CASE access_filter CONTAINS id FIELD
104
- throw new ErrorResponse("No permission", 403);
107
+ else{
108
+ throw new ErrorResponse(getTranslation("record_not_found"), 404);
105
109
  }
106
110
  return response;
107
111
  }
@@ -276,6 +280,12 @@ class Model {
276
280
  module.exports = {Model, QueryBuilder, prisma};
277
281
  `;
278
282
 
283
+ // Ensure src directory exists
284
+ const srcDir = path.dirname(modelJsPath);
285
+ if (!fs.existsSync(srcDir)) {
286
+ fs.mkdirSync(srcDir, { recursive: true });
287
+ }
288
+
279
289
  fs.writeFileSync(modelJsPath, content);
280
290
  console.log('✓ Generated src/Model.js');
281
291
  }
@@ -359,10 +369,10 @@ async function updateRelationshipsForModel(filteredModels, relationshipsPath, pr
359
369
  /**
360
370
  * Update rls.js for a specific model
361
371
  */
362
- async function updateRLSForModel(filteredModels, rlsPath, datasource, userTable, relationships) {
372
+ async function updateRLSForModel(filteredModels, allModels, rlsPath, datasource, userTable, relationships, debug = false) {
363
373
  const { generateRLS } = require('../generators/rlsGeneratorV2');
364
374
 
365
- // Generate RLS for the filtered model
375
+ // Generate RLS for the filtered model (but pass all models for user table detection)
366
376
  const tempPath = rlsPath + '.tmp';
367
377
  await generateRLS(
368
378
  filteredModels,
@@ -370,7 +380,9 @@ async function updateRLSForModel(filteredModels, rlsPath, datasource, userTable,
370
380
  datasource.url,
371
381
  datasource.isPostgreSQL,
372
382
  userTable,
373
- relationships
383
+ relationships,
384
+ debug,
385
+ allModels
374
386
  );
375
387
 
376
388
  // Read the generated RLS for the specific model
@@ -379,13 +391,52 @@ async function updateRLSForModel(filteredModels, rlsPath, datasource, userTable,
379
391
 
380
392
  // Extract the model's RLS configuration
381
393
  const modelName = Object.keys(filteredModels)[0];
382
- const modelRlsMatch = tempContent.match(new RegExp(`${modelName}:\\s*\\{[\\s\\S]*?\\n \\}(?=,|\\n)`));
383
394
 
384
- if (!modelRlsMatch) {
385
- throw new Error(`Could not extract RLS for model ${modelName}`);
395
+ // Find the start of the model definition
396
+ const modelStart = tempContent.indexOf(`${modelName}:`);
397
+ if (modelStart === -1) {
398
+ throw new Error(`Could not find model ${modelName} in generated RLS`);
386
399
  }
387
400
 
388
- const modelRls = modelRlsMatch[0];
401
+ // Find the matching closing brace by counting braces
402
+ let braceCount = 0;
403
+ let inString = false;
404
+ let stringChar = null;
405
+ let i = tempContent.indexOf('{', modelStart);
406
+ const contentStart = i;
407
+
408
+ for (; i < tempContent.length; i++) {
409
+ const char = tempContent[i];
410
+ const prevChar = i > 0 ? tempContent[i - 1] : '';
411
+
412
+ // Handle string literals
413
+ if ((char === '"' || char === "'" || char === '`') && prevChar !== '\\') {
414
+ if (!inString) {
415
+ inString = true;
416
+ stringChar = char;
417
+ } else if (char === stringChar) {
418
+ inString = false;
419
+ stringChar = null;
420
+ }
421
+ }
422
+
423
+ if (!inString) {
424
+ if (char === '{') braceCount++;
425
+ if (char === '}') braceCount--;
426
+
427
+ if (braceCount === 0) {
428
+ // Found the closing brace
429
+ const modelRls = tempContent.substring(modelStart, i + 1);
430
+ break;
431
+ }
432
+ }
433
+ }
434
+
435
+ if (braceCount !== 0) {
436
+ throw new Error(`Could not extract RLS for model ${modelName} - unmatched braces`);
437
+ }
438
+
439
+ const modelRls = tempContent.substring(modelStart, i + 1);
389
440
 
390
441
  // Read existing rls.js
391
442
  if (fs.existsSync(rlsPath)) {
@@ -399,9 +450,10 @@ async function updateRLSForModel(filteredModels, rlsPath, datasource, userTable,
399
450
  existingContent = existingContent.replace(existingModelPattern, modelRls);
400
451
  } else {
401
452
  // Add new model RLS before the closing of rls.model
453
+ // Find the last closing brace of a model object and add comma after it
402
454
  existingContent = existingContent.replace(
403
- /(\n};[\s]*\n[\s]*module\.exports)/,
404
- `,\n ${modelRls}\n};$1`
455
+ /(\n \})\n(\};)/,
456
+ `$1,\n ${modelRls}\n$2`
405
457
  );
406
458
  }
407
459
 
@@ -415,7 +467,9 @@ async function updateRLSForModel(filteredModels, rlsPath, datasource, userTable,
415
467
  datasource.url,
416
468
  datasource.isPostgreSQL,
417
469
  userTable,
418
- relationships
470
+ relationships,
471
+ debug,
472
+ allModels
419
473
  );
420
474
  }
421
475
  }
@@ -529,16 +583,16 @@ async function buildModels(options) {
529
583
  // Generate model files
530
584
  if (shouldGenerate.model) {
531
585
  generateAllModels(filteredModels, modelDir, modelJsPath);
586
+ }
532
587
 
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);
537
- }
588
+ // Generate src/Model.js (base Model class) if it doesn't exist
589
+ if (!fs.existsSync(modelJsPath)) {
590
+ console.log('\nGenerating src/Model.js...');
591
+ generateBaseModelFile(modelJsPath);
538
592
  }
539
593
 
540
- // Generate rapidd/rapidd.js - only if not filtering by model
541
- if (!options.model && !options.only) {
594
+ // Generate rapidd/rapidd.js if it doesn't exist
595
+ if (!fs.existsSync(rapiddJsPath)) {
542
596
  console.log('Generating rapidd/rapidd.js...');
543
597
  generateRapiddFile(rapiddJsPath);
544
598
  }
@@ -586,7 +640,7 @@ async function buildModels(options) {
586
640
 
587
641
  if (options.model) {
588
642
  // Update only specific model in rls.js
589
- await updateRLSForModel(filteredModels, rlsPath, datasource, options.userTable, relationships);
643
+ await updateRLSForModel(filteredModels, models, rlsPath, datasource, options.userTable, relationships, options.debug);
590
644
  } else {
591
645
  // Generate RLS for all models
592
646
  await generateRLS(
@@ -595,14 +649,15 @@ async function buildModels(options) {
595
649
  datasource.url,
596
650
  datasource.isPostgreSQL,
597
651
  options.userTable,
598
- relationships
652
+ relationships,
653
+ options.debug
599
654
  );
600
655
  }
601
656
  } catch (error) {
602
657
  console.error('Failed to generate RLS:', error.message);
603
658
  if (!options.model) {
604
659
  console.log('Generating permissive RLS fallback...');
605
- await generateRLS(models, rlsPath, null, false, options.userTable, relationships);
660
+ await generateRLS(models, rlsPath, null, false, options.userTable, relationships, options.debug);
606
661
  }
607
662
  }
608
663
  }
@@ -150,7 +150,7 @@ function generateAllModels(models, modelDir, modelJsPath) {
150
150
 
151
151
  // Copy rapidd.js to output if it exists
152
152
  const sourceRapiddJs = path.join(process.cwd(), 'rapidd', 'rapidd.js');
153
- const outputRapiddDir = path.dirname(modelDir.replace(/src[\/\\]Model$/, 'rapidd'));
153
+ const outputRapiddDir = modelDir.replace(/src[\/\\]Model$/, 'rapidd');
154
154
  const outputRapiddJs = path.join(outputRapiddDir, 'rapidd.js');
155
155
 
156
156
  if (fs.existsSync(sourceRapiddJs)) {
@@ -34,10 +34,10 @@ function detectUserTable(models, userTableOption) {
34
34
  /**
35
35
  * Extract RLS policies from PostgreSQL
36
36
  * @param {string} databaseUrl - PostgreSQL connection URL
37
- * @param {Array} modelNames - Array of model names
38
- * @returns {Object} - RLS policies for each table
37
+ * @param {Object} models - Models object with dbName mapping
38
+ * @returns {Object} - RLS policies for each model
39
39
  */
40
- async function extractPostgreSQLPolicies(databaseUrl, modelNames) {
40
+ async function extractPostgreSQLPolicies(databaseUrl, models) {
41
41
  const client = new Client({ connectionString: databaseUrl });
42
42
 
43
43
  try {
@@ -45,8 +45,11 @@ async function extractPostgreSQLPolicies(databaseUrl, modelNames) {
45
45
 
46
46
  const policies = {};
47
47
 
48
- // Initialize all models with empty policies
49
- for (const modelName of modelNames) {
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;
50
53
  policies[modelName] = [];
51
54
  }
52
55
 
@@ -65,11 +68,13 @@ async function extractPostgreSQLPolicies(databaseUrl, modelNames) {
65
68
  ORDER BY tablename, policyname
66
69
  `);
67
70
 
68
- // Group policies by table
71
+ // Group policies by model (using table to model mapping)
69
72
  for (const row of result.rows) {
70
73
  const tableName = row.tablename;
71
- if (policies[tableName] !== undefined) {
72
- policies[tableName].push({
74
+ const modelName = tableToModelMap[tableName];
75
+
76
+ if (modelName && policies[modelName] !== undefined) {
77
+ policies[modelName].push({
73
78
  name: row.policyname,
74
79
  permissive: row.permissive === 'PERMISSIVE',
75
80
  roles: row.roles,
@@ -259,7 +264,7 @@ function generateFilter(policies, expressionField) {
259
264
  * @param {boolean} isPostgreSQL - Whether database is PostgreSQL
260
265
  * @param {string} userTableOption - User-specified table name
261
266
  */
262
- async function generateRLS(models, outputPath, databaseUrl, isPostgreSQL, userTableOption) {
267
+ async function generateRLS(models, outputPath, databaseUrl, isPostgreSQL, userTableOption, debug = false) {
263
268
  const userTable = detectUserTable(models, userTableOption);
264
269
  const modelNames = Object.keys(models);
265
270
 
@@ -277,18 +282,20 @@ async function generateRLS(models, outputPath, databaseUrl, isPostgreSQL, userTa
277
282
  functionAnalysis = await analyzeFunctions(databaseUrl);
278
283
  console.log(`✓ Analyzed ${Object.keys(functionAnalysis.functionMappings).length} PostgreSQL functions`);
279
284
 
280
- // Save function analysis for debugging/manual adjustment
281
- const configPath = path.join(path.dirname(outputPath), 'rls-mappings.json');
282
- const mappingConfig = generateMappingConfig(functionAnalysis);
283
- fs.writeFileSync(configPath, JSON.stringify(mappingConfig, null, 2));
284
- console.log(`✓ Function mappings saved to ${configPath}`);
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
+ }
285
292
  } catch (error) {
286
293
  console.warn(`⚠ Could not analyze functions: ${error.message}`);
287
294
  }
288
295
 
289
296
  console.log('Extracting RLS policies from database...');
290
297
  try {
291
- policies = await extractPostgreSQLPolicies(databaseUrl, modelNames);
298
+ policies = await extractPostgreSQLPolicies(databaseUrl, models);
292
299
  const totalPolicies = Object.values(policies).reduce((sum, p) => sum + p.length, 0);
293
300
  console.log(`✓ Extracted ${totalPolicies} RLS policies from PostgreSQL`);
294
301
  } catch (error) {
@@ -32,7 +32,7 @@ function detectUserTable(models, userTableOption) {
32
32
  /**
33
33
  * Extract RLS policies from PostgreSQL
34
34
  */
35
- async function extractPostgreSQLPolicies(databaseUrl, modelNames) {
35
+ async function extractPostgreSQLPolicies(databaseUrl, models) {
36
36
  const client = new Client({ connectionString: databaseUrl });
37
37
 
38
38
  try {
@@ -40,8 +40,11 @@ async function extractPostgreSQLPolicies(databaseUrl, modelNames) {
40
40
 
41
41
  const policies = {};
42
42
 
43
- // Initialize all models with empty policies
44
- for (const modelName of modelNames) {
43
+ // Create mapping from database table name to model name
44
+ const tableToModelMap = {};
45
+ for (const [modelName, modelData] of Object.entries(models)) {
46
+ const dbName = modelData.dbName || modelName.toLowerCase();
47
+ tableToModelMap[dbName] = modelName;
45
48
  policies[modelName] = [];
46
49
  }
47
50
 
@@ -60,11 +63,13 @@ async function extractPostgreSQLPolicies(databaseUrl, modelNames) {
60
63
  ORDER BY tablename, policyname
61
64
  `);
62
65
 
63
- // Group policies by table
66
+ // Group policies by model (using table to model mapping)
64
67
  for (const row of result.rows) {
65
68
  const tableName = row.tablename;
66
- if (policies[tableName] !== undefined) {
67
- policies[tableName].push({
69
+ const modelName = tableToModelMap[tableName];
70
+
71
+ if (modelName && policies[modelName] !== undefined) {
72
+ policies[modelName].push({
68
73
  name: row.policyname,
69
74
  permissive: row.permissive === 'PERMISSIVE',
70
75
  roles: row.roles,
@@ -165,9 +170,12 @@ function generateFunction(policies, expressionField, converter, modelName) {
165
170
  if (expr) {
166
171
  try {
167
172
  const jsExpr = converter.convertToJavaScript(expr, 'data', 'user', modelName);
173
+ console.log(`✓ Policy '${policy.name}': ${expr.substring(0, 50)}... -> ${jsExpr.substring(0, 80)}`);
168
174
  conditions.push(jsExpr);
169
175
  } catch (e) {
170
- conditions.push(`true /* Error: ${e.message} */`);
176
+ console.warn(`⚠ Failed to convert RLS policy '${policy.name}' for ${modelName}: ${e.message}`);
177
+ console.warn(` SQL: ${expr}`);
178
+ conditions.push(`true /* TODO: Manual conversion needed for policy '${policy.name}' */`);
171
179
  }
172
180
  }
173
181
  }
@@ -176,6 +184,11 @@ function generateFunction(policies, expressionField, converter, modelName) {
176
184
  return 'return true;';
177
185
  }
178
186
 
187
+ // If any condition is 'true', the entire expression is true
188
+ if (conditions.some(c => c === 'true' || c.startsWith('true /*'))) {
189
+ return 'return true;';
190
+ }
191
+
179
192
  // Policies are OR'd together (any policy allows)
180
193
  return `return ${conditions.join(' || ')};`;
181
194
  }
@@ -208,7 +221,9 @@ function generateFilter(policies, expressionField, converter, modelName) {
208
221
  hasDataFilter: prismaFilter !== '{}'
209
222
  });
210
223
  } catch (e) {
211
- // On error, skip filter
224
+ console.warn(`⚠ Failed to convert RLS filter policy '${policy.name}' for ${modelName}: ${e.message}`);
225
+ console.warn(` SQL: ${expr}`);
226
+ // On error, skip filter (fail-safe - no access)
212
227
  }
213
228
  }
214
229
  }
@@ -285,8 +300,10 @@ function buildConditionalFilter(filtersWithRoles) {
285
300
  /**
286
301
  * Generate complete rls.js file
287
302
  */
288
- async function generateRLS(models, outputPath, databaseUrl, isPostgreSQL, userTableOption, relationships = {}) {
289
- const userTable = detectUserTable(models, userTableOption);
303
+ async function generateRLS(models, outputPath, databaseUrl, isPostgreSQL, userTableOption, relationships = {}, debug = false, allModels = null) {
304
+ // Use allModels for user table detection if provided (when filtering by model)
305
+ const modelsForUserDetection = allModels || models;
306
+ const userTable = detectUserTable(modelsForUserDetection, userTableOption);
290
307
  const modelNames = Object.keys(models);
291
308
 
292
309
  let policies = {};
@@ -313,11 +330,13 @@ async function generateRLS(models, outputPath, databaseUrl, isPostgreSQL, userTa
313
330
  relationships
314
331
  );
315
332
 
316
- // Save function analysis for debugging
317
- const configPath = path.join(path.dirname(outputPath), 'rls-mappings.json');
318
- const mappingConfig = generateMappingConfig(functionAnalysis);
319
- fs.writeFileSync(configPath, JSON.stringify(mappingConfig, null, 2));
320
- console.log(`✓ Function mappings saved to ${configPath}`);
333
+ // Save function analysis for debugging (only if --debug flag is set)
334
+ if (debug) {
335
+ const configPath = path.join(path.dirname(outputPath), 'rls-mappings.json');
336
+ const mappingConfig = generateMappingConfig(functionAnalysis);
337
+ fs.writeFileSync(configPath, JSON.stringify(mappingConfig, null, 2));
338
+ console.log(`✓ Function mappings saved to ${configPath}`);
339
+ }
321
340
 
322
341
  // Also add user context requirements as a comment in rls.js
323
342
  if (Object.keys(functionAnalysis.userContextRequirements).length > 0) {
@@ -337,7 +356,7 @@ ${Object.entries(functionAnalysis.userContextRequirements)
337
356
 
338
357
  // Step 2: Extract policies
339
358
  try {
340
- policies = await extractPostgreSQLPolicies(databaseUrl, modelNames);
359
+ policies = await extractPostgreSQLPolicies(databaseUrl, models);
341
360
  const totalPolicies = Object.values(policies).reduce((sum, p) => sum + p.length, 0);
342
361
  console.log(`✓ Extracted ${totalPolicies} RLS policies from PostgreSQL`);
343
362
  } catch (error) {
@@ -7,11 +7,9 @@ const path = require('path');
7
7
  * @returns {string} - Generated route code
8
8
  */
9
9
  function generateRouteFile(modelName) {
10
- const modelNameLower = modelName.toLowerCase();
11
10
  const className = modelName.charAt(0).toUpperCase() + modelName.slice(1);
12
11
 
13
12
  return `const router = require('express').Router();
14
- const {Api, ErrorResponse} = require('../../../src/Api');
15
13
  const {${className}, QueryBuilder, prisma} = require('../../../src/Model/${className}');
16
14
 
17
15
  router.all('*', async (req, res, next) => {
@@ -20,13 +18,12 @@ router.all('*', async (req, res, next) => {
20
18
  next();
21
19
  }
22
20
  else{
23
- res.status(401).send({'status_code': res.statusCode, 'message': "No valid session"});
21
+ return res.sendError(401, req.getTranslation("no_valid_session"));
24
22
  }
25
23
  });
26
24
 
27
25
  // GET ALL
28
26
  router.get('/', async function(req, res) {
29
- let response, status_code = 200;
30
27
  try {
31
28
  const { q = {}, include = "", limit = 25, offset = 0, sortBy = "id", sortOrder = "asc" } = req.query;
32
29
 
@@ -34,67 +31,64 @@ router.get('/', async function(req, res) {
34
31
  const _count = req.${className}.count(q);
35
32
  const [data, count] = await Promise.all([_data, _count]);
36
33
 
37
- response = Api.getListResponseBody(data, {'take': req.${className}.take(Number(limit)), 'skip': req.${className}.skip(Number(offset)), 'total': count});
34
+ return res.sendList(data, {'take': req.${className}.take(Number(limit)), 'skip': req.${className}.skip(Number(offset)), 'total': count});
38
35
  }
39
36
  catch(error){
40
- response = QueryBuilder.errorHandler(error);
41
- status_code = response.status_code;
37
+ const response = QueryBuilder.errorHandler(error);
38
+ return res.status(response.status_code).send(response);
42
39
  }
43
- res.status(status_code).send(response);
44
40
  });
45
41
 
46
42
  // GET BY ID
47
43
  router.get('/:id', async function(req, res) {
48
- let response, status_code = 200;
49
44
  try{
50
45
  const { include = ""} = req.query;
51
- response = await req.${className}.get(req.params.id, include);
46
+ const response = await req.${className}.get(req.params.id, include);
47
+ return res.json(response);
52
48
  }
53
49
  catch(error){
54
- response = QueryBuilder.errorHandler(error);
55
- status_code = response.status_code;
50
+ const response = QueryBuilder.errorHandler(error);
51
+ return res.status(response.status_code).send(response);
56
52
  }
57
- res.status(status_code).send(response);
58
53
  });
59
54
 
60
55
  // CREATE
61
56
  router.post('/', async function(req, res) {
62
- let response, status_code = 201, payload = req.body;
57
+ const payload = req.body;
63
58
  try{
64
- response = await req.${className}.create(payload);
59
+ const response = await req.${className}.create(payload);
60
+ return res.status(201).json(response);
65
61
  }
66
62
  catch(error){
67
- response = QueryBuilder.errorHandler(error, payload);
68
- status_code = response.status_code;
63
+ const response = QueryBuilder.errorHandler(error, payload);
64
+ return res.status(response.status_code).send(response);
69
65
  }
70
- res.status(status_code).send(response);
71
66
  });
72
67
 
73
68
  // UPDATE
74
69
  router.patch('/:id', async function(req, res) {
75
- let response, status_code = 200, payload = req.body;
70
+ const payload = req.body;
76
71
  try{
77
- response = await req.${className}.update(req.params.id, payload);
72
+ const response = await req.${className}.update(req.params.id, payload);
73
+ return res.json(response);
78
74
  }
79
75
  catch(error){
80
- response = QueryBuilder.errorHandler(error, payload);
81
- status_code = response.status_code;
76
+ const response = QueryBuilder.errorHandler(error, payload);
77
+ return res.status(response.status_code).send(response);
82
78
  }
83
- res.status(status_code).send(response);
84
79
  });
85
80
 
86
81
  // DELETE
87
82
  router.delete('/:id', async (req, res)=>{
88
- let response, status_code = 200;
89
83
  try{
90
84
  await req.${className}.delete(req.params.id);
91
- response = {'status_code': status_code, 'message': "${className} successfully deleted"}
85
+ const message = req.getTranslation("object_deleted_successfully", {modelName: "${className}"});
86
+ return res.sendResponse(200, message);
92
87
  }
93
88
  catch(error){
94
- response = QueryBuilder.errorHandler(error);
95
- status_code = response.status_code;
89
+ const response = QueryBuilder.errorHandler(error);
90
+ return res.status(response.status_code).send(response);
96
91
  }
97
- res.status(status_code).send(response);
98
92
  });
99
93
 
100
94
  module.exports = router;
@@ -180,8 +180,8 @@ class DeepSQLAnalyzer {
180
180
  * Extract direct field comparisons
181
181
  */
182
182
  extractDirectComparisons(sql, analysis) {
183
- // Pattern: field = 'value'
184
- const stringPattern = /(\w+)\s*=\s*'([^']+)'/gi;
183
+ // Pattern: field = 'value' (with or without quotes on field name)
184
+ const stringPattern = /(?:"?(\w+)"?)\s*=\s*'([^']+)'/gi;
185
185
  let match;
186
186
  while ((match = stringPattern.exec(sql)) !== null) {
187
187
  const field = match[1];
@@ -200,8 +200,8 @@ class DeepSQLAnalyzer {
200
200
  });
201
201
  }
202
202
 
203
- // Pattern: field = number
204
- const numberPattern = /(\w+)\s*=\s*(\d+)(?!\s*\))/gi;
203
+ // Pattern: field = number (with or without quotes on field name)
204
+ const numberPattern = /(?:"?(\w+)"?)\s*=\s*(\d+)(?!\s*\))/gi;
205
205
  while ((match = numberPattern.exec(sql)) !== null) {
206
206
  const field = match[1];
207
207
  const value = match[2];
@@ -219,8 +219,22 @@ class DeepSQLAnalyzer {
219
219
  });
220
220
  }
221
221
 
222
+ // Pattern: field = true/false (with or without quotes on field name)
223
+ const booleanPattern = /(?:"?(\w+)"?)\s*=\s*(true|false)/gi;
224
+ while ((match = booleanPattern.exec(sql)) !== null) {
225
+ const field = match[1];
226
+ const value = match[2].toLowerCase();
227
+
228
+ analysis.filters.push({
229
+ type: 'equal',
230
+ field: field,
231
+ value: value,
232
+ prisma: `{ ${field}: ${value} }`
233
+ });
234
+ }
235
+
222
236
  // Pattern: field IS NULL
223
- const isNullPattern = /(\w+)\s+IS\s+NULL/gi;
237
+ const isNullPattern = /(?:"?(\w+)"?)\s+IS\s+NULL/gi;
224
238
  while ((match = isNullPattern.exec(sql)) !== null) {
225
239
  analysis.filters.push({
226
240
  type: 'is_null',
@@ -244,10 +258,10 @@ class DeepSQLAnalyzer {
244
258
  * Extract function-based comparisons
245
259
  */
246
260
  extractFunctionComparisons(sql, analysis) {
247
- // Pattern: field = function()
261
+ // Pattern: field = function() (with or without quotes on field name)
248
262
  const patterns = [
249
- /(\w+)\s*=\s*([\w.]+)\s*\(\s*\)/gi, // field = function()
250
- /([\w.]+)\s*\(\s*\)\s*=\s*(\w+)/gi // function() = field
263
+ /(?:"?(\w+)"?)\s*=\s*([\w.]+)\s*\(\s*\)/gi, // field = function()
264
+ /([\w.]+)\s*\(\s*\)\s*=\s*(?:"?(\w+)"?)/gi // function() = field
251
265
  ];
252
266
 
253
267
  // Normalize dots in function names for lookup
@@ -13,6 +13,23 @@ function convertToJavaScript(sql, dataVar = 'data', userVar = 'user') {
13
13
 
14
14
  sql = sql.trim();
15
15
 
16
+ // Strip outer parentheses if they wrap the entire expression
17
+ if (sql.startsWith('(') && sql.endsWith(')')) {
18
+ let depth = 0;
19
+ let matchesOuter = true;
20
+ for (let i = 0; i < sql.length; i++) {
21
+ if (sql[i] === '(') depth++;
22
+ if (sql[i] === ')') depth--;
23
+ if (depth === 0 && i < sql.length - 1) {
24
+ matchesOuter = false;
25
+ break;
26
+ }
27
+ }
28
+ if (matchesOuter) {
29
+ sql = sql.substring(1, sql.length - 1).trim();
30
+ }
31
+ }
32
+
16
33
  // Normalize whitespace
17
34
  sql = sql.replace(/\s+/g, ' ').replace(/\n/g, ' ');
18
35
 
@@ -48,8 +65,8 @@ function convertToJavaScript(sql, dataVar = 'data', userVar = 'user') {
48
65
  return `[${arrayValues}].includes(${userVar}?.${userProperty})`;
49
66
  }
50
67
 
51
- // Handle field = function() comparisons
52
- const funcCompareMatch = sql.match(/(\w+)\s*=\s*(\w+)\s*\([^)]*\)/i);
68
+ // Handle field = function() comparisons (with or without quotes)
69
+ const funcCompareMatch = sql.match(/(?:"?(\w+)"?)\s*=\s*(\w+)\s*\([^)]*\)/i);
53
70
  if (funcCompareMatch) {
54
71
  const field = funcCompareMatch[1];
55
72
  const funcName = funcCompareMatch[2];
@@ -60,7 +77,7 @@ function convertToJavaScript(sql, dataVar = 'data', userVar = 'user') {
60
77
  }
61
78
 
62
79
  // Handle field = (current_setting(...))
63
- const currentSettingMatch = sql.match(/(\w+)\s*=\s*\(\s*current_setting\s*\(\s*'([^']+)'/i);
80
+ const currentSettingMatch = sql.match(/(?:"?(\w+)"?)\s*=\s*\(\s*current_setting\s*\(\s*'([^']+)'/i);
64
81
  if (currentSettingMatch) {
65
82
  const field = currentSettingMatch[1];
66
83
  const setting = currentSettingMatch[2];
@@ -70,6 +87,14 @@ function convertToJavaScript(sql, dataVar = 'data', userVar = 'user') {
70
87
  return `${dataVar}?.${field} === ${userVar}?.${userProperty}`;
71
88
  }
72
89
 
90
+ // Handle field = literal value (true, false, numbers, strings)
91
+ const literalMatch = sql.match(/(?:"?(\w+)"?)\s*=\s*(true|false|\d+|'[^']+')/i);
92
+ if (literalMatch) {
93
+ const field = literalMatch[1];
94
+ const value = literalMatch[2];
95
+ return `${dataVar}?.${field} === ${value}`;
96
+ }
97
+
73
98
  // Handle EXISTS subqueries
74
99
  if (sql.includes('EXISTS')) {
75
100
  return handleExistsSubquery(sql, dataVar, userVar);
@@ -80,6 +105,7 @@ function convertToJavaScript(sql, dataVar = 'data', userVar = 'user') {
80
105
  if (sql.toLowerCase() === 'false') return 'false';
81
106
 
82
107
  // Unhandled pattern
108
+ console.warn(`⚠ Unhandled RLS pattern: ${sql}`);
83
109
  return `true /* Unhandled RLS pattern: ${sql.substring(0, 60)}... */`;
84
110
  }
85
111
 
@@ -46,6 +46,7 @@ function parsePrismaSchema(schemaPath) {
46
46
  for (const { name, body } of modelBlocks) {
47
47
  const fields = parseModelFields(body);
48
48
  const compositeKeyFields = parseCompositeKey(body);
49
+ const dbName = parseMapDirective(body);
49
50
 
50
51
  // Mark composite key fields with isId
51
52
  if (compositeKeyFields) {
@@ -60,7 +61,8 @@ function parsePrismaSchema(schemaPath) {
60
61
  name,
61
62
  fields,
62
63
  relations: parseModelRelations(body),
63
- compositeKey: compositeKeyFields
64
+ compositeKey: compositeKeyFields,
65
+ dbName: dbName || name.toLowerCase() // Default to lowercase model name
64
66
  };
65
67
  }
66
68
 
@@ -94,6 +96,25 @@ function parseCompositeKey(modelBody) {
94
96
  return null;
95
97
  }
96
98
 
99
+ /**
100
+ * Parse @@map directive to get database table name
101
+ * @param {string} modelBody - The content inside model braces
102
+ * @returns {string|null} - Database table name, or null if no @@map directive
103
+ */
104
+ function parseMapDirective(modelBody) {
105
+ const lines = modelBody.split('\n').map(line => line.trim());
106
+
107
+ for (const line of lines) {
108
+ // Match @@map("table_name") or @@map('table_name')
109
+ const match = line.match(/^@@map\(["']([^"']+)["']\)/);
110
+ if (match) {
111
+ return match[1];
112
+ }
113
+ }
114
+
115
+ return null;
116
+ }
117
+
97
118
  /**
98
119
  * Parse model fields from model body
99
120
  * @param {string} modelBody - The content inside model braces
@@ -206,7 +227,8 @@ async function parsePrismaDMMF(prismaClientPath) {
206
227
  name: model.name,
207
228
  fields: {},
208
229
  relations: [],
209
- compositeKey
230
+ compositeKey,
231
+ dbName: model.dbName || model.name.toLowerCase() // Use dbName from DMMF or default to lowercase
210
232
  };
211
233
 
212
234
  for (const field of model.fields) {