@rapidd/build 1.2.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +219 -68
  2. package/dist/bin/cli.d.ts +3 -0
  3. package/dist/bin/cli.d.ts.map +1 -0
  4. package/dist/bin/cli.js +31 -0
  5. package/dist/bin/cli.js.map +1 -0
  6. package/dist/index.d.ts +18 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +32 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/src/commands/build.d.ts +17 -0
  11. package/dist/src/commands/build.d.ts.map +1 -0
  12. package/dist/src/commands/build.js +236 -0
  13. package/dist/src/commands/build.js.map +1 -0
  14. package/dist/src/generators/aclGenerator.d.ts +6 -0
  15. package/dist/src/generators/aclGenerator.d.ts.map +1 -0
  16. package/dist/src/generators/aclGenerator.js +384 -0
  17. package/dist/src/generators/aclGenerator.js.map +1 -0
  18. package/dist/src/generators/index.d.ts +4 -0
  19. package/dist/src/generators/index.d.ts.map +1 -0
  20. package/dist/src/generators/index.js +13 -0
  21. package/dist/src/generators/index.js.map +1 -0
  22. package/dist/src/generators/modelGenerator.d.ts +10 -0
  23. package/dist/src/generators/modelGenerator.d.ts.map +1 -0
  24. package/dist/src/generators/modelGenerator.js +143 -0
  25. package/dist/src/generators/modelGenerator.js.map +1 -0
  26. package/dist/src/generators/routeGenerator.d.ts +10 -0
  27. package/dist/src/generators/routeGenerator.d.ts.map +1 -0
  28. package/dist/src/generators/routeGenerator.js +172 -0
  29. package/dist/src/generators/routeGenerator.js.map +1 -0
  30. package/dist/src/parsers/datasourceParser.d.ts +11 -0
  31. package/dist/src/parsers/datasourceParser.d.ts.map +1 -0
  32. package/dist/src/parsers/datasourceParser.js +131 -0
  33. package/dist/src/parsers/datasourceParser.js.map +1 -0
  34. package/dist/src/parsers/deepSQLAnalyzer.d.ts +85 -0
  35. package/dist/src/parsers/deepSQLAnalyzer.d.ts.map +1 -0
  36. package/dist/src/parsers/deepSQLAnalyzer.js +482 -0
  37. package/dist/src/parsers/deepSQLAnalyzer.js.map +1 -0
  38. package/dist/src/parsers/enhancedRLSConverter.d.ts +14 -0
  39. package/dist/src/parsers/enhancedRLSConverter.d.ts.map +1 -0
  40. package/dist/src/parsers/enhancedRLSConverter.js +168 -0
  41. package/dist/src/parsers/enhancedRLSConverter.js.map +1 -0
  42. package/dist/src/parsers/functionAnalyzer.d.ts +55 -0
  43. package/dist/src/parsers/functionAnalyzer.d.ts.map +1 -0
  44. package/dist/src/parsers/functionAnalyzer.js +274 -0
  45. package/dist/src/parsers/functionAnalyzer.js.map +1 -0
  46. package/dist/src/parsers/index.d.ts +13 -0
  47. package/dist/src/parsers/index.d.ts.map +1 -0
  48. package/dist/src/parsers/index.js +20 -0
  49. package/dist/src/parsers/index.js.map +1 -0
  50. package/dist/src/parsers/prismaFilterBuilder.d.ts +79 -0
  51. package/dist/src/parsers/prismaFilterBuilder.d.ts.map +1 -0
  52. package/dist/src/parsers/prismaFilterBuilder.js +322 -0
  53. package/dist/src/parsers/prismaFilterBuilder.js.map +1 -0
  54. package/dist/src/parsers/prismaParser.d.ts +14 -0
  55. package/dist/src/parsers/prismaParser.d.ts.map +1 -0
  56. package/dist/src/parsers/prismaParser.js +263 -0
  57. package/dist/src/parsers/prismaParser.js.map +1 -0
  58. package/package.json +21 -13
  59. package/bin/cli.js +0 -33
  60. package/index.js +0 -11
  61. package/src/commands/build.js +0 -638
  62. package/src/generators/aclGenerator.js +0 -394
  63. package/src/generators/modelGenerator.js +0 -172
  64. package/src/generators/relationshipsGenerator.js +0 -200
  65. package/src/generators/routeGenerator.js +0 -119
  66. package/src/parsers/datasourceParser.js +0 -121
  67. package/src/parsers/deepSQLAnalyzer.js +0 -554
  68. package/src/parsers/enhancedRLSConverter.js +0 -181
  69. package/src/parsers/functionAnalyzer.js +0 -302
  70. package/src/parsers/prismaFilterBuilder.js +0 -422
  71. package/src/parsers/prismaParser.js +0 -287
@@ -1,181 +0,0 @@
1
- /**
2
- * Enhanced RLS Converter using Deep SQL Analysis
3
- */
4
-
5
- const DeepSQLAnalyzer = require('./deepSQLAnalyzer');
6
- const PrismaFilterBuilder = require('./prismaFilterBuilder');
7
-
8
- function createEnhancedConverter(functionMappings = {}, sessionVariables = {}, models = {}, relationships = {}) {
9
- const analyzer = new DeepSQLAnalyzer();
10
- const filterBuilder = new PrismaFilterBuilder(models, relationships);
11
-
12
- /**
13
- * Convert PostgreSQL RLS to JavaScript with deep analysis
14
- */
15
- function convertToJavaScript(sql, dataVar = 'data', userVar = 'user', modelName = null) {
16
- if (!sql || sql.trim() === '') {
17
- return 'true';
18
- }
19
-
20
- // Use deep analysis
21
- const analysis = analyzer.analyzeSQLForFilters(sql);
22
- analysis.sql = sql; // Store original SQL for OR/AND detection
23
-
24
- // If we have models and relationships, use the filter builder for better JavaScript
25
- if (modelName && Object.keys(models).length > 0) {
26
- return filterBuilder.buildJavaScriptCondition(modelName, analysis, dataVar, userVar);
27
- }
28
-
29
- // Fallback to simple JavaScript generation
30
- const conditions = [];
31
-
32
- // Add filter-based conditions
33
- for (const filter of analysis.filters) {
34
- if (filter.type.startsWith('user_') || filter.type.startsWith('session_')) {
35
- // Dynamic user field comparison
36
- const userField = filter.userField || filter.type.replace(/^(user_|session_)/, '');
37
- conditions.push(`${dataVar}?.${filter.field} === ${userVar}?.${userField}`);
38
- } else {
39
- switch (filter.type) {
40
- case 'equal':
41
- if (isNaN(filter.value) && filter.value !== 'true' && filter.value !== 'false') {
42
- conditions.push(`${dataVar}?.${filter.field} === '${filter.value}'`);
43
- } else {
44
- conditions.push(`${dataVar}?.${filter.field} === ${filter.value}`);
45
- }
46
- break;
47
- case 'not_equal':
48
- conditions.push(`${dataVar}?.${filter.field} !== '${filter.value}'`);
49
- break;
50
- case 'is_null':
51
- conditions.push(`${dataVar}?.${filter.field} === null`);
52
- break;
53
- case 'not_null':
54
- conditions.push(`${dataVar}?.${filter.field} !== null`);
55
- break;
56
- case 'in':
57
- conditions.push(`[${filter.values.join(', ')}].includes(${dataVar}?.${filter.field})`);
58
- break;
59
- }
60
- }
61
- }
62
-
63
- // Add condition-based checks (roles, etc.)
64
- for (const condition of analysis.conditions) {
65
- if (condition.javascript) {
66
- // Replace user placeholder with actual variable
67
- const jsCondition = condition.javascript.replace(/user/g, userVar);
68
- conditions.push(jsCondition);
69
- } else if (condition.type === 'role_any') {
70
- conditions.push(`[${condition.roles.map(r => `'${r}'`).join(', ')}].includes(${userVar}?.role)`);
71
- } else if (condition.type === 'role_equal') {
72
- conditions.push(`${userVar}?.role === '${condition.role}'`);
73
- }
74
- }
75
-
76
- // Handle OR/AND logic in original SQL
77
- sql = sql.trim().replace(/\s+/g, ' ');
78
-
79
- // Check if the entire expression is OR'd
80
- if (sql.includes(' OR ') && !sql.includes(' AND ')) {
81
- // If we have multiple conditions and SQL uses OR, join with ||
82
- if (conditions.length > 1) {
83
- return conditions.join(' || ');
84
- }
85
- } else if (sql.includes(' AND ') && conditions.length > 1) {
86
- // If SQL uses AND, wrap in parentheses for clarity
87
- return '(' + conditions.join(' && ') + ')';
88
- }
89
-
90
- // Default: join with AND
91
- return conditions.length > 0 ? conditions.join(' && ') : 'true';
92
- }
93
-
94
- /**
95
- * Convert to Prisma filter with deep analysis
96
- */
97
- function convertToPrismaFilter(sql, userVar = 'user', modelName = null) {
98
- if (!sql || sql.trim() === '') return '{}';
99
-
100
- // Use deep analysis
101
- const analysis = analyzer.analyzeSQLForFilters(sql);
102
- analysis.sql = sql; // Store original SQL for OR/AND detection
103
-
104
- // If we have models and relationships, use the filter builder
105
- if (modelName && Object.keys(models).length > 0) {
106
- return filterBuilder.buildFilter(modelName, analysis, userVar);
107
- }
108
-
109
- // Fallback to simple filter generation
110
- const filters = [];
111
-
112
- for (const filter of analysis.filters) {
113
- if (filter.type.startsWith('user_') || filter.type.startsWith('session_')) {
114
- // Dynamic user field comparison
115
- const userField = filter.userField || filter.type.replace(/^(user_|session_)/, '');
116
- filters.push(`{ ${filter.field}: ${userVar}?.${userField} }`);
117
- } else {
118
- switch (filter.type) {
119
- case 'equal':
120
- if (isNaN(filter.value) && filter.value !== 'true' && filter.value !== 'false') {
121
- filters.push(`{ ${filter.field}: '${filter.value}' }`);
122
- } else {
123
- filters.push(`{ ${filter.field}: ${filter.value} }`);
124
- }
125
- break;
126
- case 'not_equal':
127
- filters.push(`{ ${filter.field}: { not: '${filter.value}' } }`);
128
- break;
129
- case 'is_null':
130
- filters.push(`{ ${filter.field}: null }`);
131
- break;
132
- case 'not_null':
133
- filters.push(`{ ${filter.field}: { not: null } }`);
134
- break;
135
- case 'in':
136
- filters.push(`{ ${filter.field}: { in: [${filter.values.join(', ')}] } }`);
137
- break;
138
- }
139
- }
140
- }
141
-
142
- // Role checks can't be directly filtered in Prisma (they're runtime checks)
143
- // But we can still return the data filters
144
-
145
- if (filters.length === 0) {
146
- return '{}';
147
- }
148
-
149
- if (filters.length === 1) {
150
- return filters[0];
151
- }
152
-
153
- // Check if original SQL uses OR or AND
154
- sql = sql.trim().replace(/\s+/g, ' ');
155
-
156
- if (sql.includes(' OR ') && !sql.includes(' AND ')) {
157
- // Use OR for multiple filters
158
- return `{ OR: [${filters.join(', ')}] }`;
159
- }
160
-
161
- // Default to AND
162
- return `{ AND: [${filters.join(', ')}] }`;
163
- }
164
-
165
- /**
166
- * Analyze and get user context requirements
167
- */
168
- function getUserContextRequirements(sql) {
169
- const analysis = analyzer.analyzeSQLForFilters(sql);
170
- return analysis.userContext || {};
171
- }
172
-
173
- return {
174
- convertToJavaScript,
175
- convertToPrismaFilter,
176
- getUserContextRequirements,
177
- analyzer // Expose analyzer for debugging
178
- };
179
- }
180
-
181
- module.exports = { createEnhancedConverter };
@@ -1,302 +0,0 @@
1
- /**
2
- * PostgreSQL Function Analyzer
3
- * Dynamically analyzes PostgreSQL functions and generates JavaScript mappings
4
- */
5
-
6
- const { Client } = require('pg');
7
-
8
- /**
9
- * Analyze all PostgreSQL functions used in RLS policies
10
- * @param {string} databaseUrl - PostgreSQL connection URL
11
- * @returns {Object} - Function mappings and metadata
12
- */
13
- async function analyzeFunctions(databaseUrl) {
14
- if (!databaseUrl) {
15
- return {
16
- functionMappings: {},
17
- sessionVariables: [],
18
- userContextRequirements: {}
19
- };
20
- }
21
-
22
- const client = new Client({ connectionString: databaseUrl });
23
-
24
- try {
25
- await client.connect();
26
-
27
- // Step 1: Find all functions used in RLS policies
28
- const functionsQuery = await client.query(`
29
- WITH rls_text AS (
30
- SELECT
31
- qual || ' ' || COALESCE(with_check, '') as policy_text
32
- FROM pg_policies
33
- WHERE schemaname = 'public'
34
- ),
35
- function_names AS (
36
- SELECT DISTINCT
37
- unnest(
38
- regexp_matches(
39
- policy_text,
40
- '(\\w+)\\s*\\(',
41
- 'g'
42
- )
43
- ) as function_name
44
- FROM rls_text
45
- )
46
- SELECT function_name
47
- FROM function_names
48
- WHERE function_name NOT IN ('SELECT', 'EXISTS', 'ANY', 'ARRAY', 'WHERE', 'AND', 'OR', 'NOT', 'IN', 'THEN', 'ELSE', 'CASE', 'WHEN', 'END')
49
- AND function_name NOT LIKE 'current_setting%'
50
- `);
51
-
52
- const functionNames = functionsQuery.rows.map(r => r.function_name);
53
-
54
- // Step 2: Analyze each function's definition
55
- const functionMappings = {};
56
- const userContextRequirements = {};
57
- const sessionVariables = new Set();
58
-
59
- for (const funcName of functionNames) {
60
- try {
61
- const funcDef = await client.query(`
62
- SELECT
63
- proname as name,
64
- prosrc as source,
65
- pg_get_functiondef(oid) as full_definition,
66
- prorettype::regtype as return_type
67
- FROM pg_proc
68
- WHERE proname = $1
69
- AND pronamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')
70
- `, [funcName]);
71
-
72
- if (funcDef.rows.length > 0) {
73
- const func = funcDef.rows[0];
74
- const analysis = analyzeFunctionBody(func.source, func.return_type);
75
-
76
- functionMappings[funcName] = analysis.mapping;
77
-
78
- // Track what this function requires
79
- if (analysis.requiresUserId) {
80
- userContextRequirements.id = true;
81
- }
82
- if (analysis.queriesTable) {
83
- userContextRequirements[analysis.returnField] = {
84
- table: analysis.queriesTable,
85
- lookupField: analysis.lookupField || 'user_id',
86
- description: `${funcName}() queries ${analysis.queriesTable} table`
87
- };
88
- }
89
-
90
- // Track session variables
91
- analysis.sessionVars.forEach(v => sessionVariables.add(v));
92
- }
93
- } catch (e) {
94
- console.warn(`Could not analyze function ${funcName}:`, e.message);
95
- }
96
- }
97
-
98
- // Step 3: Find all session variables used
99
- const settingsQuery = await client.query(`
100
- SELECT DISTINCT
101
- unnest(
102
- regexp_matches(
103
- pg_policies.qual || ' ' || COALESCE(pg_policies.with_check, ''),
104
- 'current_setting\\s*\\(\\s*''([^'']+)''',
105
- 'g'
106
- )
107
- ) as setting_name
108
- FROM pg_policies
109
- WHERE schemaname = 'public'
110
- `);
111
-
112
- settingsQuery.rows.forEach(r => sessionVariables.add(r.setting_name));
113
-
114
- await client.end();
115
-
116
- return {
117
- functionMappings,
118
- sessionVariables: Array.from(sessionVariables),
119
- userContextRequirements
120
- };
121
-
122
- } catch (error) {
123
- try {
124
- await client.end();
125
- } catch (e) {}
126
-
127
- console.warn('Could not analyze PostgreSQL functions:', error.message);
128
- return {
129
- functionMappings: {},
130
- sessionVariables: [],
131
- userContextRequirements: {}
132
- };
133
- }
134
- }
135
-
136
- /**
137
- * Analyze a PostgreSQL function body to understand what it does
138
- * @param {string} functionBody - The PL/pgSQL function source
139
- * @param {string} returnType - The return type of the function
140
- * @returns {Object} - Analysis results
141
- */
142
- function analyzeFunctionBody(functionBody, returnType) {
143
- const analysis = {
144
- mapping: null,
145
- requiresUserId: false,
146
- queriesTable: null,
147
- returnField: null,
148
- lookupField: null,
149
- sessionVars: []
150
- };
151
-
152
- if (!functionBody) return analysis;
153
-
154
- // Detect session variable usage
155
- const sessionMatches = functionBody.matchAll(/current_setting\s*\(\s*'([^']+)'/gi);
156
- for (const match of sessionMatches) {
157
- analysis.sessionVars.push(match[1]);
158
- if (match[1].includes('user_id')) {
159
- analysis.requiresUserId = true;
160
- }
161
- }
162
-
163
- // Detect SELECT statements to understand what table is queried
164
- const selectMatch = functionBody.match(/SELECT\s+(\w+)(?:\.(\w+))?\s+INTO\s+\w+\s+FROM\s+(\w+)/i);
165
- if (selectMatch) {
166
- const fieldOrAlias = selectMatch[1];
167
- const fieldName = selectMatch[2] || fieldOrAlias;
168
- const tableName = selectMatch[3];
169
-
170
- analysis.queriesTable = tableName;
171
-
172
- // Infer the return field name
173
- if (fieldName === 'id' || fieldOrAlias === 'id') {
174
- analysis.returnField = `${tableName}_id`;
175
- } else {
176
- analysis.returnField = fieldName;
177
- }
178
-
179
- // Detect the lookup condition
180
- const whereMatch = functionBody.match(/WHERE\s+\w+\.(\w+)\s*=\s*current_setting/i);
181
- if (whereMatch) {
182
- analysis.lookupField = whereMatch[1];
183
- }
184
-
185
- // Generate JavaScript mapping
186
- if (returnType.includes('int')) {
187
- analysis.mapping = {
188
- type: 'lookup',
189
- returns: `user?.${analysis.returnField}`,
190
- description: `Looks up ${tableName}.id where ${tableName}.${analysis.lookupField} = current_user_id`
191
- };
192
- } else if (returnType.includes('char') || returnType.includes('text')) {
193
- // For string returns (like role), map directly
194
- const userTableMatch = functionBody.match(/FROM\s+["']?user["']?/i);
195
- if (userTableMatch) {
196
- // Querying user table directly
197
- analysis.mapping = {
198
- type: 'direct',
199
- returns: `user?.${analysis.returnField}`,
200
- description: `Returns user.${analysis.returnField}`
201
- };
202
- } else {
203
- analysis.mapping = {
204
- type: 'lookup',
205
- returns: `user?.${analysis.returnField}`,
206
- description: `Looks up ${tableName}.${analysis.returnField}`
207
- };
208
- }
209
- }
210
- }
211
-
212
- // If we couldn't parse it, make a best guess based on function patterns
213
- if (!analysis.mapping) {
214
- const funcName = functionBody.match(/FUNCTION\s+(\w+)/i)?.[1] || '';
215
-
216
- if (funcName.includes('role')) {
217
- analysis.mapping = {
218
- type: 'inferred',
219
- returns: 'user?.role',
220
- description: 'Inferred from function name'
221
- };
222
- } else if (funcName.includes('_id')) {
223
- const entity = funcName.replace(/get_current_|_id/gi, '');
224
- analysis.mapping = {
225
- type: 'inferred',
226
- returns: `user?.${entity}_id`,
227
- description: 'Inferred from function name'
228
- };
229
- } else {
230
- analysis.mapping = {
231
- type: 'unknown',
232
- returns: 'null',
233
- description: 'Could not analyze function'
234
- };
235
- }
236
- }
237
-
238
- return analysis;
239
- }
240
-
241
- /**
242
- * Generate a function mapping configuration
243
- * This can be saved to a file for manual adjustment if needed
244
- */
245
- function generateMappingConfig(analysisResult) {
246
- const config = {
247
- // Metadata
248
- generated: new Date().toISOString(),
249
- source: 'PostgreSQL function analysis',
250
-
251
- // Function mappings
252
- functions: {},
253
-
254
- // Session variable mappings
255
- sessionVariables: {},
256
-
257
- // User context requirements
258
- userContext: {
259
- required: [],
260
- optional: [],
261
- relationships: {}
262
- }
263
- };
264
-
265
- // Build function mappings
266
- for (const [funcName, mapping] of Object.entries(analysisResult.functionMappings)) {
267
- config.functions[funcName] = {
268
- javascript: mapping.returns,
269
- description: mapping.description,
270
- type: mapping.type
271
- };
272
- }
273
-
274
- // Build session variable mappings
275
- for (const varName of analysisResult.sessionVariables) {
276
- if (varName.includes('user_id')) {
277
- config.sessionVariables[varName] = 'user.id';
278
- } else {
279
- // Infer from variable name
280
- const key = varName.split('.').pop().replace(/_/g, '');
281
- config.sessionVariables[varName] = `user.${key}`;
282
- }
283
- }
284
-
285
- // Build user context requirements
286
- for (const [field, requirement] of Object.entries(analysisResult.userContextRequirements)) {
287
- if (field === 'id') {
288
- config.userContext.required.push('id');
289
- } else {
290
- config.userContext.relationships[field] = requirement;
291
- config.userContext.optional.push(field);
292
- }
293
- }
294
-
295
- return config;
296
- }
297
-
298
- module.exports = {
299
- analyzeFunctions,
300
- analyzeFunctionBody,
301
- generateMappingConfig
302
- };