import { DataSource, DataSourceOptions } from 'typeorm'; import * as path from 'path'; import { resolveDataDatabaseType, resolvePostgresConnection } from '../config/managed-service-connections'; import { loadCliEnv } from './load-cli-env'; // Load env with the same precedence as the app (process.env > .env > configured data mount), so the // migration CLI targets the SAME database the dashboard configured — not the default SQLite DB. loadCliEnv(); const dbType = resolveDataDatabaseType(); const sourceGlob = (...segments: string[]): string => path.join(__dirname, ...segments).replace(/\\/g, '/'); // Scoped to the DATA-owned modules only (session/webhook/message/template/engine/integration), mirroring // the runtime data connection (app.module.ts). A broad '**' glob would also sweep in the main-owned // auth/audit entities and pollute `migration:generate` against the data DB with their DDL. const dataEntities = [ sourceGlob('..', 'modules', 'session', '**', '*.entity{.ts,.js}'), sourceGlob('..', 'modules', 'webhook', '**', '*.entity{.ts,.js}'), sourceGlob('..', 'modules', 'message', '**', '*.entity{.ts,.js}'), sourceGlob('..', 'modules', 'template', '**', '*.entity{.ts,.js}'), sourceGlob('..', 'engine', '**', '*.entity{.ts,.js}'), sourceGlob('..', 'modules', 'integration', '**', '*.entity{.ts,.js}'), ]; const dataMigrations = [sourceGlob('migrations', '*{.ts,.js}')]; // SQLite configuration const sqliteDataSourceOptions: DataSourceOptions = { type: 'better-sqlite3', database: process.env.DATABASE_NAME || './data/openwa.sqlite', entities: dataEntities, migrations: dataMigrations, synchronize: false, logging: process.env.DATABASE_LOGGING === 'true', }; // PostgreSQL configuration. // // Exported as plain DataSourceOptions, NOT a DataSource instance: the TypeORM CLI's loadDataSource() // rejects a data-source file that exports more than one DataSource instance ("must contain only one // export of DataSource"). Keeping the postgres config as an options object leaves exactly one instance // export (the default below) so every `migration:*` command resolves it. // Schema selection: a non-public POSTGRES_SCHEMA sets TypeORM's `schema` option AND the session // search_path (via pg's startup `options` parameter) so the project's RAW, unqualified migration SQL // (CREATE TABLE "x"..., ALTER TABLE "y"...) resolves to the configured schema. TypeORM's `schema` // option alone does NOT set search_path — without the `options` override, raw DDL would land in // `public` while the migration ledger lands in the configured schema. `,public` keeps public // on the path so pg_catalog + any public helpers still resolve; the configured schema wins. // // Exported as a builder so the schema/search_path logic is unit-testable without mutating process.env // or reloading the module. The default `postgresDataSourceOptions` reads the loaded env at module eval. export function buildPostgresDataSourceOptions(env: NodeJS.ProcessEnv = process.env): DataSourceOptions { const connection = resolvePostgresConnection(env); const schema = env.POSTGRES_SCHEMA || 'public'; const useCustomSearchPath = schema !== 'public'; return { type: 'postgres', schema, host: connection.host, port: connection.port, username: connection.username, password: connection.password, database: connection.database, entities: dataEntities, migrations: dataMigrations, synchronize: false, // Never auto-sync in production logging: env.DATABASE_LOGGING === 'true', ssl: connection.ssl ? { rejectUnauthorized: connection.sslRejectUnauthorized } : false, extra: { max: parseInt(env.DATABASE_POOL_SIZE || '10', 10), // Pool resilience only. NO statement_timeout here: this connection runs migrations, and a // long CREATE INDEX / backfill must not be aborted mid-flight. idleTimeoutMillis: parseInt(env.DATABASE_IDLE_TIMEOUT_MS || '30000', 10), connectionTimeoutMillis: parseInt(env.DATABASE_CONNECTION_TIMEOUT_MS || '10000', 10), // Only set for a non-public schema (see above). ...(useCustomSearchPath ? { options: `-c search_path=${schema},public` } : {}), }, }; } export const postgresDataSourceOptions: DataSourceOptions = buildPostgresDataSourceOptions(); // Exactly ONE DataSource instance is exported (the default), selected by DATABASE_TYPE. export default new DataSource(dbType === 'postgres' ? postgresDataSourceOptions : sqliteDataSourceOptions);