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
text/typescript
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"];
}