Skip to content

Commit 0117968

Browse files
d-csclaude
andcommitted
feat(run-ops): dedicated run-ops database package + docker service + migration runner
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 8276496 commit 0117968

22 files changed

Lines changed: 2203 additions & 123 deletions

File tree

.github/workflows/unit-tests-internal.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ jobs:
9595
}
9696
echo "Pre-pulling Docker images with authenticated session..."
9797
pull postgres:14
98+
pull postgres:17 # for the heterogeneous PG14/17 test fixture gate (heteroPostgresTest)
9899
pull clickhouse/clickhouse-server:26.2.19.43-alpine@sha256:c6ad6a7eb2fb5999df3adfb8b69a0c7222c68fa9b8f6b04a088564ebbc959251
99100
pull redis:7.2
100101
pull testcontainers/ryuk:0.14.0

docker/Dockerfile.postgres17

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
FROM postgres:17
2+
3+
RUN apt-get update \
4+
&& apt-get install -y --no-install-recommends postgresql-17-partman \
5+
&& rm -rf /var/lib/apt/lists/*

docker/docker-compose.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ name: triggerdotdev-docker
1111
volumes:
1212
database-data:
1313
database-data-alt:
14+
database-runops-data:
1415
database-replica-data:
1516
redis-data:
1617
minio-data:
@@ -51,6 +52,36 @@ services:
5152
- -c
5253
- max_connections=500
5354

55+
# Opt-in NEW run-ops database, PG17 (the `database` above is PG14) — a separate cluster
56+
# so the run-ops split's distinct-DB sentinel passes. Start with:
57+
# COMPOSE_PROFILES=runops pnpm run docker
58+
database-runops:
59+
container_name: ${CONTAINER_PREFIX:-}database-runops
60+
profiles: ["runops"]
61+
build:
62+
context: .
63+
dockerfile: Dockerfile.postgres17
64+
restart: always
65+
volumes:
66+
- ${DB_RUNOPS_VOLUME:-database-runops-data}:/var/lib/postgresql/data/
67+
environment:
68+
POSTGRES_USER: postgres
69+
POSTGRES_PASSWORD: postgres
70+
POSTGRES_DB: postgres
71+
networks:
72+
- app_network
73+
ports:
74+
- "${POSTGRES_RUNOPS_HOST_PORT:-5434}:5432"
75+
command:
76+
- -c
77+
- listen_addresses=*
78+
- -c
79+
- wal_level=logical
80+
- -c
81+
- shared_preload_libraries=pg_partman_bgw
82+
- -c
83+
- max_connections=500
84+
5485
# Opt-in streaming read replica with configurable apply lag — a dial-a-lag rig for
5586
# testing replica-race behavior (e.g. the realtime read-your-writes gate) locally.
5687
# Start with: COMPOSE_PROFILES=replica pnpm run docker

internal-packages/database/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"generate": "prisma generate",
1919
"db:migrate:dev:create": "prisma migrate dev --create-only",
2020
"db:migrate:deploy": "prisma migrate deploy",
21+
"db:migrate:run-ops": "node scripts/migrate-run-ops.mjs",
2122
"db:push": "prisma db push",
2223
"db:studio": "prisma studio",
2324
"db:reset": "prisma migrate reset",
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Apply Prisma migrations to the RUN-OPS database — the second physical DB in the run-ops
2+
// split. The standard `db:migrate` only targets DATABASE_URL (the control-plane DB), so the
3+
// run-ops DB must be migrated explicitly or its schema drifts (e.g. cross-DB FKs that were
4+
// dropped on the control-plane DB linger on the run-ops DB and break inserts).
5+
//
6+
// The run-ops connection comes from TASK_RUN_DATABASE_URL / TASK_RUN_DATABASE_DIRECT_URL
7+
// (set directly in deploy environments; read from the local .env otherwise). We then run
8+
// `prisma migrate deploy` with DATABASE_URL/DIRECT_URL pointed at it.
9+
import { spawnSync } from "node:child_process";
10+
import { readFileSync } from "node:fs";
11+
import { dirname, resolve } from "node:path";
12+
import { fileURLToPath } from "node:url";
13+
14+
const dbPackageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
15+
16+
function readFromEnvFiles(key) {
17+
for (const file of [resolve(dbPackageRoot, ".env"), resolve(dbPackageRoot, "../../.env")]) {
18+
let contents;
19+
try {
20+
contents = readFileSync(file, "utf8");
21+
} catch {
22+
continue;
23+
}
24+
for (const line of contents.split("\n")) {
25+
const match = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*?)\s*$/);
26+
if (!match || match[1] !== key) continue;
27+
let value = match[2];
28+
if (
29+
(value.startsWith('"') && value.endsWith('"')) ||
30+
(value.startsWith("'") && value.endsWith("'"))
31+
) {
32+
value = value.slice(1, -1);
33+
}
34+
if (value) return value;
35+
}
36+
}
37+
return undefined;
38+
}
39+
40+
const resolveVar = (key) => process.env[key] || readFromEnvFiles(key);
41+
const redact = (url) => url.replace(/:\/\/[^@]*@/, "://***@");
42+
43+
const databaseUrl = resolveVar("TASK_RUN_DATABASE_URL");
44+
const directUrl = resolveVar("TASK_RUN_DATABASE_DIRECT_URL") || databaseUrl;
45+
46+
if (!databaseUrl) {
47+
console.error(
48+
"db:migrate:run-ops: TASK_RUN_DATABASE_URL is not set (checked env and .env). " +
49+
"It is the run-ops database in the split — nothing to migrate without it."
50+
);
51+
process.exit(1);
52+
}
53+
54+
console.log(`Applying Prisma migrations to the run-ops database (${redact(databaseUrl)})`);
55+
56+
const result = spawnSync("prisma", ["migrate", "deploy"], {
57+
cwd: dbPackageRoot,
58+
stdio: "inherit",
59+
env: { ...process.env, DATABASE_URL: databaseUrl, DIRECT_URL: directUrl },
60+
});
61+
62+
process.exit(result.status ?? 1);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../.env
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules
2+
dist
3+
generated/run-ops
4+
5+
# Ensure the .env symlink is not removed by accident
6+
!.env
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "@internal/run-ops-database",
3+
"private": true,
4+
"version": "0.0.1",
5+
"main": "./dist/index.js",
6+
"types": "./dist/index.d.ts",
7+
"exports": {
8+
".": {
9+
"@triggerdotdev/source": "./src/index.ts",
10+
"types": "./dist/index.d.ts",
11+
"import": "./dist/index.js",
12+
"default": "./dist/index.js"
13+
},
14+
"./prisma/schema.prisma": "./prisma/schema.prisma",
15+
"./prisma/*": "./prisma/*",
16+
"./package.json": "./package.json"
17+
},
18+
"dependencies": {
19+
"@prisma/client": "6.14.0",
20+
"prisma": "6.14.0"
21+
},
22+
"devDependencies": {
23+
"rimraf": "6.0.1",
24+
"vitest": "4.1.7"
25+
},
26+
"scripts": {
27+
"clean": "rimraf dist",
28+
"generate": "prisma generate",
29+
"db:migrate:dev:create": "prisma migrate dev --create-only",
30+
"db:migrate:deploy": "prisma migrate deploy",
31+
"db:push": "prisma db push",
32+
"test": "vitest run",
33+
"typecheck": "tsc --noEmit",
34+
"build": "pnpm run clean && tsc --noEmit false --outDir dist --declaration",
35+
"dev": "tsc --noEmit false --outDir dist --declaration --watch",
36+
"db:studio": "prisma studio",
37+
"db:reset": "prisma migrate reset"
38+
}
39+
}

0 commit comments

Comments
 (0)