From c054fe4889ab85f076a14be11c26bfc74295473a Mon Sep 17 00:00:00 2001 From: npmrun <1549469775@qq.com> Date: Sat, 18 Apr 2026 11:21:09 +0800 Subject: [PATCH] fix(db): stable SQLite file URL resolution; resilient logout on DB errors Made-with: Cursor --- packages/drizzle-pkg/database/sqlite/db.ts | 30 ++++++++++------------- packages/drizzle-pkg/env.ts | 21 +++++----------- packages/drizzle-pkg/lib/paths.ts | 33 ++++++++++++++++++++++++++ packages/drizzle-pkg/lib/resolve-sqlite-url.ts | 15 ++++++++++++ server/api/auth/logout.post.ts | 16 +++++++++---- 5 files changed, 78 insertions(+), 37 deletions(-) create mode 100644 packages/drizzle-pkg/lib/paths.ts create mode 100644 packages/drizzle-pkg/lib/resolve-sqlite-url.ts diff --git a/packages/drizzle-pkg/database/sqlite/db.ts b/packages/drizzle-pkg/database/sqlite/db.ts index 696fbb6..180e2e6 100644 --- a/packages/drizzle-pkg/database/sqlite/db.ts +++ b/packages/drizzle-pkg/database/sqlite/db.ts @@ -1,24 +1,18 @@ -import { drizzle } from 'drizzle-orm/libsql'; -import path from 'path'; +import { drizzle } from "drizzle-orm/libsql"; +import { resolveSqliteDatabaseUrl } from "../../lib/resolve-sqlite-url"; -if (process.env.NODE_ENV === 'production') { - // 打包时需要保证migrator被引入 - import('drizzle-orm/better-sqlite3/migrator') +if (process.env.NODE_ENV === "production") { + // 打包时需要保证migrator被引入 + import("drizzle-orm/better-sqlite3/migrator"); } -const tempCwd = process.env.NODE_ENV !== 'production' - ? path.resolve(process.cwd(), 'packages/drizzle-pkg') - : process.cwd(); - -let dbUrl = process.env.DATABASE_URL; -if (dbUrl && dbUrl.startsWith('file:')) { - let filePath = dbUrl.slice(5); - if (!path.isAbsolute(filePath)) { - filePath = path.resolve(tempCwd, filePath); - process.env.DATABASE_URL = 'file:' + filePath; - } +const rawUrl = process.env.DATABASE_URL; +if (!rawUrl) { + throw new Error("DATABASE_URL 未设置"); } +const resolvedUrl = resolveSqliteDatabaseUrl(rawUrl); +process.env.DATABASE_URL = resolvedUrl; -const _db = drizzle(process.env.DATABASE_URL!); +const _db = drizzle(resolvedUrl); -export { _db as dbGlobal } \ No newline at end of file +export { _db as dbGlobal }; \ No newline at end of file diff --git a/packages/drizzle-pkg/env.ts b/packages/drizzle-pkg/env.ts index bcc40f1..2571d33 100644 --- a/packages/drizzle-pkg/env.ts +++ b/packages/drizzle-pkg/env.ts @@ -1,18 +1,9 @@ +import { config } from "dotenv"; +import { resolveSqliteDatabaseUrl } from "./lib/resolve-sqlite-url"; -import { config } from 'dotenv'; -import path from 'path'; +config({ path: "../../.env" }); -config({ path: '../../.env' }); - -const tempCwd = process.env.NODE_ENV === 'production' - ? path.resolve(process.cwd(), 'packages/drizzle-pkg') - : process.cwd(); - -let dbUrl = process.env.DATABASE_URL; -if (dbUrl && dbUrl.startsWith('file:')) { - let filePath = dbUrl.slice(5); - if (!path.isAbsolute(filePath)) { - filePath = path.resolve(tempCwd, filePath); - process.env.DATABASE_URL = 'file:' + filePath; - } +const dbUrl = process.env.DATABASE_URL; +if (dbUrl?.startsWith("file:")) { + process.env.DATABASE_URL = resolveSqliteDatabaseUrl(dbUrl); } diff --git a/packages/drizzle-pkg/lib/paths.ts b/packages/drizzle-pkg/lib/paths.ts new file mode 100644 index 0000000..860b6e6 --- /dev/null +++ b/packages/drizzle-pkg/lib/paths.ts @@ -0,0 +1,33 @@ +import { existsSync, readFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +function isDrizzlePkgRoot(dir: string): boolean { + const pkg = path.join(dir, "package.json"); + if (!existsSync(pkg)) { + return false; + } + try { + const { name } = JSON.parse(readFileSync(pkg, "utf8")) as { name?: string }; + return name === "drizzle-pkg"; + } catch { + return false; + } +} + +/** + * `drizzle-pkg` 包根目录(与 `package.json`、`db.sqlite` 同级)。 + * - 开发:用 `import.meta` 锚定,避免 cwd 变化把 `file:db.sqlite` 指到错误文件(只读 / DBMOVED)。 + * - 生产:打包后 chunk 路径不可靠,回退到 `cwd/packages/drizzle-pkg`;部署时建议使用绝对 `DATABASE_URL`。 + */ +export function getDrizzlePkgRoot(): string { + const fromMeta = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); + if (isDrizzlePkgRoot(fromMeta)) { + return fromMeta; + } + const fromCwd = path.resolve(process.cwd(), "packages/drizzle-pkg"); + if (isDrizzlePkgRoot(fromCwd)) { + return fromCwd; + } + return fromMeta; +} diff --git a/packages/drizzle-pkg/lib/resolve-sqlite-url.ts b/packages/drizzle-pkg/lib/resolve-sqlite-url.ts new file mode 100644 index 0000000..de59c62 --- /dev/null +++ b/packages/drizzle-pkg/lib/resolve-sqlite-url.ts @@ -0,0 +1,15 @@ +import path from "node:path"; +import { getDrizzlePkgRoot } from "./paths"; + +/** 将 `file:` 相对路径解析为绝对路径(相对 drizzle-pkg 根目录) */ +export function resolveSqliteDatabaseUrl(url: string): string { + if (!url.startsWith("file:")) { + return url; + } + let filePath = url.slice("file:".length); + if (path.isAbsolute(filePath)) { + return `file:${filePath}`; + } + const root = getDrizzlePkgRoot(); + return `file:${path.resolve(root, filePath)}`; +} diff --git a/server/api/auth/logout.post.ts b/server/api/auth/logout.post.ts index dfcaa64..2a2265f 100644 --- a/server/api/auth/logout.post.ts +++ b/server/api/auth/logout.post.ts @@ -1,15 +1,23 @@ import { logoutUser } from "#server/service/auth"; import { getSessionId, clearSessionCookie } from "#server/service/auth/cookie"; import { toPublicAuthError } from "#server/service/auth/errors"; +import log4js from "logger"; + +const logger = log4js.getLogger("AUTH"); export default defineWrappedResponseHandler(async (event) => { - try { - const sessionId = getSessionId(event); - if (sessionId) { + const sessionId = getSessionId(event); + if (sessionId) { + try { await logoutUser(sessionId); + } catch (err) { + // 仍清除 Cookie,避免客户端卡在「已登出但库只读/路径异常」无法退出 + logger.warn("logout: 删除会话失败(仍将清除 Cookie)", err); } - clearSessionCookie(event); + } + clearSessionCookie(event); + try { return R.success({ success: true, });