From 01b8dcf03b274b2384400e304334a6006fb12203 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Fri, 26 Jun 2026 11:09:36 +0100 Subject: [PATCH] feat(dashboard-agent-db): run migrations over a direct (non-pooler) connection (#4054) ## Summary The in-dashboard agent's datastore now runs migrations over a direct (non-pooler) connection. A transaction-mode pooler can't run the migrator (no advisory locks, no multi-statement DDL), so when the agent's database sits behind a pooler the migration step needs a separate direct connection. The application keeps connecting over the pooled `DASHBOARD_AGENT_DATABASE_URL`. Only the migration entry points changed (`drizzle.config.ts`, `migrate.mjs`, `migrate-status.mjs`); the runtime client is untouched. ## Connection resolution (migrations) ``` DASHBOARD_AGENT_DIRECT_URL direct agent connection (used for migrations) DASHBOARD_AGENT_DATABASE_URL pooled agent connection (preserves current behavior) DIRECT_URL main direct connection (single-database fallback) DATABASE_URL last resort ``` Mirrors the existing `DATABASE_URL` / `DIRECT_URL` split. Fully backward-compatible: with nothing new set, resolution is identical to before. The agent-specific vars take precedence over the main `DIRECT_URL`, so a separate agent database is never migrated against the wrong one. When the agent falls back to the main single database, migrations now prefer its direct connection. --- internal-packages/dashboard-agent-db/README.md | 13 ++++++++----- .../dashboard-agent-db/drizzle.config.ts | 8 ++++++-- .../dashboard-agent-db/migrate-status.mjs | 9 +++++++-- internal-packages/dashboard-agent-db/migrate.mjs | 14 ++++++++++---- 4 files changed, 31 insertions(+), 13 deletions(-) diff --git a/internal-packages/dashboard-agent-db/README.md b/internal-packages/dashboard-agent-db/README.md index ff1bfbd0d..fe33a538b 100644 --- a/internal-packages/dashboard-agent-db/README.md +++ b/internal-packages/dashboard-agent-db/README.md @@ -4,10 +4,13 @@ The conversation datastore for the in-dashboard agent, isolated from the main Prisma database. Drizzle (postgres-js) over a dedicated `trigger_dashboard_agent` Postgres schema. -- **Cloud:** a separate PlanetScale Postgres database (`DASHBOARD_AGENT_DATABASE_URL`), - reached over a standard pooled connection. -- **OSS / self-host:** falls back to the main `DATABASE_URL`; the tables live in - the dedicated `trigger_dashboard_agent` schema, isolated from Prisma's `public`. +- **Cloud:** a separate PlanetScale Postgres database. The app connects over a + pooled connection (`DASHBOARD_AGENT_DATABASE_URL`); migrations run over a direct + (non-pooler) connection (`DASHBOARD_AGENT_DIRECT_URL`), since a transaction-mode + pooler can't run the migrator. +- **OSS / self-host:** falls back to the main `DATABASE_URL` (and `DIRECT_URL` for + migrations); the tables live in the dedicated `trigger_dashboard_agent` schema, + isolated from Prisma's `public`. The schema is **foreign-key-free** — it references main entities (`organizationId`, `userId`) by id only, because in cloud it lives in a different database. @@ -36,7 +39,7 @@ of truth. ```bash pnpm run db:generate # generate SQL migration from src/schema.ts (offline) -pnpm run db:migrate # apply migrations (needs DASHBOARD_AGENT_DATABASE_URL or DATABASE_URL) +pnpm run db:migrate # apply migrations (direct url: DASHBOARD_AGENT_DIRECT_URL, falling back to DASHBOARD_AGENT_DATABASE_URL / DIRECT_URL / DATABASE_URL) ``` drizzle-kit is scoped to the `trigger_dashboard_agent` schema (`schemaFilter`), so diff --git a/internal-packages/dashboard-agent-db/drizzle.config.ts b/internal-packages/dashboard-agent-db/drizzle.config.ts index ddaddb9fd..8713b1704 100644 --- a/internal-packages/dashboard-agent-db/drizzle.config.ts +++ b/internal-packages/dashboard-agent-db/drizzle.config.ts @@ -1,9 +1,13 @@ import { defineConfig } from "drizzle-kit"; -// Cloud points at the dedicated PlanetScale database; OSS falls back to the main -// DATABASE_URL (tables still land in the trigger_dashboard_agent schema). +// Migrations need a direct (non-pooler) connection; a transaction-mode pooler +// can't run the migrator. Prefer the agent's direct url, then its pooled url, +// then the main DIRECT_URL/DATABASE_URL (OSS single-database fallback; tables +// still land in the trigger_dashboard_agent schema). const url = + process.env.DASHBOARD_AGENT_DIRECT_URL ?? process.env.DASHBOARD_AGENT_DATABASE_URL ?? + process.env.DIRECT_URL ?? process.env.DATABASE_URL ?? "postgres://placeholder"; // generate is offline; a real url is only needed for migrate/studio diff --git a/internal-packages/dashboard-agent-db/migrate-status.mjs b/internal-packages/dashboard-agent-db/migrate-status.mjs index 8d8208fdb..431876aaf 100644 --- a/internal-packages/dashboard-agent-db/migrate-status.mjs +++ b/internal-packages/dashboard-agent-db/migrate-status.mjs @@ -8,11 +8,16 @@ import postgres from "postgres"; const MIGRATIONS_SCHEMA = "drizzle"; const MIGRATIONS_TABLE = "__dashboard_agent_migrations"; -const connectionString = process.env.DASHBOARD_AGENT_DATABASE_URL ?? process.env.DATABASE_URL; +// Match migrate.mjs: a direct (non-pooler) connection, same precedence. +const connectionString = + process.env.DASHBOARD_AGENT_DIRECT_URL ?? + process.env.DASHBOARD_AGENT_DATABASE_URL ?? + process.env.DIRECT_URL ?? + process.env.DATABASE_URL; if (!connectionString) { console.error( - "[dashboard-agent-db] DASHBOARD_AGENT_DATABASE_URL / DATABASE_URL not set; cannot check status." + "[dashboard-agent-db] No database url set (DASHBOARD_AGENT_DIRECT_URL / DASHBOARD_AGENT_DATABASE_URL / DIRECT_URL / DATABASE_URL); cannot check status." ); process.exit(2); } diff --git a/internal-packages/dashboard-agent-db/migrate.mjs b/internal-packages/dashboard-agent-db/migrate.mjs index d840707dd..ba4e4c6c8 100644 --- a/internal-packages/dashboard-agent-db/migrate.mjs +++ b/internal-packages/dashboard-agent-db/migrate.mjs @@ -10,13 +10,19 @@ import { drizzle } from "drizzle-orm/postgres-js"; import { migrate } from "drizzle-orm/postgres-js/migrator"; import postgres from "postgres"; -// Cloud points at the dedicated dashboard-agent database; OSS falls back to the -// main DATABASE_URL (tables still land in the `trigger_dashboard_agent` schema). -const connectionString = process.env.DASHBOARD_AGENT_DATABASE_URL ?? process.env.DATABASE_URL; +// Migrations need a direct (non-pooler) connection; a transaction-mode pooler +// can't run the migrator. Prefer the agent's direct url, then its pooled url, +// then the main DIRECT_URL/DATABASE_URL (OSS single-database fallback; tables +// still land in the `trigger_dashboard_agent` schema). +const connectionString = + process.env.DASHBOARD_AGENT_DIRECT_URL ?? + process.env.DASHBOARD_AGENT_DATABASE_URL ?? + process.env.DIRECT_URL ?? + process.env.DATABASE_URL; if (!connectionString) { console.error( - "[dashboard-agent-db] DASHBOARD_AGENT_DATABASE_URL / DATABASE_URL not set; cannot migrate." + "[dashboard-agent-db] No database url set (DASHBOARD_AGENT_DIRECT_URL / DASHBOARD_AGENT_DATABASE_URL / DIRECT_URL / DATABASE_URL); cannot migrate." ); process.exit(1); }