diff --git a/apps/api/drizzle.config.ts b/apps/api/drizzle.config.ts index 6b2ee36..c5b3aa5 100644 --- a/apps/api/drizzle.config.ts +++ b/apps/api/drizzle.config.ts @@ -1,9 +1,13 @@ import './src/db/env.js' +import { requireDatabaseUrl } from './src/db/env.js' import { defineConfig } from 'drizzle-kit' +// 故意不做 DATABASE_URL_TEST 回退:迁移永不指向临时测试库(Task 13 helper 用显式连接串迁移临时库) +const url = requireDatabaseUrl({ allowTestUrl: false }) + export default defineConfig({ dialect: 'postgresql', schema: './src/db/schema.ts', out: './drizzle', - dbCredentials: { url: process.env.DATABASE_URL! }, + dbCredentials: { url }, }) diff --git a/apps/api/package.json b/apps/api/package.json index 7258b65..9ee413b 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -4,7 +4,8 @@ "description": "SiteForge API:NestJS 模块化单体骨架(本切片以 standalone context 供 CLI 使用,不监听端口)+ Drizzle/postgres.js 接入", "type": "module", "scripts": { - "build": "tsc -p tsconfig.json", + "build": "tsc -p tsconfig.build.json", + "typecheck": "tsc -p tsconfig.json --noEmit", "db:generate": "drizzle-kit generate", "db:migrate": "drizzle-kit migrate" }, diff --git a/apps/api/src/db/client.ts b/apps/api/src/db/client.ts index ed7e91b..78d6a8d 100644 --- a/apps/api/src/db/client.ts +++ b/apps/api/src/db/client.ts @@ -2,12 +2,10 @@ import './env.js' import { drizzle } from 'drizzle-orm/postgres-js' import postgres from 'postgres' import * as schema from './schema.js' +import { requireDatabaseUrl } from './env.js' -// DATABASE_URL_TEST ?? DATABASE_URL 的回退链是 Task 13 集成测试的接线: -// 测试临时库优先,开发库兜底。env 加载见 ./env.ts(向上查找仓库根 .env)。 -export function getDb( - connectionString = process.env.DATABASE_URL_TEST ?? process.env.DATABASE_URL!, -) { +// 每次调用都会新建连接池、由调用方持有其生命周期;`db.$client.end()` 可关闭(drizzle-orm 0.45 已暴露)。 +export function getDb(connectionString = requireDatabaseUrl()) { const sql = postgres(connectionString, { max: 5 }) return drizzle(sql, { schema }) } diff --git a/apps/api/src/db/env.ts b/apps/api/src/db/env.ts index 0a14afa..0c62708 100644 --- a/apps/api/src/db/env.ts +++ b/apps/api/src/db/env.ts @@ -16,3 +16,17 @@ for (;;) { if (parent === dir) break dir = parent } + +// 早失败守卫:postgres(undefined) 会静默回退到 OS 用户@localhost:5432(postgres.js 源码行为), +// 本机多 PG 实例时 select 1 仍会通过、迁移可能打错库,故必须在建客户端前拦截。 +// allowTestUrl(默认 true):运行时连接允许 DATABASE_URL_TEST ?? DATABASE_URL 回退链(Task 13 集成测试接线); +// 传 false 则只认 DATABASE_URL——drizzle 迁移用它,保证迁移永不指向临时测试库。 +export function requireDatabaseUrl({ allowTestUrl = true }: { allowTestUrl?: boolean } = {}): string { + const url = (allowTestUrl ? process.env.DATABASE_URL_TEST : undefined) ?? process.env.DATABASE_URL + if (!url) { + throw new Error( + 'DATABASE_URL not set — copy .env.example to /.env (searched upward from cwd)', + ) + } + return url +} diff --git a/apps/api/test/db.connect.test.ts b/apps/api/test/db.connect.test.ts index 765544d..3bd269b 100644 --- a/apps/api/test/db.connect.test.ts +++ b/apps/api/test/db.connect.test.ts @@ -9,4 +9,18 @@ describe('数据库连接', () => { const r = (await db.execute('select 1 as ok')) as any expect(Number(r[0].ok)).toBe(1) }) + + it('DATABASE_URL 缺失时 getDb 早失败并给出诊断信息(不静默连 localhost)', () => { + const savedTest = process.env.DATABASE_URL_TEST + const saved = process.env.DATABASE_URL + delete process.env.DATABASE_URL_TEST + delete process.env.DATABASE_URL + try { + // postgres(undefined) 会静默回退 OS 用户@localhost:5432,必须在此前置拦截 + expect(() => getDb()).toThrow(/DATABASE_URL not set/) + } finally { + if (savedTest !== undefined) process.env.DATABASE_URL_TEST = savedTest + if (saved !== undefined) process.env.DATABASE_URL = saved + } + }) }) diff --git a/apps/api/tsconfig.build.json b/apps/api/tsconfig.build.json new file mode 100644 index 0000000..d0c16e4 --- /dev/null +++ b/apps/api/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "." + }, + "exclude": ["test"] +}