forge-sql-orm 2.1.18 → 2.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -8
- package/dist/async/index.d.ts +2 -0
- package/dist/async/index.d.ts.map +1 -0
- package/dist/async/index.js +18 -0
- package/dist/async/index.js.map +1 -0
- package/dist/core/ForgeSQLORM.d.ts +7 -7
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/core/ForgeSQLORM.js +2 -2
- package/dist/core/ForgeSQLORM.js.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.d.ts +55 -2
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.js.map +1 -1
- package/dist/core/Rovo.d.ts +104 -50
- package/dist/core/Rovo.d.ts.map +1 -1
- package/dist/core/Rovo.js +174 -59
- package/dist/core/Rovo.js.map +1 -1
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +21 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.d.ts +4 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -8
- package/dist/index.js.map +1 -1
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +18 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/utils/cacheContextUtils.js +1 -1
- package/dist/utils/cacheContextUtils.js.map +1 -1
- package/dist/utils/cacheUtils.d.ts.map +1 -1
- package/dist/utils/cacheUtils.js +1 -1
- package/dist/utils/cacheUtils.js.map +1 -1
- package/dist/utils/forgeDriver.js +2 -2
- package/dist/utils/forgeDriver.js.map +1 -1
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +19 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/sqlUtils.d.ts +11 -2
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist/utils/sqlUtils.js +30 -9
- package/dist/utils/sqlUtils.js.map +1 -1
- package/dist/webtriggers/clearCacheSchedulerTrigger.d.ts +2 -0
- package/dist/webtriggers/clearCacheSchedulerTrigger.d.ts.map +1 -1
- package/dist/webtriggers/clearCacheSchedulerTrigger.js +2 -0
- package/dist/webtriggers/clearCacheSchedulerTrigger.js.map +1 -1
- package/dist/webtriggers/dropMigrationWebTrigger.d.ts +3 -1
- package/dist/webtriggers/dropMigrationWebTrigger.d.ts.map +1 -1
- package/dist/webtriggers/dropMigrationWebTrigger.js +9 -3
- package/dist/webtriggers/dropMigrationWebTrigger.js.map +1 -1
- package/dist/webtriggers/dropTablesMigrationWebTrigger.d.ts +3 -1
- package/dist/webtriggers/dropTablesMigrationWebTrigger.d.ts.map +1 -1
- package/dist/webtriggers/dropTablesMigrationWebTrigger.js +7 -1
- package/dist/webtriggers/dropTablesMigrationWebTrigger.js.map +1 -1
- package/dist/webtriggers/fetchSchemaWebTrigger.d.ts +3 -1
- package/dist/webtriggers/fetchSchemaWebTrigger.d.ts.map +1 -1
- package/dist/webtriggers/fetchSchemaWebTrigger.js +8 -1
- package/dist/webtriggers/fetchSchemaWebTrigger.js.map +1 -1
- package/package.json +12 -11
- package/src/async/index.ts +1 -0
- package/src/core/ForgeSQLORM.ts +1 -1
- package/src/core/ForgeSQLQueryBuilder.ts +59 -2
- package/src/core/Rovo.ts +181 -59
- package/src/core/index.ts +4 -0
- package/src/index.ts +8 -8
- package/src/lib/index.ts +1 -0
- package/src/utils/cacheContextUtils.ts +1 -1
- package/src/utils/cacheUtils.ts +1 -3
- package/src/utils/forgeDriver.ts +2 -2
- package/src/utils/index.ts +2 -0
- package/src/utils/sqlUtils.ts +23 -4
- package/src/webtriggers/clearCacheSchedulerTrigger.ts +2 -0
- package/src/webtriggers/dropMigrationWebTrigger.ts +12 -3
- package/src/webtriggers/dropTablesMigrationWebTrigger.ts +11 -2
- package/src/webtriggers/fetchSchemaWebTrigger.ts +8 -1
package/src/core/ForgeSQLORM.ts
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
SelectAliasedDistinctType,
|
|
27
27
|
SelectAliasedType,
|
|
28
28
|
UpdateAndEvictCacheType,
|
|
29
|
-
} from "../lib
|
|
29
|
+
} from "../lib";
|
|
30
30
|
import { ForgeSQLAnalyseOperation } from "./ForgeSQLAnalyseOperations";
|
|
31
31
|
import { ForgeSQLCacheOperations } from "./ForgeSQLCacheOperations";
|
|
32
32
|
import { MySqlTable } from "drizzle-orm/mysql-core/table";
|
|
@@ -1143,14 +1143,71 @@ export interface RlsSettings {
|
|
|
1143
1143
|
* @interface RovoIntegrationSettingCreator
|
|
1144
1144
|
*/
|
|
1145
1145
|
export interface RovoIntegrationSettingCreator {
|
|
1146
|
+
/**
|
|
1147
|
+
* Adds a string context parameter for query substitution.
|
|
1148
|
+
* The value will be wrapped in single quotes in the SQL query.
|
|
1149
|
+
*
|
|
1150
|
+
* @param {string} parameterName - The parameter name to replace in the query (e.g., '{{projectKey}}')
|
|
1151
|
+
* @param {string} value - The string value to substitute for the parameter
|
|
1152
|
+
* @returns {RovoIntegrationSettingCreator} This builder instance for method chaining
|
|
1153
|
+
*
|
|
1154
|
+
* @example
|
|
1155
|
+
* ```typescript
|
|
1156
|
+
* builder.addStringContextParameter('{{projectKey}}', 'PROJ-123');
|
|
1157
|
+
* // In SQL: {{projectKey}} will be replaced with 'PROJ-123'
|
|
1158
|
+
* ```
|
|
1159
|
+
*/
|
|
1160
|
+
addStringContextParameter(parameterName: string, value: string): RovoIntegrationSettingCreator;
|
|
1161
|
+
/**
|
|
1162
|
+
* Adds a number context parameter for query substitution.
|
|
1163
|
+
* The value will be inserted as-is without quotes in the SQL query.
|
|
1164
|
+
*
|
|
1165
|
+
* @param {string} parameterName - The parameter name to replace in the query (e.g., '{{limit}}')
|
|
1166
|
+
* @param {number} value - The numeric value to substitute for the parameter
|
|
1167
|
+
* @returns {RovoIntegrationSettingCreator} This builder instance for method chaining
|
|
1168
|
+
*
|
|
1169
|
+
* @example
|
|
1170
|
+
* ```typescript
|
|
1171
|
+
* builder.addNumberContextParameter('{{limit}}', 100);
|
|
1172
|
+
* // In SQL: {{limit}} will be replaced with 100
|
|
1173
|
+
* ```
|
|
1174
|
+
*/
|
|
1175
|
+
addNumberContextParameter(parameterName: string, value: number): RovoIntegrationSettingCreator;
|
|
1176
|
+
/**
|
|
1177
|
+
* Adds a boolean context parameter for query substitution.
|
|
1178
|
+
* The value will be converted to 1 (true) or 0 (false) and inserted as a number.
|
|
1179
|
+
*
|
|
1180
|
+
* @param {string} parameterName - The parameter name to replace in the query (e.g., '{{isActive}}')
|
|
1181
|
+
* @param {boolean} value - The boolean value to substitute for the parameter
|
|
1182
|
+
* @returns {RovoIntegrationSettingCreator} This builder instance for method chaining
|
|
1183
|
+
*
|
|
1184
|
+
* @example
|
|
1185
|
+
* ```typescript
|
|
1186
|
+
* builder.addBooleanContextParameter('{{isActive}}', true);
|
|
1187
|
+
* // In SQL: {{isActive}} will be replaced with 1
|
|
1188
|
+
* ```
|
|
1189
|
+
*/
|
|
1190
|
+
addBooleanContextParameter(parameterName: string, value: boolean): RovoIntegrationSettingCreator;
|
|
1146
1191
|
/**
|
|
1147
1192
|
* Adds a context parameter for query substitution.
|
|
1193
|
+
* Context parameters are replaced in the SQL query before execution.
|
|
1148
1194
|
*
|
|
1149
|
-
* @param {string} parameterName - The parameter name to replace in the query
|
|
1195
|
+
* @param {string} parameterName - The parameter name to replace in the query (e.g., '{{projectKey}}')
|
|
1150
1196
|
* @param {string} value - The value to substitute for the parameter
|
|
1197
|
+
* @param {boolean} wrap - Whether to wrap the value in single quotes (true for strings, false for numbers)
|
|
1151
1198
|
* @returns {RovoIntegrationSettingCreator} This builder instance for method chaining
|
|
1199
|
+
*
|
|
1200
|
+
* @example
|
|
1201
|
+
* ```typescript
|
|
1202
|
+
* builder.addContextParameter('{{projectKey}}', 'PROJ-123', true);
|
|
1203
|
+
* // In SQL: {{projectKey}} will be replaced with 'PROJ-123'
|
|
1204
|
+
* ```
|
|
1152
1205
|
*/
|
|
1153
|
-
addContextParameter(
|
|
1206
|
+
addContextParameter(
|
|
1207
|
+
parameterName: string,
|
|
1208
|
+
value: string,
|
|
1209
|
+
wrap: boolean,
|
|
1210
|
+
): RovoIntegrationSettingCreator;
|
|
1154
1211
|
|
|
1155
1212
|
/**
|
|
1156
1213
|
* Enables Row-Level Security (RLS) for the query.
|
package/src/core/Rovo.ts
CHANGED
|
@@ -31,10 +31,10 @@ class RovoIntegrationSettingImpl implements RovoIntegrationSetting {
|
|
|
31
31
|
*
|
|
32
32
|
* @param {string} accountId - The account ID of the active user
|
|
33
33
|
* @param {string} tableName - The name of the table to query
|
|
34
|
-
* @param {Record<string, string>} contextParam - Context parameters for query substitution
|
|
34
|
+
* @param {Record<string, string>} contextParam - Context parameters for query substitution (parameter name -> value mapping)
|
|
35
35
|
* @param {boolean} rls - Whether Row-Level Security is enabled
|
|
36
36
|
* @param {string[]} rlsFields - Array of field names required for RLS validation
|
|
37
|
-
* @param {(alias: string) => string} rlsWherePart - Function that generates WHERE clause for RLS
|
|
37
|
+
* @param {(alias: string) => string} rlsWherePart - Function that generates WHERE clause for RLS filtering
|
|
38
38
|
*/
|
|
39
39
|
constructor(
|
|
40
40
|
accountId: string,
|
|
@@ -135,21 +135,83 @@ class RovoIntegrationSettingCreatorImpl implements RovoIntegrationSettingCreator
|
|
|
135
135
|
this.accountId = accountId;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Adds a string context parameter for query substitution.
|
|
140
|
+
* The value will be wrapped in single quotes in the SQL query.
|
|
141
|
+
*
|
|
142
|
+
* @param {string} parameterName - The parameter name to replace in the query (e.g., '{{projectKey}}')
|
|
143
|
+
* @param {string} value - The string value to substitute for the parameter
|
|
144
|
+
* @returns {RovoIntegrationSettingCreator} This builder instance for method chaining
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```typescript
|
|
148
|
+
* builder.addStringContextParameter('{{projectKey}}', 'PROJ-123');
|
|
149
|
+
* // In SQL: {{projectKey}} will be replaced with 'PROJ-123'
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
addStringContextParameter(parameterName: string, value: string): RovoIntegrationSettingCreator {
|
|
153
|
+
this.addContextParameter(parameterName, value, true);
|
|
154
|
+
return this;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Adds a number context parameter for query substitution.
|
|
159
|
+
* The value will be inserted as-is without quotes in the SQL query.
|
|
160
|
+
*
|
|
161
|
+
* @param {string} parameterName - The parameter name to replace in the query (e.g., '{{limit}}')
|
|
162
|
+
* @param {number} value - The numeric value to substitute for the parameter
|
|
163
|
+
* @returns {RovoIntegrationSettingCreator} This builder instance for method chaining
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* builder.addNumberContextParameter('{{limit}}', 100);
|
|
168
|
+
* // In SQL: {{limit}} will be replaced with 100
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
addNumberContextParameter(parameterName: string, value: number): RovoIntegrationSettingCreator {
|
|
172
|
+
this.addContextParameter(parameterName, String(value), false);
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Adds a boolean context parameter for query substitution.
|
|
178
|
+
* The value will be converted to 1 (true) or 0 (false) and inserted as a number.
|
|
179
|
+
*
|
|
180
|
+
* @param {string} parameterName - The parameter name to replace in the query (e.g., '{{isActive}}')
|
|
181
|
+
* @param {boolean} value - The boolean value to substitute for the parameter
|
|
182
|
+
* @returns {RovoIntegrationSettingCreator} This builder instance for method chaining
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```typescript
|
|
186
|
+
* builder.addBooleanContextParameter('{{isActive}}', true);
|
|
187
|
+
* // In SQL: {{isActive}} will be replaced with 1
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
addBooleanContextParameter(parameterName: string, value: boolean): RovoIntegrationSettingCreator {
|
|
191
|
+
this.addNumberContextParameter(parameterName, value ? 1 : 0);
|
|
192
|
+
return this;
|
|
193
|
+
}
|
|
138
194
|
/**
|
|
139
195
|
* Adds a context parameter for query substitution.
|
|
140
196
|
* Context parameters are replaced in the SQL query before execution.
|
|
141
197
|
*
|
|
142
|
-
* @param {string} parameterName - The parameter name to replace in the query
|
|
198
|
+
* @param {string} parameterName - The parameter name to replace in the query (e.g., '{{projectKey}}')
|
|
143
199
|
* @param {string} value - The value to substitute for the parameter
|
|
200
|
+
* @param {boolean} wrap - Whether to wrap the value in single quotes (true for strings, false for numbers)
|
|
144
201
|
* @returns {RovoIntegrationSettingCreator} This builder instance for method chaining
|
|
145
202
|
*
|
|
146
203
|
* @example
|
|
147
204
|
* ```typescript
|
|
148
|
-
* builder.addContextParameter('{{projectKey}}', 'PROJ-123');
|
|
205
|
+
* builder.addContextParameter('{{projectKey}}', 'PROJ-123', true);
|
|
206
|
+
* // In SQL: {{projectKey}} will be replaced with 'PROJ-123'
|
|
149
207
|
* ```
|
|
150
208
|
*/
|
|
151
|
-
addContextParameter(
|
|
152
|
-
|
|
209
|
+
addContextParameter(
|
|
210
|
+
parameterName: string,
|
|
211
|
+
value: string,
|
|
212
|
+
wrap: boolean,
|
|
213
|
+
): RovoIntegrationSettingCreator {
|
|
214
|
+
this.contextParam[parameterName] = wrap ? `'${value}'` : value;
|
|
153
215
|
return this;
|
|
154
216
|
}
|
|
155
217
|
|
|
@@ -179,6 +241,12 @@ class RovoIntegrationSettingCreatorImpl implements RovoIntegrationSettingCreator
|
|
|
179
241
|
private isUseRlsConditionalSettings: () => Promise<boolean> = async () => true;
|
|
180
242
|
private rlsFieldsSettings: string[] = [];
|
|
181
243
|
private wherePartSettings: (alias: string) => string = () => "";
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Creates a new RlsSettingsImpl instance.
|
|
247
|
+
*
|
|
248
|
+
* @param {RovoIntegrationSettingCreatorImpl} parent - The parent settings builder instance
|
|
249
|
+
*/
|
|
182
250
|
constructor(private readonly parent: RovoIntegrationSettingCreatorImpl) {}
|
|
183
251
|
/**
|
|
184
252
|
* Sets a conditional function to determine if RLS should be applied.
|
|
@@ -328,8 +396,8 @@ export class Rovo implements RovoIntegration {
|
|
|
328
396
|
/**
|
|
329
397
|
* Creates a new Rovo instance.
|
|
330
398
|
*
|
|
331
|
-
* @param {ForgeSqlOperation} forgeSqlOperations - The ForgeSQL operations instance for query analysis
|
|
332
|
-
* @param options - Configuration options for the ORM
|
|
399
|
+
* @param {ForgeSqlOperation} forgeSqlOperations - The ForgeSQL operations instance for query analysis and execution
|
|
400
|
+
* @param {ForgeSqlOrmOptions} options - Configuration options for the ORM (e.g., logging settings)
|
|
333
401
|
*/
|
|
334
402
|
constructor(forgeSqlOperations: ForgeSqlOperation, options: ForgeSqlOrmOptions) {
|
|
335
403
|
this.forgeOperations = forgeSqlOperations;
|
|
@@ -337,10 +405,11 @@ export class Rovo implements RovoIntegration {
|
|
|
337
405
|
}
|
|
338
406
|
|
|
339
407
|
/**
|
|
340
|
-
* Parses SQL query into AST and validates it's a single SELECT statement
|
|
341
|
-
*
|
|
342
|
-
* @
|
|
343
|
-
* @
|
|
408
|
+
* Parses SQL query into AST and validates it's a single SELECT statement.
|
|
409
|
+
*
|
|
410
|
+
* @param {string} sqlQuery - Normalized SQL query string
|
|
411
|
+
* @returns {Select} Parsed AST of the SELECT statement
|
|
412
|
+
* @throws {Error} If parsing fails or query is not a single SELECT statement
|
|
344
413
|
*/
|
|
345
414
|
private parseSqlQuery(sqlQuery: string): Select {
|
|
346
415
|
const parser = new Parser();
|
|
@@ -370,9 +439,10 @@ export class Rovo implements RovoIntegration {
|
|
|
370
439
|
}
|
|
371
440
|
|
|
372
441
|
/**
|
|
373
|
-
* Recursively processes array or single node and extracts
|
|
374
|
-
*
|
|
375
|
-
* @param
|
|
442
|
+
* Recursively processes array or single node and extracts table names.
|
|
443
|
+
*
|
|
444
|
+
* @param {any} items - Array of AST nodes or single AST node
|
|
445
|
+
* @param {string[]} tables - Accumulator array for collecting table names (modified in place)
|
|
376
446
|
*/
|
|
377
447
|
private extractTablesFromItems(items: any, tables: string[]): void {
|
|
378
448
|
if (Array.isArray(items)) {
|
|
@@ -385,9 +455,10 @@ export class Rovo implements RovoIntegration {
|
|
|
385
455
|
}
|
|
386
456
|
|
|
387
457
|
/**
|
|
388
|
-
* Extracts table name from table node
|
|
389
|
-
*
|
|
390
|
-
* @
|
|
458
|
+
* Extracts table name from table AST node.
|
|
459
|
+
*
|
|
460
|
+
* @param {any} node - AST node with table information
|
|
461
|
+
* @returns {string | null} Table name in uppercase, or null if not applicable (e.g., 'dual' table)
|
|
391
462
|
*/
|
|
392
463
|
private extractTableName(node: any): string | null {
|
|
393
464
|
if (!node.table) {
|
|
@@ -398,9 +469,11 @@ export class Rovo implements RovoIntegration {
|
|
|
398
469
|
}
|
|
399
470
|
|
|
400
471
|
/**
|
|
401
|
-
* Recursively extracts all table names from SQL AST node
|
|
402
|
-
*
|
|
403
|
-
*
|
|
472
|
+
* Recursively extracts all table names from SQL AST node.
|
|
473
|
+
* Traverses FROM and JOIN clauses to find all referenced tables.
|
|
474
|
+
*
|
|
475
|
+
* @param {any} node - AST node to extract tables from
|
|
476
|
+
* @returns {string[]} Array of table names in uppercase
|
|
404
477
|
*/
|
|
405
478
|
private extractTables(node: any): string[] {
|
|
406
479
|
const tables: string[] = [];
|
|
@@ -427,9 +500,11 @@ export class Rovo implements RovoIntegration {
|
|
|
427
500
|
}
|
|
428
501
|
|
|
429
502
|
/**
|
|
430
|
-
* Recursively checks if AST node contains scalar subqueries
|
|
431
|
-
*
|
|
432
|
-
*
|
|
503
|
+
* Recursively checks if AST node contains scalar subqueries.
|
|
504
|
+
* Used for security validation to prevent subquery-based attacks.
|
|
505
|
+
*
|
|
506
|
+
* @param {any} node - AST node to check for subqueries
|
|
507
|
+
* @returns {boolean} True if node contains scalar subquery, false otherwise
|
|
433
508
|
*/
|
|
434
509
|
private hasScalarSubquery(node: any): boolean {
|
|
435
510
|
if (!node) return false;
|
|
@@ -452,13 +527,16 @@ export class Rovo implements RovoIntegration {
|
|
|
452
527
|
/**
|
|
453
528
|
* Creates a settings builder for Rovo queries using a raw table name.
|
|
454
529
|
*
|
|
455
|
-
* @param {string} tableName - The name of the table to query
|
|
456
|
-
* @param {string} accountId - The account ID of the active user
|
|
530
|
+
* @param {string} tableName - The name of the table to query (case-insensitive)
|
|
531
|
+
* @param {string} accountId - The account ID of the active user for RLS filtering
|
|
457
532
|
* @returns {RovoIntegrationSettingCreator} Builder for configuring Rovo query settings
|
|
458
533
|
*
|
|
459
534
|
* @example
|
|
460
535
|
* ```typescript
|
|
461
536
|
* const builder = rovo.rovoRawSettingBuilder('users', accountId);
|
|
537
|
+
* const settings = await builder
|
|
538
|
+
* .addStringContextParameter('{{status}}', 'active')
|
|
539
|
+
* .build();
|
|
462
540
|
* ```
|
|
463
541
|
*/
|
|
464
542
|
rovoRawSettingBuilder(tableName: string, accountId: string): RovoIntegrationSettingCreator {
|
|
@@ -467,14 +545,21 @@ export class Rovo implements RovoIntegration {
|
|
|
467
545
|
|
|
468
546
|
/**
|
|
469
547
|
* Creates a settings builder for Rovo queries using a Drizzle table object.
|
|
548
|
+
* This is a convenience method that extracts the table name from the Drizzle table object.
|
|
470
549
|
*
|
|
471
550
|
* @param {AnyMySqlTable} table - The Drizzle table object
|
|
472
|
-
* @param {string} accountId - The account ID of the active user
|
|
551
|
+
* @param {string} accountId - The account ID of the active user for RLS filtering
|
|
473
552
|
* @returns {RovoIntegrationSettingCreator} Builder for configuring Rovo query settings
|
|
474
553
|
*
|
|
475
554
|
* @example
|
|
476
555
|
* ```typescript
|
|
477
556
|
* const builder = rovo.rovoSettingBuilder(usersTable, accountId);
|
|
557
|
+
* const settings = await builder
|
|
558
|
+
* .useRLS()
|
|
559
|
+
* .addRlsColumn(usersTable.id)
|
|
560
|
+
* .addRlsWherePart((alias) => `${alias}.userId = '${accountId}'`)
|
|
561
|
+
* .finish()
|
|
562
|
+
* .build();
|
|
478
563
|
* ```
|
|
479
564
|
*/
|
|
480
565
|
rovoSettingBuilder(table: AnyMySqlTable, accountId: string): RovoIntegrationSettingCreator {
|
|
@@ -482,33 +567,12 @@ export class Rovo implements RovoIntegration {
|
|
|
482
567
|
}
|
|
483
568
|
|
|
484
569
|
/**
|
|
485
|
-
*
|
|
486
|
-
*
|
|
487
|
-
* This method performs multiple security checks:
|
|
488
|
-
* 1. Validates that the query is a SELECT statement
|
|
489
|
-
* 2. Ensures the query targets only the specified table
|
|
490
|
-
* 3. Blocks JOINs, subqueries, and window functions
|
|
491
|
-
* 4. Applies Row-Level Security filtering if enabled
|
|
492
|
-
* 5. Validates query results to ensure security fields are present
|
|
493
|
-
*
|
|
494
|
-
* @param {string} dynamicSql - The SQL query to execute (must be a SELECT statement)
|
|
495
|
-
* @param {RovoIntegrationSetting} settings - Configuration settings for the query
|
|
496
|
-
* @returns {Promise<Result<unknown>>} Query execution result with metadata
|
|
497
|
-
* @throws {Error} If the query violates security restrictions
|
|
570
|
+
* Validates basic input parameters for the SQL query.
|
|
498
571
|
*
|
|
499
|
-
* @
|
|
500
|
-
*
|
|
501
|
-
*
|
|
502
|
-
*
|
|
503
|
-
* settings
|
|
504
|
-
* );
|
|
505
|
-
*
|
|
506
|
-
* console.log(result.rows); // Query results
|
|
507
|
-
* console.log(result.metadata); // Query metadata
|
|
508
|
-
* ```
|
|
509
|
-
*/
|
|
510
|
-
/**
|
|
511
|
-
* Validates basic input parameters
|
|
572
|
+
* @param {string} query - The SQL query string to validate
|
|
573
|
+
* @param {string} tableName - The expected table name
|
|
574
|
+
* @returns {string} The trimmed query string
|
|
575
|
+
* @throws {Error} If query is empty, table name is missing, or query is not a SELECT statement
|
|
512
576
|
*/
|
|
513
577
|
private validateInputs(query: string, tableName: string): string {
|
|
514
578
|
if (!query?.trim()) {
|
|
@@ -530,7 +594,12 @@ export class Rovo implements RovoIntegration {
|
|
|
530
594
|
}
|
|
531
595
|
|
|
532
596
|
/**
|
|
533
|
-
* Normalizes SQL query using AST parsing and stringification
|
|
597
|
+
* Normalizes SQL query using AST parsing and stringification.
|
|
598
|
+
* This ensures consistent formatting and validates the query structure.
|
|
599
|
+
*
|
|
600
|
+
* @param {string} sql - The SQL query string to normalize
|
|
601
|
+
* @returns {string} The normalized SQL query string
|
|
602
|
+
* @throws {Error} If parsing fails, query is not a SELECT statement, or multiple statements are detected
|
|
534
603
|
*/
|
|
535
604
|
private normalizeSqlString(sql: string): string {
|
|
536
605
|
try {
|
|
@@ -566,7 +635,12 @@ export class Rovo implements RovoIntegration {
|
|
|
566
635
|
}
|
|
567
636
|
|
|
568
637
|
/**
|
|
569
|
-
* Validates that query targets the correct table
|
|
638
|
+
* Validates that query targets the correct table.
|
|
639
|
+
* Checks that the FROM clause references only the expected table.
|
|
640
|
+
*
|
|
641
|
+
* @param {string} normalized - The normalized SQL query string
|
|
642
|
+
* @param {string} tableName - The expected table name
|
|
643
|
+
* @throws {Error} If query does not target the expected table
|
|
570
644
|
*/
|
|
571
645
|
private validateTableName(normalized: string, tableName: string): void {
|
|
572
646
|
const upperTableName = tableName.toUpperCase();
|
|
@@ -581,7 +655,12 @@ export class Rovo implements RovoIntegration {
|
|
|
581
655
|
}
|
|
582
656
|
|
|
583
657
|
/**
|
|
584
|
-
* Validates query structure
|
|
658
|
+
* Validates query structure for security compliance.
|
|
659
|
+
* Checks that only the specified table is referenced and no scalar subqueries are present.
|
|
660
|
+
*
|
|
661
|
+
* @param {Select} selectAst - The parsed SELECT AST node
|
|
662
|
+
* @param {string} tableName - The expected table name
|
|
663
|
+
* @throws {Error} If query references other tables or contains scalar subqueries
|
|
585
664
|
*/
|
|
586
665
|
private validateQueryStructure(selectAst: Select, tableName: string): void {
|
|
587
666
|
const upperTableName = tableName.toUpperCase();
|
|
@@ -616,7 +695,13 @@ export class Rovo implements RovoIntegration {
|
|
|
616
695
|
}
|
|
617
696
|
|
|
618
697
|
/**
|
|
619
|
-
* Validates query execution plan for security violations
|
|
698
|
+
* Validates query execution plan for security violations.
|
|
699
|
+
* Uses EXPLAIN to detect JOINs, window functions, and references to other tables.
|
|
700
|
+
*
|
|
701
|
+
* @param {string} normalized - The normalized SQL query string
|
|
702
|
+
* @param {string} tableName - The expected table name
|
|
703
|
+
* @returns {Promise<void>}
|
|
704
|
+
* @throws {Error} If execution plan reveals JOINs, window functions, or references to other tables
|
|
620
705
|
*/
|
|
621
706
|
private async validateExecutionPlan(normalized: string, tableName: string): Promise<void> {
|
|
622
707
|
const explainRows = await this.forgeOperations.analyze().explainRaw(normalized, []);
|
|
@@ -667,7 +752,12 @@ export class Rovo implements RovoIntegration {
|
|
|
667
752
|
}
|
|
668
753
|
|
|
669
754
|
/**
|
|
670
|
-
* Applies row-level security filtering to query
|
|
755
|
+
* Applies row-level security filtering to query.
|
|
756
|
+
* Wraps the original query in a subquery and adds a WHERE clause with RLS conditions.
|
|
757
|
+
*
|
|
758
|
+
* @param {string} normalized - The normalized SQL query string
|
|
759
|
+
* @param {RovoIntegrationSetting} settings - Rovo settings containing RLS configuration
|
|
760
|
+
* @returns {string} The SQL query with RLS filtering applied
|
|
671
761
|
*/
|
|
672
762
|
private applyRLSFiltering(normalized: string, settings: RovoIntegrationSetting): string {
|
|
673
763
|
if (normalized.endsWith(";")) {
|
|
@@ -684,7 +774,13 @@ export class Rovo implements RovoIntegration {
|
|
|
684
774
|
}
|
|
685
775
|
|
|
686
776
|
/**
|
|
687
|
-
* Validates query results for RLS compliance
|
|
777
|
+
* Validates query results for RLS compliance.
|
|
778
|
+
* Ensures that required RLS fields are present and all fields originate from the correct table.
|
|
779
|
+
*
|
|
780
|
+
* @param {Result<unknown>} result - The query execution result
|
|
781
|
+
* @param {RovoIntegrationSetting} settings - Rovo settings containing RLS field requirements
|
|
782
|
+
* @param {string} upperTableName - The expected table name in uppercase
|
|
783
|
+
* @throws {Error} If required RLS fields are missing or fields originate from other tables
|
|
688
784
|
*/
|
|
689
785
|
private validateQueryResults(
|
|
690
786
|
result: Result<unknown>,
|
|
@@ -731,6 +827,32 @@ export class Rovo implements RovoIntegration {
|
|
|
731
827
|
}
|
|
732
828
|
}
|
|
733
829
|
|
|
830
|
+
/**
|
|
831
|
+
* Executes a dynamic SQL query with comprehensive security validations.
|
|
832
|
+
*
|
|
833
|
+
* This method performs multiple security checks:
|
|
834
|
+
* 1. Validates that the query is a SELECT statement
|
|
835
|
+
* 2. Ensures the query targets only the specified table
|
|
836
|
+
* 3. Blocks JOINs, subqueries, and window functions
|
|
837
|
+
* 4. Applies Row-Level Security filtering if enabled
|
|
838
|
+
* 5. Validates query results to ensure security fields are present
|
|
839
|
+
*
|
|
840
|
+
* @param {string} dynamicSql - The SQL query to execute (must be a SELECT statement)
|
|
841
|
+
* @param {RovoIntegrationSetting} settings - Configuration settings for the query
|
|
842
|
+
* @returns {Promise<Result<unknown>>} Query execution result with metadata
|
|
843
|
+
* @throws {Error} If the query violates security restrictions, parsing fails, or validation errors occur
|
|
844
|
+
*
|
|
845
|
+
* @example
|
|
846
|
+
* ```typescript
|
|
847
|
+
* const result = await rovo.dynamicIsolatedQuery(
|
|
848
|
+
* "SELECT id, name, email FROM users WHERE status = 'active' ORDER BY name",
|
|
849
|
+
* settings
|
|
850
|
+
* );
|
|
851
|
+
*
|
|
852
|
+
* console.log(result.rows); // Query results
|
|
853
|
+
* console.log(result.metadata); // Query metadata
|
|
854
|
+
* ```
|
|
855
|
+
*/
|
|
734
856
|
async dynamicIsolatedQuery(
|
|
735
857
|
dynamicSql: string,
|
|
736
858
|
settings: RovoIntegrationSetting,
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export { default } from "./core/ForgeSQLORM";
|
|
2
2
|
|
|
3
|
-
export * from "./core
|
|
4
|
-
|
|
5
|
-
export * from "./
|
|
6
|
-
|
|
7
|
-
export * from "./utils/forgeDriver";
|
|
3
|
+
export * from "./core";
|
|
4
|
+
|
|
5
|
+
export * from "./utils";
|
|
6
|
+
|
|
8
7
|
export * from "./webtriggers";
|
|
9
|
-
|
|
10
|
-
export * from "./
|
|
11
|
-
|
|
8
|
+
|
|
9
|
+
export * from "./lib";
|
|
10
|
+
|
|
11
|
+
export * from "./async";
|
package/src/lib/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./drizzle/extensions/additionalActions";
|
|
@@ -161,7 +161,7 @@ export async function getQueryLocalCacheQuery<
|
|
|
161
161
|
}
|
|
162
162
|
const toSQL = sql.toSQL();
|
|
163
163
|
const key = hashKey(toSQL);
|
|
164
|
-
if (context
|
|
164
|
+
if (context?.cache[key]?.sql === toSQL.sql.toLowerCase()) {
|
|
165
165
|
if (options.logCache) {
|
|
166
166
|
const q = toSQL;
|
|
167
167
|
// eslint-disable-next-line no-console
|
package/src/utils/cacheUtils.ts
CHANGED
|
@@ -333,9 +333,7 @@ export async function getFromCache<T>(
|
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
try {
|
|
336
|
-
const cacheResult =
|
|
337
|
-
| CacheEntity
|
|
338
|
-
| undefined;
|
|
336
|
+
const cacheResult = await kvs.entity<CacheEntity>(options.cacheEntityName).get(key);
|
|
339
337
|
|
|
340
338
|
if (
|
|
341
339
|
cacheResult &&
|
package/src/utils/forgeDriver.ts
CHANGED
|
@@ -144,10 +144,10 @@ async function processAllMethod(
|
|
|
144
144
|
query: string,
|
|
145
145
|
params: unknown[] | undefined,
|
|
146
146
|
): Promise<ForgeDriverResult> {
|
|
147
|
-
const sqlStatement =
|
|
147
|
+
const sqlStatement = sql.prepare<unknown>(query);
|
|
148
148
|
|
|
149
149
|
if (params) {
|
|
150
|
-
|
|
150
|
+
sqlStatement.bindParams(...params);
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
const result = await withTimeout(sqlStatement.execute(), timeoutMessage, timeoutMs);
|
package/src/utils/sqlUtils.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
sql,
|
|
15
15
|
StringChunk,
|
|
16
16
|
} from "drizzle-orm";
|
|
17
|
-
import { AnyMySqlTable, MySqlCustomColumn } from "drizzle-orm/mysql-core
|
|
17
|
+
import { AnyMySqlTable, MySqlCustomColumn, MySqlTable } from "drizzle-orm/mysql-core";
|
|
18
18
|
import { DateTime } from "luxon";
|
|
19
19
|
import { PrimaryKeyBuilder } from "drizzle-orm/mysql-core/primary-keys";
|
|
20
20
|
import { AnyIndexBuilder } from "drizzle-orm/mysql-core/indexes";
|
|
@@ -22,13 +22,13 @@ import { CheckBuilder } from "drizzle-orm/mysql-core/checks";
|
|
|
22
22
|
import { ForeignKeyBuilder } from "drizzle-orm/mysql-core/foreign-keys";
|
|
23
23
|
import { UniqueConstraintBuilder } from "drizzle-orm/mysql-core/unique-constraint";
|
|
24
24
|
import { SelectedFields } from "drizzle-orm/mysql-core/query-builders/select.types";
|
|
25
|
-
import { MySqlTable } from "drizzle-orm/mysql-core";
|
|
26
25
|
import { isSQLWrapper } from "drizzle-orm/sql/sql";
|
|
27
|
-
import { clusterStatementsSummary, slowQuery } from "../core
|
|
28
|
-
import { ForgeSqlOperation } from "../core/ForgeSQLQueryBuilder";
|
|
26
|
+
import { clusterStatementsSummary, slowQuery, ForgeSqlOperation } from "../core";
|
|
29
27
|
import { ColumnDataType } from "drizzle-orm/column-builder";
|
|
30
28
|
import { AnyMySqlColumn } from "drizzle-orm/mysql-core/columns/common";
|
|
31
29
|
import type { ColumnBaseConfig } from "drizzle-orm/column";
|
|
30
|
+
import { getHttpResponse, TriggerResponse } from "../webtriggers";
|
|
31
|
+
import { getAppContext } from "@forge/api";
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* Interface representing table metadata information
|
|
@@ -969,3 +969,22 @@ export function withTidbHint<
|
|
|
969
969
|
// but returning TExpr keeps the column type info in downstream inference.
|
|
970
970
|
return sql`/*+ SET_VAR(tidb_session_alias=${sql.raw(SESSION_ALIAS_NAME_ORM)}) */ ${column}` as unknown as AnyMySqlColumn<TPartial>;
|
|
971
971
|
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* Checks if the current environment is production and returns an error response if so.
|
|
975
|
+
* This function is used to prevent development-only operations from running in production.
|
|
976
|
+
*
|
|
977
|
+
* @param functionName - The name of the function being checked (for logging purposes)
|
|
978
|
+
* @returns {TriggerResponse<string> | null} Returns error response if in production, null otherwise
|
|
979
|
+
*/
|
|
980
|
+
export function checkProductionEnvironment(functionName: string): TriggerResponse<string> | null {
|
|
981
|
+
const environmentType = getAppContext()?.environmentType;
|
|
982
|
+
if (environmentType === "PRODUCTION") {
|
|
983
|
+
// eslint-disable-next-line no-console
|
|
984
|
+
console.log(`${functionName} is disabled in production environment`);
|
|
985
|
+
return getHttpResponse<string>(500, `${functionName} is disabled in production environment`);
|
|
986
|
+
}
|
|
987
|
+
// eslint-disable-next-line no-console
|
|
988
|
+
console.log(`${functionName} triggered in ${environmentType}`);
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
@@ -7,6 +7,8 @@ import { ForgeSqlOrmOptions } from "../core/ForgeSQLQueryBuilder";
|
|
|
7
7
|
* This trigger should be configured as a Forge scheduler to automatically
|
|
8
8
|
* clean up expired cache entries based on their TTL (Time To Live).
|
|
9
9
|
*
|
|
10
|
+
* @note This function is automatically disabled in production environments and will return a 500 error if called.
|
|
11
|
+
*
|
|
10
12
|
* @param options - Optional ForgeSQL ORM configuration. If not provided,
|
|
11
13
|
* uses default cache settings with cacheEntityName: "cache"
|
|
12
14
|
* @returns Promise that resolves to HTTP response object
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { sql } from "@forge/sql";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
checkProductionEnvironment,
|
|
4
|
+
generateDropTableStatements as generateStatements,
|
|
5
|
+
} from "../utils/sqlUtils";
|
|
3
6
|
import { getHttpResponse, TriggerResponse } from "./index";
|
|
4
|
-
import { getTables } from "../core
|
|
7
|
+
import { getTables } from "../core";
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* ⚠️ DEVELOPMENT ONLY WEB TRIGGER ⚠️
|
|
@@ -15,9 +18,11 @@ import { getTables } from "../core/SystemTables";
|
|
|
15
18
|
* - It may affect application functionality
|
|
16
19
|
* - It could lead to data loss and system instability
|
|
17
20
|
*
|
|
21
|
+
* @note This function is automatically disabled in production environments and will return a 500 error if called.
|
|
22
|
+
*
|
|
18
23
|
* @returns {Promise<TriggerResponse<string>>} A response containing:
|
|
19
24
|
* - On success: 200 status with warning message about permanent deletion of tables and sequences
|
|
20
|
-
* - On failure: 500 status with error message
|
|
25
|
+
* - On failure: 500 status with error message (including when called in production)
|
|
21
26
|
*
|
|
22
27
|
* @example
|
|
23
28
|
* ```typescript
|
|
@@ -27,6 +32,10 @@ import { getTables } from "../core/SystemTables";
|
|
|
27
32
|
* ```
|
|
28
33
|
*/
|
|
29
34
|
export async function dropSchemaMigrations(): Promise<TriggerResponse<string>> {
|
|
35
|
+
const productionCheck = checkProductionEnvironment("dropSchemaMigrations");
|
|
36
|
+
if (productionCheck) {
|
|
37
|
+
return productionCheck;
|
|
38
|
+
}
|
|
30
39
|
try {
|
|
31
40
|
const tables = await getTables();
|
|
32
41
|
// Generate drop statements
|