@rapidd/build 1.2.3 → 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 -174
  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,554 +0,0 @@
1
- /**
2
- * Deep SQL Analyzer for PostgreSQL RLS Policies
3
- * Uses extensive regex patterns to extract meaning from SQL expressions
4
- */
5
-
6
- class DeepSQLAnalyzer {
7
- constructor() {
8
- // Common PostgreSQL function patterns mapped to user fields
9
- this.functionMappings = {
10
- // User ID functions
11
- 'get_current_user_id': 'id',
12
- 'current_user_id': 'id',
13
- 'auth.uid': 'id',
14
- 'auth.user_id': 'id',
15
-
16
- // Role functions
17
- 'get_current_user_role': 'role',
18
- 'current_user_role': 'role',
19
- 'current_role': 'role',
20
- 'auth.role': 'role',
21
-
22
- // Tenant functions
23
- 'get_current_tenant_id': 'tenant_id',
24
- 'current_tenant_id': 'tenant_id',
25
- 'current_tenant': 'tenant_id',
26
-
27
- // Organization functions
28
- 'get_current_org_id': 'org_id',
29
- 'current_org_id': 'org_id',
30
- 'current_organization_id': 'org_id',
31
-
32
- // Related entity functions
33
- 'get_current_student_id': 'student_id',
34
- 'current_student_id': 'student_id',
35
- 'get_student_id_for_user': 'student_id',
36
-
37
- 'get_current_teacher_id': 'teacher_id',
38
- 'current_teacher_id': 'teacher_id',
39
- 'get_teacher_id_for_user': 'teacher_id',
40
-
41
- 'get_current_employee_id': 'employee_id',
42
- 'current_employee_id': 'employee_id',
43
- 'get_employee_id_for_user': 'employee_id',
44
-
45
- 'get_current_customer_id': 'customer_id',
46
- 'current_customer_id': 'customer_id',
47
- 'get_customer_id_for_user': 'customer_id'
48
- };
49
-
50
- // Session variable mappings
51
- this.sessionMappings = {
52
- 'app.current_user_id': 'id',
53
- 'jwt.claims.sub': 'id',
54
- 'request.jwt.claims.sub': 'id',
55
- 'request.jwt.claim.sub': 'id',
56
-
57
- 'app.current_role': 'role',
58
- 'jwt.claims.role': 'role',
59
- 'request.jwt.claim.role': 'role',
60
-
61
- 'app.current_tenant': 'tenant_id',
62
- 'app.tenant_id': 'tenant_id',
63
- 'jwt.claims.tenant_id': 'tenant_id',
64
-
65
- 'app.org_id': 'org_id',
66
- 'app.organization_id': 'org_id'
67
- };
68
- }
69
-
70
- /**
71
- * Analyze SQL expression and extract Prisma filters
72
- */
73
- analyzeSQLForFilters(sql) {
74
- if (!sql || sql.trim() === '') {
75
- return { filters: [], conditions: [], userContext: {} };
76
- }
77
-
78
- const analysis = {
79
- filters: [],
80
- conditions: [],
81
- userContext: {}
82
- };
83
-
84
- // Normalize SQL
85
- sql = this.normalizeSql(sql);
86
-
87
- // Remove EXISTS subqueries with proper parentheses matching
88
- const sqlWithoutExists = this.removeExistsSubqueries(sql);
89
-
90
- // Extract different types of conditions (use sqlWithoutExists to avoid EXISTS internals)
91
- this.extractDirectComparisons(sqlWithoutExists, analysis);
92
- this.extractFunctionComparisons(sqlWithoutExists, analysis);
93
- this.extractSessionVariableComparisons(sqlWithoutExists, analysis);
94
- this.extractInClauses(sqlWithoutExists, analysis);
95
-
96
- // Extract EXISTS from original SQL
97
- this.extractExistsSubqueries(sql, analysis);
98
-
99
- // Extract from original SQL for these
100
- this.extractCaseWhenConditions(sql, analysis);
101
- this.extractRoleChecks(sql, analysis);
102
- this.extractComplexJoins(sql, analysis);
103
-
104
- return analysis;
105
- }
106
-
107
- /**
108
- * Normalize SQL for easier parsing
109
- */
110
- normalizeSql(sql) {
111
- let normalized = sql
112
- .replace(/\s+/g, ' ')
113
- .replace(/\n/g, ' ')
114
- .replace(/\t/g, ' ')
115
- .replace(/::character varying/gi, '') // Remove multi-word type casts first
116
- .replace(/::[\w_]+\s*\[\]/g, ']') // Replace ::type[] with just ] (preserve array bracket)
117
- .replace(/::[\w_]+/g, '') // Remove simple type casts
118
- .trim();
119
-
120
- // Remove balanced outer wrapping parentheses
121
- while (normalized.startsWith('(') && normalized.endsWith(')')) {
122
- const inner = normalized.slice(1, -1);
123
- // Count parentheses to check if outer pair is balanced
124
- let depth = 0;
125
- let isBalanced = true;
126
- for (let i = 0; i < inner.length; i++) {
127
- if (inner[i] === '(') depth++;
128
- if (inner[i] === ')') depth--;
129
- // If depth goes negative, the outer parens are needed
130
- if (depth < 0) {
131
- isBalanced = false;
132
- break;
133
- }
134
- }
135
- // Only remove if balanced and not a single function call
136
- if (isBalanced && depth === 0 && (inner.includes(' = ') || inner.includes(' AND ') || inner.includes(' OR '))) {
137
- normalized = inner.trim();
138
- } else {
139
- break;
140
- }
141
- }
142
-
143
- return normalized;
144
- }
145
-
146
- /**
147
- * Remove EXISTS subqueries with proper parentheses matching
148
- */
149
- removeExistsSubqueries(sql) {
150
- let result = sql;
151
- let changed = true;
152
-
153
- // Keep removing EXISTS clauses until none are left
154
- while (changed) {
155
- changed = false;
156
- const existsIndex = result.search(/EXISTS\s*\(/i);
157
-
158
- if (existsIndex !== -1) {
159
- // Find the matching closing parenthesis
160
- const startParen = result.indexOf('(', existsIndex);
161
- let depth = 1;
162
- let endParen = startParen + 1;
163
-
164
- while (endParen < result.length && depth > 0) {
165
- if (result[endParen] === '(') depth++;
166
- if (result[endParen] === ')') depth--;
167
- endParen++;
168
- }
169
-
170
- // Replace EXISTS(...) with 'true'
171
- result = result.substring(0, existsIndex) + 'true' + result.substring(endParen);
172
- changed = true;
173
- }
174
- }
175
-
176
- return result;
177
- }
178
-
179
- /**
180
- * Extract direct field comparisons
181
- */
182
- extractDirectComparisons(sql, analysis) {
183
- // Pattern: field = 'value' (with or without quotes on field name)
184
- const stringPattern = /(?:"?(\w+)"?)\s*=\s*'([^']+)'/gi;
185
- let match;
186
- while ((match = stringPattern.exec(sql)) !== null) {
187
- const field = match[1];
188
- const value = match[2];
189
-
190
- // Skip if field is actually a function
191
- if (field.toLowerCase().includes('current') || field.toLowerCase().includes('get')) {
192
- continue;
193
- }
194
-
195
- analysis.filters.push({
196
- type: 'equal',
197
- field: field,
198
- value: value,
199
- prisma: `{ ${field}: '${value}' }`
200
- });
201
- }
202
-
203
- // Pattern: field = number (with or without quotes on field name)
204
- const numberPattern = /(?:"?(\w+)"?)\s*=\s*(\d+)(?!\s*\))/gi;
205
- while ((match = numberPattern.exec(sql)) !== null) {
206
- const field = match[1];
207
- const value = match[2];
208
-
209
- // Skip if field is actually a function
210
- if (field.toLowerCase().includes('current') || field.toLowerCase().includes('get')) {
211
- continue;
212
- }
213
-
214
- analysis.filters.push({
215
- type: 'equal',
216
- field: field,
217
- value: value,
218
- prisma: `{ ${field}: ${value} }`
219
- });
220
- }
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
-
236
- // Pattern: field IS NULL
237
- const isNullPattern = /(?:"?(\w+)"?)\s+IS\s+NULL/gi;
238
- while ((match = isNullPattern.exec(sql)) !== null) {
239
- analysis.filters.push({
240
- type: 'is_null',
241
- field: match[1],
242
- prisma: `{ ${match[1]}: null }`
243
- });
244
- }
245
-
246
- // Pattern: field IS NOT NULL
247
- const isNotNullPattern = /(\w+)\s+IS\s+NOT\s+NULL/gi;
248
- while ((match = isNotNullPattern.exec(sql)) !== null) {
249
- analysis.filters.push({
250
- type: 'not_null',
251
- field: match[1],
252
- prisma: `{ ${match[1]}: { not: null } }`
253
- });
254
- }
255
- }
256
-
257
- /**
258
- * Extract function-based comparisons
259
- */
260
- extractFunctionComparisons(sql, analysis) {
261
- // Pattern: field = function() (with or without quotes on field name)
262
- const patterns = [
263
- /(?:"?(\w+)"?)\s*=\s*([\w.]+)\s*\(\s*\)/gi, // field = function()
264
- /([\w.]+)\s*\(\s*\)\s*=\s*(?:"?(\w+)"?)/gi // function() = field
265
- ];
266
-
267
- // Normalize dots in function names for lookup
268
- const normalizeFuncName = (name) => name.replace(/\./g, '_');
269
-
270
- for (let i = 0; i < patterns.length; i++) {
271
- const pattern = patterns[i];
272
- let match;
273
- while ((match = pattern.exec(sql)) !== null) {
274
- let field, funcName;
275
-
276
- // First pattern is: field = function()
277
- // Second pattern is: function() = field
278
- if (i === 0) {
279
- field = match[1];
280
- funcName = normalizeFuncName(match[2]);
281
- } else {
282
- funcName = normalizeFuncName(match[1]);
283
- field = match[2];
284
- }
285
-
286
- // Look up function mapping (check both with/without underscore and original)
287
- let userField = this.functionMappings[funcName] || this.functionMappings[funcName.toLowerCase()];
288
-
289
- // Also try the original name without normalization
290
- if (!userField) {
291
- const originalName = (i === 0 ? match[2] : match[1]);
292
- userField = this.functionMappings[originalName] || this.functionMappings[originalName.toLowerCase()];
293
- }
294
-
295
- if (userField) {
296
- // Skip if field is actually a function (e.g., both sides are functions)
297
- if (field.toLowerCase().includes('current') || field.toLowerCase().includes('get') || field.includes('(')) {
298
- continue;
299
- }
300
-
301
- // Skip if this is part of an ANY clause (handled by extractRoleChecks)
302
- if (sql.includes(match[0] + ' = ANY') || sql.includes(match[0] + '= ANY') ||
303
- sql.includes(match[0] + ' =ANY') || sql.includes(match[0] + '=ANY')) {
304
- continue;
305
- }
306
-
307
- analysis.filters.push({
308
- type: `user_${userField}`,
309
- field: field,
310
- userField: userField,
311
- prisma: `{ ${field}: user?.${userField} }`
312
- });
313
-
314
- // Track user context requirements
315
- const contextKey = `requires${userField.charAt(0).toUpperCase()}${userField.slice(1).replace(/_(.)/g, (_, c) => c.toUpperCase())}`;
316
- analysis.userContext[contextKey] = true;
317
- }
318
- }
319
- }
320
-
321
- // Pattern: function() = 'value'
322
- const funcValuePattern = /([\w.]+)\s*\(\s*\)\s*=\s*'([^']+)'/gi;
323
- let match;
324
- while ((match = funcValuePattern.exec(sql)) !== null) {
325
- const funcName = match[1].replace(/\./g, '_');
326
- const value = match[2];
327
-
328
- const userField = this.functionMappings[funcName] || this.functionMappings[funcName.toLowerCase()];
329
-
330
- if (userField === 'role') {
331
- analysis.conditions.push({
332
- type: 'role_equal',
333
- role: value,
334
- javascript: `user?.role === '${value}'`
335
- });
336
- analysis.userContext.requiresRole = true;
337
- }
338
- }
339
- }
340
-
341
- /**
342
- * Extract session variable comparisons
343
- */
344
- extractSessionVariableComparisons(sql, analysis) {
345
- // Pattern: field = current_setting('...')
346
- const patterns = [
347
- /(\w+)\s*=\s*(?:\(?\s*current_setting\s*\(\s*'([^']+)'[^)]*\)\s*\)?)/gi,
348
- /current_setting\s*\(\s*'([^']+)'[^)]*\)\s*=\s*(\w+)/gi
349
- ];
350
-
351
- for (let i = 0; i < patterns.length; i++) {
352
- const pattern = patterns[i];
353
- let match;
354
- while ((match = pattern.exec(sql)) !== null) {
355
- let field, setting;
356
-
357
- // First pattern: field = current_setting(...)
358
- // Second pattern: current_setting(...) = field
359
- if (i === 0) {
360
- field = match[1];
361
- setting = match[2];
362
- } else {
363
- setting = match[1];
364
- field = match[2];
365
- }
366
-
367
- const userField = this.sessionMappings[setting];
368
-
369
- if (userField) {
370
- // Skip if field is actually a function
371
- if (field.includes('(')) continue;
372
-
373
- analysis.filters.push({
374
- type: `session_${userField}`,
375
- field: field,
376
- userField: userField,
377
- prisma: `{ ${field}: user?.${userField} }`
378
- });
379
-
380
- // Track user context requirements
381
- const contextKey = `requires${userField.charAt(0).toUpperCase()}${userField.slice(1).replace(/_(.)/g, (_, c) => c.toUpperCase())}`;
382
- analysis.userContext[contextKey] = true;
383
- }
384
- }
385
- }
386
- }
387
-
388
- /**
389
- * Extract IN clauses
390
- */
391
- extractInClauses(sql, analysis) {
392
- // Pattern: field IN (values)
393
- const inPattern = /(\w+)\s+IN\s*\(([^)]+)\)/gi;
394
- let match;
395
-
396
- while ((match = inPattern.exec(sql)) !== null) {
397
- const field = match[1];
398
- const values = match[2];
399
-
400
- // Skip if field is a function
401
- if (field.toLowerCase().includes('current') || field.toLowerCase().includes('get')) {
402
- continue;
403
- }
404
-
405
- // Check if it's a subquery
406
- if (values.toLowerCase().includes('select')) {
407
- // Handle subquery
408
- analysis.conditions.push({
409
- type: 'in_subquery',
410
- field: field,
411
- subquery: values,
412
- prisma: `/* IN subquery for ${field} - requires manual implementation */`
413
- });
414
- } else {
415
- // Parse values
416
- const valueList = values
417
- .split(',')
418
- .map(v => v.trim().replace(/'/g, ''));
419
-
420
- const quotedValues = valueList.map(v =>
421
- isNaN(v) && v !== 'true' && v !== 'false' ? `'${v}'` : v
422
- );
423
-
424
- analysis.filters.push({
425
- type: 'in',
426
- field: field,
427
- values: quotedValues,
428
- prisma: `{ ${field}: { in: [${quotedValues.join(', ')}] } }`
429
- });
430
- }
431
- }
432
- }
433
-
434
- /**
435
- * Extract EXISTS subqueries
436
- */
437
- extractExistsSubqueries(sql, analysis) {
438
- const existsPattern = /EXISTS\s*\(([^)]+(?:\([^)]*\)[^)]*)*)\)/gi;
439
- let match;
440
-
441
- while ((match = existsPattern.exec(sql)) !== null) {
442
- const subquery = match[1];
443
-
444
- // Try to extract the table and join condition
445
- const fromMatch = subquery.match(/FROM\s+(\w+)/i);
446
- const whereMatch = subquery.match(/WHERE\s+(.+)/i);
447
-
448
- if (fromMatch) {
449
- const table = fromMatch[1];
450
- const condition = whereMatch ? whereMatch[1] : '';
451
-
452
- analysis.conditions.push({
453
- type: 'exists',
454
- table: table,
455
- condition: condition,
456
- prisma: `/* EXISTS check on ${table} - implement as relation check */`
457
- });
458
- }
459
- }
460
- }
461
-
462
- /**
463
- * Extract CASE WHEN conditions
464
- */
465
- extractCaseWhenConditions(sql, analysis) {
466
- const casePattern = /CASE\s+WHEN\s+([^THEN]+)\s+THEN\s+([^WHEN|ELSE|END]+)(?:\s+WHEN\s+([^THEN]+)\s+THEN\s+([^WHEN|ELSE|END]+))*(?:\s+ELSE\s+([^END]+))?\s+END/gi;
467
- let match;
468
-
469
- while ((match = casePattern.exec(sql)) !== null) {
470
- const conditions = [];
471
-
472
- // Extract WHEN conditions
473
- const whenPattern = /WHEN\s+([^THEN]+)\s+THEN\s+([^WHEN|ELSE|END]+)/gi;
474
- let whenMatch;
475
-
476
- while ((whenMatch = whenPattern.exec(match[0])) !== null) {
477
- const condition = whenMatch[1].trim();
478
- const result = whenMatch[2].trim();
479
-
480
- conditions.push({
481
- condition: condition,
482
- result: result
483
- });
484
- }
485
-
486
- if (conditions.length > 0) {
487
- analysis.conditions.push({
488
- type: 'case_when',
489
- conditions: conditions,
490
- prisma: `/* CASE WHEN logic - requires conditional implementation */`
491
- });
492
- }
493
- }
494
- }
495
-
496
- /**
497
- * Extract role-based checks
498
- */
499
- extractRoleChecks(sql, analysis) {
500
- // Pattern: (function()) = ANY((ARRAY[...])) or function() = ANY(ARRAY[...])
501
- // Handle optional wrapping parens around function and multiple parens around ARRAY
502
- const anyArrayPattern = /\(?([\w.]+)\s*\(\s*\)\)?\s*=\s*ANY\s*\(+\s*(?:ARRAY\s*)?\[([^\]]+)\]/gi;
503
- let match;
504
-
505
- while ((match = anyArrayPattern.exec(sql)) !== null) {
506
- const funcName = match[1].replace(/\./g, '_');
507
- const values = match[2];
508
-
509
- const userField = this.functionMappings[funcName] || this.functionMappings[funcName.toLowerCase()];
510
-
511
- if (userField === 'role') {
512
- const roles = values
513
- .split(',')
514
- .map(r => r.trim().replace(/'/g, ''));
515
-
516
- analysis.conditions.push({
517
- type: 'role_any',
518
- roles: roles,
519
- javascript: `[${roles.map(r => `'${r}'`).join(', ')}].includes(user?.role)`
520
- });
521
- analysis.userContext.requiresRole = true;
522
- }
523
- }
524
- }
525
-
526
- /**
527
- * Extract complex JOIN conditions
528
- */
529
- extractComplexJoins(sql, analysis) {
530
- // Look for patterns that suggest relationships
531
- const relationPatterns = [
532
- // user owns resource through intermediate table
533
- /(\w+)\s+IN\s*\(\s*SELECT\s+\w+\s+FROM\s+(\w+)\s+WHERE\s+(\w+)=/gi,
534
- // team/group membership
535
- /(\w+)\.(\w+)\s+IN\s*\(\s*SELECT\s+\w+\s+FROM\s+(\w+_members?)\s+WHERE/gi
536
- ];
537
-
538
- for (const pattern of relationPatterns) {
539
- let match;
540
- while ((match = pattern.exec(sql)) !== null) {
541
- if (match[2]) {
542
- analysis.conditions.push({
543
- type: 'relation',
544
- table: match[2],
545
- field: match[1],
546
- prisma: `/* Relation check through ${match[2]} table */`
547
- });
548
- }
549
- }
550
- }
551
- }
552
- }
553
-
554
- module.exports = DeepSQLAnalyzer;