UNPKG

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.

101 lines (90 loc) 3.52 kB
import { sql } from "@forge/sql"; import { getHttpResponse, TriggerResponse } from "./index"; import { forgeSystemTables, getTables } from "../core/SystemTables"; import { getTableName } from "drizzle-orm/table"; import { checkProductionEnvironment } from "../utils/sqlUtils"; interface CreateTableRow { Table: string; "Create Table": string; } /** * ⚠️ DEVELOPMENT ONLY WEB TRIGGER ⚠️ * * This web trigger retrieves the current database schema from Atlassian Forge SQL. * It generates SQL statements that can be used to recreate the database structure. * * @warning This trigger should ONLY be used in development environments. It: * - Exposes your database structure * - Disables foreign key checks temporarily * - Generates SQL that could potentially be used maliciously * - May expose sensitive table names and structures * * @note This function is automatically disabled in production environments and will return a 500 error if called. * * @returns {Promise<TriggerResponse<string>>} A response containing SQL statements to recreate the database schema * - On success: Returns 200 status with SQL statements * - On failure: Returns 500 status with error message (including when called in production) * * @example * ```typescript * // The response will contain SQL statements like: * // SET foreign_key_checks = 0; * // CREATE TABLE IF NOT EXISTS users (...); * // CREATE TABLE IF NOT EXISTS orders (...); * // SET foreign_key_checks = 1; * ``` */ export async function fetchSchemaWebTrigger(): Promise<TriggerResponse<string>> { const productionCheck = checkProductionEnvironment("fetchSchemaWebTrigger"); if (productionCheck) { return productionCheck; } try { const tables = await getTables(); const createTableStatements = await generateCreateTableStatements(tables); const sqlStatements = wrapWithForeignKeyChecks(createTableStatements); return getHttpResponse<string>(200, sqlStatements.join(";\n")); } catch (error: any) { const errorMessage = error?.debug?.sqlMessage ?? error?.debug?.message ?? error.message ?? "Unknown error occurred"; // eslint-disable-next-line no-console console.error(errorMessage); return getHttpResponse<string>(500, errorMessage); } } /** * Generates CREATE TABLE statements for each table */ async function generateCreateTableStatements(tables: string[]): Promise<string[]> { const statements: string[] = []; for (const table of tables) { const createTableResult = await sql.executeDDL<CreateTableRow>(`SHOW CREATE TABLE "${table}"`); const createTableStatements = createTableResult.rows .filter((row) => !isSystemTable(row.Table)) .filter((row) => Boolean(row["Create Table"])) .map((row) => formatCreateTableStatement(row["Create Table"])); statements.push(...createTableStatements); } return statements; } /** * Checks if the table is a system table */ function isSystemTable(tableName: string): boolean { return forgeSystemTables.some((st) => getTableName(st) === tableName); } /** * Formats the CREATE TABLE statement */ function formatCreateTableStatement(statement: string): string { return statement.replace(/"/g, "").replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS"); } /** * Wraps the SQL statements with foreign key check controls */ function wrapWithForeignKeyChecks(statements: string[]): string[] { return ["SET foreign_key_checks = 0", ...statements, "SET foreign_key_checks = 1"]; }