forge-sql-orm
Version:
Drizzle ORM integration for Atlassian @forge/sql. Provides a custom driver, schema migration, two levels of caching (local and global via @forge/kvs), optimistic locking, and query analysis.
210 lines • 9.13 kB
TypeScript
import { ForgeSqlOperation, ForgeSqlOrmOptions, RovoIntegration, RovoIntegrationSetting, RovoIntegrationSettingCreator } from "./ForgeSQLQueryBuilder";
import { Result } from "@forge/sql";
import { AnyMySqlTable } from "drizzle-orm/mysql-core";
/**
* Main class for Rovo integration - a secure pattern for natural-language analytics in Forge apps.
*
* Rovo provides a secure way to execute dynamic SQL queries with comprehensive security validations:
* - Only SELECT queries are allowed
* - Queries are restricted to a single table
* - JOINs, subqueries, and window functions are blocked
* - Row-Level Security (RLS) support for data isolation
* - Post-execution validation of query results
*
* @class Rovo
* @implements {RovoIntegration}
*
* @example
* ```typescript
* const rovo = forgeSQL.rovo();
* const settings = await rovo.rovoSettingBuilder(usersTable, accountId)
* .useRLS()
* .addRlsColumn(usersTable.id)
* .addRlsWherePart((alias) => `${alias}.id = '${accountId}'`)
* .finish()
* .build();
*
* const result = await rovo.dynamicIsolatedQuery(
* "SELECT id, name FROM users WHERE status = 'active'",
* settings
* );
* ```
*/
export declare class Rovo implements RovoIntegration {
private readonly forgeOperations;
private readonly options;
/**
* Creates a new Rovo instance.
*
* @param {ForgeSqlOperation} forgeSqlOperations - The ForgeSQL operations instance for query analysis and execution
* @param {ForgeSqlOrmOptions} options - Configuration options for the ORM (e.g., logging settings)
*/
constructor(forgeSqlOperations: ForgeSqlOperation, options: ForgeSqlOrmOptions);
/**
* Parses SQL query into AST and validates it's a single SELECT statement.
*
* @param {string} sqlQuery - Normalized SQL query string
* @returns {Select} Parsed AST of the SELECT statement
* @throws {Error} If parsing fails or query is not a single SELECT statement
*/
private parseSqlQuery;
/**
* Recursively processes array or single node and extracts table names.
*
* @param {any} items - Array of AST nodes or single AST node
* @param {string[]} tables - Accumulator array for collecting table names (modified in place)
*/
private extractTablesFromItems;
/**
* Extracts table name from table AST node.
*
* @param {any} node - AST node with table information
* @returns {string | null} Table name in uppercase, or null if not applicable (e.g., 'dual' table)
*/
private extractTableName;
/**
* Recursively extracts all table names from SQL AST node.
* Traverses FROM and JOIN clauses to find all referenced tables.
*
* @param {any} node - AST node to extract tables from
* @returns {string[]} Array of table names in uppercase
*/
private extractTables;
/**
* Recursively checks if AST node contains scalar subqueries.
* Used for security validation to prevent subquery-based attacks.
*
* @param {any} node - AST node to check for subqueries
* @returns {boolean} True if node contains scalar subquery, false otherwise
*/
private hasScalarSubquery;
/**
* Creates a settings builder for Rovo queries using a raw table name.
*
* @param {string} tableName - The name of the table to query (case-insensitive)
* @param {string} accountId - The account ID of the active user for RLS filtering
* @returns {RovoIntegrationSettingCreator} Builder for configuring Rovo query settings
*
* @example
* ```typescript
* const builder = rovo.rovoRawSettingBuilder('users', accountId);
* const settings = await builder
* .addStringContextParameter('{{status}}', 'active')
* .build();
* ```
*/
rovoRawSettingBuilder(tableName: string, accountId: string): RovoIntegrationSettingCreator;
/**
* Creates a settings builder for Rovo queries using a Drizzle table object.
* This is a convenience method that extracts the table name from the Drizzle table object.
*
* @param {AnyMySqlTable} table - The Drizzle table object
* @param {string} accountId - The account ID of the active user for RLS filtering
* @returns {RovoIntegrationSettingCreator} Builder for configuring Rovo query settings
*
* @example
* ```typescript
* const builder = rovo.rovoSettingBuilder(usersTable, accountId);
* const settings = await builder
* .useRLS()
* .addRlsColumn(usersTable.id)
* .addRlsWherePart((alias) => `${alias}.userId = '${accountId}'`)
* .finish()
* .build();
* ```
*/
rovoSettingBuilder(table: AnyMySqlTable, accountId: string): RovoIntegrationSettingCreator;
/**
* Validates basic input parameters for the SQL query.
*
* @param {string} query - The SQL query string to validate
* @param {string} tableName - The expected table name
* @returns {string} The trimmed query string
* @throws {Error} If query is empty, table name is missing, or query is not a SELECT statement
*/
private validateInputs;
/**
* Normalizes SQL query using AST parsing and stringification.
* This ensures consistent formatting and validates the query structure.
*
* @param {string} sql - The SQL query string to normalize
* @returns {string} The normalized SQL query string
* @throws {Error} If parsing fails, query is not a SELECT statement, or multiple statements are detected
*/
private normalizeSqlString;
/**
* Validates that query targets the correct table.
* Checks that the FROM clause references only the expected table.
*
* @param {string} normalized - The normalized SQL query string
* @param {string} tableName - The expected table name
* @throws {Error} If query does not target the expected table
*/
private validateTableName;
/**
* Validates query structure for security compliance.
* Checks that only the specified table is referenced and no scalar subqueries are present.
*
* @param {Select} selectAst - The parsed SELECT AST node
* @param {string} tableName - The expected table name
* @throws {Error} If query references other tables or contains scalar subqueries
*/
private validateQueryStructure;
/**
* Validates query execution plan for security violations.
* Uses EXPLAIN to detect JOINs, window functions, and references to other tables.
*
* @param {string} normalized - The normalized SQL query string
* @param {string} tableName - The expected table name
* @returns {Promise<void>}
* @throws {Error} If execution plan reveals JOINs, window functions, or references to other tables
*/
private validateExecutionPlan;
/**
* Applies row-level security filtering to query.
* Wraps the original query in a subquery and adds a WHERE clause with RLS conditions.
*
* @param {string} normalized - The normalized SQL query string
* @param {RovoIntegrationSetting} settings - Rovo settings containing RLS configuration
* @returns {string} The SQL query with RLS filtering applied
*/
private applyRLSFiltering;
/**
* Validates query results for RLS compliance.
* Ensures that required RLS fields are present and all fields originate from the correct table.
*
* @param {Result<unknown>} result - The query execution result
* @param {RovoIntegrationSetting} settings - Rovo settings containing RLS field requirements
* @param {string} upperTableName - The expected table name in uppercase
* @throws {Error} If required RLS fields are missing or fields originate from other tables
*/
private validateQueryResults;
/**
* Executes a dynamic SQL query with comprehensive security validations.
*
* This method performs multiple security checks:
* 1. Validates that the query is a SELECT statement
* 2. Ensures the query targets only the specified table
* 3. Blocks JOINs, subqueries, and window functions
* 4. Applies Row-Level Security filtering if enabled
* 5. Validates query results to ensure security fields are present
*
* @param {string} dynamicSql - The SQL query to execute (must be a SELECT statement)
* @param {RovoIntegrationSetting} settings - Configuration settings for the query
* @returns {Promise<Result<unknown>>} Query execution result with metadata
* @throws {Error} If the query violates security restrictions, parsing fails, or validation errors occur
*
* @example
* ```typescript
* const result = await rovo.dynamicIsolatedQuery(
* "SELECT id, name, email FROM users WHERE status = 'active' ORDER BY name",
* settings
* );
*
* console.log(result.rows); // Query results
* console.log(result.metadata); // Query metadata
* ```
*/
dynamicIsolatedQuery(dynamicSql: string, settings: RovoIntegrationSetting): Promise<Result<unknown>>;
}
//# sourceMappingURL=Rovo.d.ts.map