forge-sql-orm 2.1.15 → 2.1.17

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 (57) hide show
  1. package/README.md +194 -1
  2. package/dist/async/PrintQueryConsumer.d.ts +98 -0
  3. package/dist/async/PrintQueryConsumer.d.ts.map +1 -0
  4. package/dist/async/PrintQueryConsumer.js +89 -0
  5. package/dist/async/PrintQueryConsumer.js.map +1 -0
  6. package/dist/core/ForgeSQLQueryBuilder.d.ts +2 -3
  7. package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
  8. package/dist/core/ForgeSQLQueryBuilder.js.map +1 -1
  9. package/dist/core/ForgeSQLSelectOperations.d.ts +2 -1
  10. package/dist/core/ForgeSQLSelectOperations.d.ts.map +1 -1
  11. package/dist/core/ForgeSQLSelectOperations.js.map +1 -1
  12. package/dist/core/Rovo.d.ts +40 -0
  13. package/dist/core/Rovo.d.ts.map +1 -1
  14. package/dist/core/Rovo.js +164 -138
  15. package/dist/core/Rovo.js.map +1 -1
  16. package/dist/index.d.ts +2 -2
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +4 -2
  19. package/dist/index.js.map +1 -1
  20. package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
  21. package/dist/lib/drizzle/extensions/additionalActions.js +72 -22
  22. package/dist/lib/drizzle/extensions/additionalActions.js.map +1 -1
  23. package/dist/utils/cacheTableUtils.d.ts +11 -0
  24. package/dist/utils/cacheTableUtils.d.ts.map +1 -0
  25. package/dist/utils/cacheTableUtils.js +450 -0
  26. package/dist/utils/cacheTableUtils.js.map +1 -0
  27. package/dist/utils/cacheUtils.d.ts.map +1 -1
  28. package/dist/utils/cacheUtils.js +3 -22
  29. package/dist/utils/cacheUtils.js.map +1 -1
  30. package/dist/utils/forgeDriver.d.ts.map +1 -1
  31. package/dist/utils/forgeDriver.js +5 -12
  32. package/dist/utils/forgeDriver.js.map +1 -1
  33. package/dist/utils/forgeDriverProxy.js +7 -5
  34. package/dist/utils/forgeDriverProxy.js.map +1 -1
  35. package/dist/utils/metadataContextUtils.d.ts +44 -4
  36. package/dist/utils/metadataContextUtils.d.ts.map +1 -1
  37. package/dist/utils/metadataContextUtils.js +155 -50
  38. package/dist/utils/metadataContextUtils.js.map +1 -1
  39. package/dist/utils/sqlUtils.d.ts +3 -1
  40. package/dist/utils/sqlUtils.d.ts.map +1 -1
  41. package/dist/utils/sqlUtils.js +264 -144
  42. package/dist/utils/sqlUtils.js.map +1 -1
  43. package/dist/webtriggers/applyMigrationsWebTrigger.js +1 -1
  44. package/package.json +14 -13
  45. package/src/async/PrintQueryConsumer.ts +114 -0
  46. package/src/core/ForgeSQLQueryBuilder.ts +2 -2
  47. package/src/core/ForgeSQLSelectOperations.ts +2 -1
  48. package/src/core/Rovo.ts +209 -167
  49. package/src/index.ts +2 -3
  50. package/src/lib/drizzle/extensions/additionalActions.ts +98 -42
  51. package/src/utils/cacheTableUtils.ts +511 -0
  52. package/src/utils/cacheUtils.ts +3 -25
  53. package/src/utils/forgeDriver.ts +5 -11
  54. package/src/utils/forgeDriverProxy.ts +9 -9
  55. package/src/utils/metadataContextUtils.ts +169 -52
  56. package/src/utils/sqlUtils.ts +372 -177
  57. package/src/webtriggers/applyMigrationsWebTrigger.ts +1 -1
package/src/core/Rovo.ts CHANGED
@@ -168,7 +168,6 @@ class RovoIntegrationSettingCreatorImpl implements RovoIntegrationSettingCreator
168
168
  * ```
169
169
  */
170
170
  useRLS(): RlsSettings {
171
- const _this = this;
172
171
  /**
173
172
  * Internal implementation of RlsSettings interface.
174
173
  * Provides fluent API for configuring Row-Level Security settings.
@@ -180,7 +179,7 @@ class RovoIntegrationSettingCreatorImpl implements RovoIntegrationSettingCreator
180
179
  private isUseRlsConditionalSettings: () => Promise<boolean> = async () => true;
181
180
  private rlsFieldsSettings: string[] = [];
182
181
  private wherePartSettings: (alias: string) => string = () => "";
183
-
182
+ constructor(private readonly parent: RovoIntegrationSettingCreatorImpl) {}
184
183
  /**
185
184
  * Sets a conditional function to determine if RLS should be applied.
186
185
  *
@@ -255,13 +254,13 @@ class RovoIntegrationSettingCreatorImpl implements RovoIntegrationSettingCreator
255
254
  * @returns {RovoIntegrationSettingCreator} The parent settings builder
256
255
  */
257
256
  finish(): RovoIntegrationSettingCreator {
258
- _this.isUseRls = true;
259
- this.rlsFieldsSettings.forEach((columnName) => _this.rlsFields.push(columnName));
260
- _this.wherePart = this.wherePartSettings;
261
- _this.isUseRlsConditional = this.isUseRlsConditionalSettings;
262
- return _this;
257
+ this.parent.isUseRls = true;
258
+ this.rlsFieldsSettings.forEach((columnName) => this.parent.rlsFields.push(columnName));
259
+ this.parent.wherePart = this.wherePartSettings;
260
+ this.parent.isUseRlsConditional = this.isUseRlsConditionalSettings;
261
+ return this.parent;
263
262
  }
264
- })();
263
+ })(this);
265
264
  }
266
265
 
267
266
  /**
@@ -363,13 +362,41 @@ export class Rovo implements RovoIntegration {
363
362
  );
364
363
  }
365
364
  return ast[0];
366
- } else if (ast && ast.type === "select") {
365
+ } else if (ast?.type === "select") {
367
366
  return ast;
368
367
  } else {
369
368
  throw new Error("Only SELECT queries are allowed.");
370
369
  }
371
370
  }
372
371
 
372
+ /**
373
+ * Recursively processes array or single node and extracts tables
374
+ * @param items - Array of nodes or single node
375
+ * @param tables - Accumulator array for table names
376
+ */
377
+ private extractTablesFromItems(items: any, tables: string[]): void {
378
+ if (Array.isArray(items)) {
379
+ items.forEach((item: any) => {
380
+ tables.push(...this.extractTables(item));
381
+ });
382
+ } else {
383
+ tables.push(...this.extractTables(items));
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Extracts table name from table node
389
+ * @param node - AST node with table information
390
+ * @returns Table name in uppercase or null if not applicable
391
+ */
392
+ private extractTableName(node: any): string | null {
393
+ if (!node.table) {
394
+ return null;
395
+ }
396
+ const tableName = node.table === "dual" ? "dual" : node.table.name || node.table;
397
+ return tableName && tableName !== "dual" ? tableName.toUpperCase() : null;
398
+ }
399
+
373
400
  /**
374
401
  * Recursively extracts all table names from SQL AST node
375
402
  * @param node - AST node to extract tables from
@@ -378,33 +405,22 @@ export class Rovo implements RovoIntegration {
378
405
  private extractTables(node: any): string[] {
379
406
  const tables: string[] = [];
380
407
 
408
+ // Extract table name if node is a table type
381
409
  if (node.type === "table" || node.type === "dual") {
382
- if (node.table) {
383
- const tableName = node.table === "dual" ? "dual" : node.table.name || node.table;
384
- if (tableName && tableName !== "dual") {
385
- tables.push(tableName.toUpperCase());
386
- }
410
+ const tableName = this.extractTableName(node);
411
+ if (tableName) {
412
+ tables.push(tableName);
387
413
  }
388
414
  }
389
415
 
416
+ // Extract tables from FROM clause
390
417
  if (node.from) {
391
- if (Array.isArray(node.from)) {
392
- node.from.forEach((fromItem: any) => {
393
- tables.push(...this.extractTables(fromItem));
394
- });
395
- } else {
396
- tables.push(...this.extractTables(node.from));
397
- }
418
+ this.extractTablesFromItems(node.from, tables);
398
419
  }
399
420
 
421
+ // Extract tables from JOIN clause
400
422
  if (node.join) {
401
- if (Array.isArray(node.join)) {
402
- node.join.forEach((joinItem: any) => {
403
- tables.push(...this.extractTables(joinItem));
404
- });
405
- } else {
406
- tables.push(...this.extractTables(node.join));
407
- }
423
+ this.extractTablesFromItems(node.join, tables);
408
424
  }
409
425
 
410
426
  return tables;
@@ -418,7 +434,7 @@ export class Rovo implements RovoIntegration {
418
434
  private hasScalarSubquery(node: any): boolean {
419
435
  if (!node) return false;
420
436
 
421
- if (node.type === "subquery" || (node.ast && node.ast.type === "select")) {
437
+ if (node.type === "subquery" || node.ast?.type === "select") {
422
438
  return true;
423
439
  }
424
440
 
@@ -491,23 +507,17 @@ export class Rovo implements RovoIntegration {
491
507
  * console.log(result.metadata); // Query metadata
492
508
  * ```
493
509
  */
494
- async dynamicIsolatedQuery(
495
- dynamicSql: string,
496
- settings: RovoIntegrationSetting,
497
- ): Promise<Result<unknown>> {
498
- const query: string = dynamicSql;
499
- const tableName = settings.getTableName();
500
- const accountId = settings.getActiveUser();
501
- const parameters = settings.getParameters();
502
- if (!query || !query.trim()) {
510
+ /**
511
+ * Validates basic input parameters
512
+ */
513
+ private validateInputs(query: string, tableName: string): string {
514
+ if (!query?.trim()) {
503
515
  throw new Error("SQL query is required. Please provide a valid SELECT query.");
504
516
  }
505
517
  if (!tableName) {
506
518
  throw new Error("Table Name is required. Please provide a valid Table Name.");
507
519
  }
508
520
 
509
- // Quick validation: check if query starts with SELECT (case-insensitive)
510
- // This allows us to fail fast for non-SELECT queries before normalization
511
521
  const trimmedQuery = query.trim();
512
522
  const quickUpper = trimmedQuery.toUpperCase();
513
523
  if (!quickUpper.startsWith("SELECT")) {
@@ -516,78 +526,50 @@ export class Rovo implements RovoIntegration {
516
526
  );
517
527
  }
518
528
 
519
- /**
520
- * Normalizes SQL query using AST parsing and stringification.
521
- * This approach is safer than regex-based normalization as it:
522
- * - Avoids regex backtracking vulnerabilities
523
- * - Preserves SQL semantics correctly
524
- * - Handles complex SQL structures properly
525
- *
526
- * @param sql - SQL query string to normalize (must be a valid SELECT query)
527
- * @returns Normalized SQL string
528
- * @throws Error if parsing fails or query is invalid
529
- */
530
- const normalizeSqlString = (sql: string): string => {
531
- try {
532
- const parser = new Parser();
533
- // Parse SQL to AST
534
- const ast = parser.astify(sql.trim());
535
- // Validate it's a SELECT query before normalizing
536
- if (Array.isArray(ast)) {
537
- if (ast.length !== 1 || ast[0].type !== "select") {
538
- throw new Error(
539
- "Only a single SELECT query is allowed. Multiple statements or non-SELECT statements are not permitted.",
540
- );
541
- }
542
- } else if (ast && ast.type !== "select") {
543
- throw new Error("Only SELECT queries are allowed.");
544
- }
545
- // Convert AST back to SQL (this normalizes formatting)
546
- const normalized = parser.sqlify(Array.isArray(ast) ? ast[0] : ast);
547
- // trim
548
- return normalized.trim();
549
- } catch (error: any) {
550
- // If it's a validation error we threw, re-throw it
551
- if (
552
- error.message &&
553
- (error.message.includes("Only") || error.message.includes("single SELECT"))
554
- ) {
555
- throw error;
556
- }
557
- // For parsing errors, wrap them in a more user-friendly message
558
- // Check if error is already wrapped to avoid double wrapping
559
- if (error.message && error.message.includes("SQL parsing error")) {
560
- throw error;
529
+ return trimmedQuery;
530
+ }
531
+
532
+ /**
533
+ * Normalizes SQL query using AST parsing and stringification
534
+ */
535
+ private normalizeSqlString(sql: string): string {
536
+ try {
537
+ const parser = new Parser();
538
+ const ast = parser.astify(sql.trim());
539
+
540
+ if (Array.isArray(ast)) {
541
+ if (ast.length !== 1 || ast[0].type !== "select") {
542
+ throw new Error(
543
+ "Only a single SELECT query is allowed. Multiple statements or non-SELECT statements are not permitted.",
544
+ );
561
545
  }
562
- throw new Error(
563
- `SQL parsing error: ${error.message || "Invalid SQL syntax"}. Please check your query syntax.`,
564
- );
546
+ } else if (ast && ast.type !== "select") {
547
+ throw new Error("Only SELECT queries are allowed.");
565
548
  }
566
- };
567
- let normalized: string;
568
- try {
569
- normalized = normalizeSqlString(trimmedQuery);
549
+
550
+ const normalized = parser.sqlify(Array.isArray(ast) ? ast[0] : ast);
551
+ return normalized.trim();
570
552
  } catch (error: any) {
571
- // Re-throw validation errors as-is
572
553
  if (
573
554
  error.message &&
574
555
  (error.message.includes("Only") || error.message.includes("single SELECT"))
575
556
  ) {
576
557
  throw error;
577
558
  }
578
- // Check if error is already wrapped to avoid double wrapping
579
- if (error.message && error.message.includes("SQL parsing error")) {
559
+ if (error.message?.includes("SQL parsing error")) {
580
560
  throw error;
581
561
  }
582
- // For other errors, wrap them
583
562
  throw new Error(
584
563
  `SQL parsing error: ${error.message || "Invalid SQL syntax"}. Please check your query syntax.`,
585
564
  );
586
565
  }
566
+ }
587
567
 
568
+ /**
569
+ * Validates that query targets the correct table
570
+ */
571
+ private validateTableName(normalized: string, tableName: string): void {
588
572
  const upperTableName = tableName.toUpperCase();
589
- // Validate table name
590
- // sqlify may add backticks, so we check for both formats: FROM table_name and FROM `table_name`
591
573
  const tableNamePattern = new RegExp(`FROM\\s+[\`]?${upperTableName}[\`]?`, "i");
592
574
  if (!tableNamePattern.test(normalized)) {
593
575
  throw new Error(
@@ -596,25 +578,15 @@ export class Rovo implements RovoIntegration {
596
578
  "' table only. Other tables are not accessible.",
597
579
  );
598
580
  }
581
+ }
599
582
 
600
- if (!accountId) {
601
- throw new Error(
602
- "Authentication error: User account ID is missing. Please ensure you are logged in.",
603
- );
604
- }
605
- normalized = normalized.replaceAll("ari:cloud:identity::user/", "");
606
- Object.entries(parameters).forEach(([key, value]) => {
607
- normalized = normalized.replaceAll(key, value);
608
- });
609
-
610
- // Parse SQL query to validate structure before execution
611
- const selectAst = this.parseSqlQuery(normalized);
612
-
613
- // Extract all tables from the query
583
+ /**
584
+ * Validates query structure (tables, subqueries)
585
+ */
586
+ private validateQueryStructure(selectAst: Select, tableName: string): void {
587
+ const upperTableName = tableName.toUpperCase();
614
588
  const tablesInQuery = this.extractTables(selectAst);
615
589
  const uniqueTables = [...new Set(tablesInQuery)];
616
-
617
- // Check that only table is used
618
590
  const invalidTables = uniqueTables.filter((table) => table !== upperTableName);
619
591
 
620
592
  if (invalidTables.length > 0) {
@@ -625,7 +597,6 @@ export class Rovo implements RovoIntegration {
625
597
  );
626
598
  }
627
599
 
628
- // Check for scalar subqueries in SELECT columns
629
600
  if (selectAst.columns && Array.isArray(selectAst.columns)) {
630
601
  const hasSubqueryInColumns = selectAst.columns.some((col: any) => {
631
602
  if (col.expr) {
@@ -642,8 +613,12 @@ export class Rovo implements RovoIntegration {
642
613
  );
643
614
  }
644
615
  }
616
+ }
645
617
 
646
- // Check for JOIN operations using EXPLAIN
618
+ /**
619
+ * Validates query execution plan for security violations
620
+ */
621
+ private async validateExecutionPlan(normalized: string, tableName: string): Promise<void> {
647
622
  const explainRows = await this.forgeOperations.analyze().explainRaw(normalized, []);
648
623
 
649
624
  const hasJoin = explainRows.some((row) => {
@@ -664,9 +639,6 @@ export class Rovo implements RovoIntegration {
664
639
  );
665
640
  }
666
641
 
667
- // Detect window functions (e.g., COUNT(*) OVER(...), ROW_NUMBER() OVER(...))
668
- // Window functions are not allowed for security
669
- // Users should use regular aggregate functions with GROUP BY instead
670
642
  const hasWindow = explainRows.some((row) => {
671
643
  const id = row.id.toUpperCase();
672
644
  const info = (row.operatorInfo ?? "").toUpperCase();
@@ -680,8 +652,6 @@ export class Rovo implements RovoIntegration {
680
652
  );
681
653
  }
682
654
 
683
- // Check for references to other tables in the query execution plan
684
- // This detects JOINs, subqueries, or any other references to tables other than expected
685
655
  const tablesInPlan = explainRows.filter(
686
656
  (row) =>
687
657
  row.accessObject?.startsWith("table:") &&
@@ -694,70 +664,142 @@ export class Rovo implements RovoIntegration {
694
664
  "JOINs, subqueries, or references to other tables are not permitted for security reasons.",
695
665
  );
696
666
  }
667
+ }
697
668
 
698
- // row-level security protection
699
- const isUseRLSFiltering = settings.isUseRLS();
700
- if (isUseRLSFiltering) {
701
- if (normalized.endsWith(";")) {
702
- normalized = normalized.slice(0, -1);
703
- }
669
+ /**
670
+ * Applies row-level security filtering to query
671
+ */
672
+ private applyRLSFiltering(normalized: string, settings: RovoIntegrationSetting): string {
673
+ if (normalized.endsWith(";")) {
674
+ normalized = normalized.slice(0, -1);
675
+ }
704
676
 
705
- normalized = `
677
+ return `
706
678
  SELECT *
707
679
  FROM (
708
680
  ${normalized}
709
681
  ) AS t
710
682
  WHERE (${settings.userScopeWhere("t")})
711
683
  `;
684
+ }
685
+
686
+ /**
687
+ * Validates query results for RLS compliance
688
+ */
689
+ private validateQueryResults(
690
+ result: Result<unknown>,
691
+ settings: RovoIntegrationSetting,
692
+ upperTableName: string,
693
+ ): void {
694
+ if (!result?.metadata?.fields) {
695
+ return;
712
696
  }
713
- if (this.options.logRawSqlQuery) {
714
- // eslint-disable-next-line no-console
715
- console.debug("Rovo query: " + normalized);
716
- }
717
- const result = await sql.executeRaw(normalized);
718
697
 
719
- // Post-execution validation for non-admin users
720
- // Verify that required security fields exist and come from table
721
- // Also ensure all fields with orgTable come from (no JOINs or subqueries)
722
- if (isUseRLSFiltering && result?.metadata?.fields) {
723
- const fields = result.metadata.fields as Array<{
724
- name: string;
725
- schema?: string;
726
- table?: string;
727
- orgTable?: string;
728
- }>;
729
-
730
- settings.userScopeFields().forEach((field) => {
731
- const actualFields = fields.filter((f) => f.name.toLowerCase() === field?.toLowerCase());
732
- if (actualFields.length === 0) {
733
- throw new Error(
734
- `Security validation failed: The query must include ${field} as a raw column in the SELECT statement. This field is required for row-level security enforcement.`,
735
- );
736
- }
737
- const actualField = actualFields.find(
738
- (f) => !f.orgTable || f.orgTable.toUpperCase() !== upperTableName,
739
- );
740
- if (actualField) {
741
- throw new Error(
742
- `Security validation failed: '${field}' must come directly from the ${upperTableName} table. Joins, subqueries, or table aliases that change the origin of this column are not allowed.`,
743
- );
744
- }
745
- });
698
+ const fields = result.metadata.fields as Array<{
699
+ name: string;
700
+ schema?: string;
701
+ table?: string;
702
+ orgTable?: string;
703
+ }>;
746
704
 
747
- // Check that all fields with orgTable come from table
748
- // (This prevents JOINs or subqueries that reference other tables)
749
- // Note: Fields without orgTable (empty/undefined) are allowed - these are computed/calculated fields
750
- // We only check fields that have orgTable set - if orgTable exists, it must be table
751
- const fieldsFromOtherTables = fields.filter(
752
- (f) => f.orgTable && f.orgTable.toUpperCase() !== upperTableName,
705
+ settings.userScopeFields().forEach((field) => {
706
+ const actualFields = fields.filter((f) => f.name.toLowerCase() === field?.toLowerCase());
707
+ if (actualFields.length === 0) {
708
+ throw new Error(
709
+ `Security validation failed: The query must include ${field} as a raw column in the SELECT statement. This field is required for row-level security enforcement.`,
710
+ );
711
+ }
712
+ const actualField = actualFields.find(
713
+ (f) => !f.orgTable || f.orgTable.toUpperCase() !== upperTableName,
753
714
  );
754
- if (fieldsFromOtherTables.length > 0) {
715
+ if (actualField) {
755
716
  throw new Error(
756
- `Security validation failed: All fields must come from the ${upperTableName} table. ` +
757
- "Fields from other tables detected, which indicates the use of JOINs, subqueries, or references to other tables. " +
758
- "This is not allowed for security reasons.",
717
+ `Security validation failed: '${field}' must come directly from the ${upperTableName} table. Joins, subqueries, or table aliases that change the origin of this column are not allowed.`,
759
718
  );
760
719
  }
720
+ });
721
+
722
+ const fieldsFromOtherTables = fields.filter(
723
+ (f) => f.orgTable && f.orgTable.toUpperCase() !== upperTableName,
724
+ );
725
+ if (fieldsFromOtherTables.length > 0) {
726
+ throw new Error(
727
+ `Security validation failed: All fields must come from the ${upperTableName} table. ` +
728
+ "Fields from other tables detected, which indicates the use of JOINs, subqueries, or references to other tables. " +
729
+ "This is not allowed for security reasons.",
730
+ );
731
+ }
732
+ }
733
+
734
+ async dynamicIsolatedQuery(
735
+ dynamicSql: string,
736
+ settings: RovoIntegrationSetting,
737
+ ): Promise<Result<unknown>> {
738
+ const tableName = settings.getTableName();
739
+ const accountId = settings.getActiveUser();
740
+ const parameters = settings.getParameters();
741
+
742
+ // Validate inputs
743
+ const trimmedQuery = this.validateInputs(dynamicSql, tableName);
744
+
745
+ // Normalize SQL
746
+ let normalized: string;
747
+ try {
748
+ normalized = this.normalizeSqlString(trimmedQuery);
749
+ } catch (error: any) {
750
+ if (
751
+ error.message &&
752
+ (error.message.includes("Only") || error.message.includes("single SELECT"))
753
+ ) {
754
+ throw error;
755
+ }
756
+ if (error.message?.includes("SQL parsing error")) {
757
+ throw error;
758
+ }
759
+ throw new Error(
760
+ `SQL parsing error: ${error.message || "Invalid SQL syntax"}. Please check your query syntax.`,
761
+ );
762
+ }
763
+
764
+ // Validate table name and account
765
+ this.validateTableName(normalized, tableName);
766
+
767
+ if (!accountId) {
768
+ throw new Error(
769
+ "Authentication error: User account ID is missing. Please ensure you are logged in.",
770
+ );
771
+ }
772
+
773
+ // Replace parameters in query
774
+ normalized = normalized.replaceAll("ari:cloud:identity::user/", "");
775
+ Object.entries(parameters).forEach(([key, value]) => {
776
+ normalized = normalized.replaceAll(key, value);
777
+ });
778
+
779
+ // Validate query structure
780
+ const selectAst = this.parseSqlQuery(normalized);
781
+ this.validateQueryStructure(selectAst, tableName);
782
+
783
+ // Validate execution plan
784
+ await this.validateExecutionPlan(normalized, tableName);
785
+
786
+ // Apply RLS filtering if needed
787
+ const isUseRLSFiltering = settings.isUseRLS();
788
+ if (isUseRLSFiltering) {
789
+ normalized = this.applyRLSFiltering(normalized, settings);
790
+ }
791
+
792
+ if (this.options.logRawSqlQuery) {
793
+ // eslint-disable-next-line no-console
794
+ console.debug("Rovo query: " + normalized);
795
+ }
796
+
797
+ const result = await sql.executeRaw(normalized);
798
+
799
+ // Validate query results for RLS compliance
800
+ if (isUseRLSFiltering) {
801
+ const upperTableName = tableName.toUpperCase();
802
+ this.validateQueryResults(result, settings, upperTableName);
761
803
  }
762
804
 
763
805
  return result;
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import ForgeSQLORM from "./core/ForgeSQLORM";
1
+ export { default } from "./core/ForgeSQLORM";
2
2
 
3
3
  export * from "./core/ForgeSQLQueryBuilder";
4
4
  export * from "./core/ForgeSQLCrudOperations";
@@ -8,5 +8,4 @@ export * from "./utils/forgeDriver";
8
8
  export * from "./webtriggers";
9
9
  export * from "./lib/drizzle/extensions/additionalActions";
10
10
  export * from "./core/SystemTables";
11
-
12
- export default ForgeSQLORM;
11
+ export * from "./async/PrintQueryConsumer";