From 39734a7467c5e9b23d5f129b8e1f21393a74d54a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Tue, 20 Oct 2026 19:10:19 +0800 Subject: [PATCH] =?UTF-8?q?fix(api):=20requireDatabaseUrl=20=E6=97=A9?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E5=AE=88=E5=8D=AB=EF=BC=9Btypecheck=20?= =?UTF-8?q?=E9=97=A8=E7=A6=81=EF=BC=9Bbuild=20=E6=8E=92=E9=99=A4=20test?= =?UTF-8?q?=EF=BC=9B=E6=B1=A0=E7=94=9F=E5=91=BD=E5=91=A8=E6=9C=9F=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Code --- apps/api/drizzle.config.ts | 6 +++++- apps/api/package.json | 3 ++- apps/api/src/db/client.ts | 8 +++----- apps/api/src/db/env.ts | 14 ++++++++++++++ apps/api/test/db.connect.test.ts | 14 ++++++++++++++ apps/api/tsconfig.build.json | 7 +++++++ 6 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 apps/api/tsconfig.build.json 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"] +}