refactor: 主动审计修复多处可观测性、依赖、代码质量缺口
通过并行 explore + librarian + 自查发现并修复: 代码缺陷 - shutdown.ts: db.$client.end().finally(...) 静默吞错——关闭失败会 谎报 "DB pool closed" 后照常 exit 0。改用 await + try/catch 分别记录成功/失败,setTimeout 也换成 Bun.sleep。 - interceptors.ts: 两条 instanceof ORPCError && instanceof ValidationError 重复检查,改用 early return + 单 if 分支区分 code。 - types.ts: 移除从未被引用的 RouterInputs 死代码(仅 RouterOutputs 被 TodoItem 用到)。 Bun 原生 API(删/换 Node 兼容层) - fields.ts: uuid v7 → Bun.randomUUIDv7(),删除 uuid 依赖 - migrate.ts: node:crypto.createHash → Bun.CryptoHasher.hash, 少一个 Promise.all 项 + 一个 import - shutdown.ts: setTimeout → Bun.sleep(顺带) Biome 2.4 规则补强 - domains.types: "all"——开启类型感知规则集(noFloatingPromises / noMisusedPromises / useAwaitThenable / noUnnecessaryConditions 等 Promise/异步陷阱) - domains.drizzle: "recommended"、domains.react: "recommended" - 显式开启 suspicious.noImportCycles(2.4 已 promote) 文档 - AGENTS.md 在 Stack & runtime 段加 "Prefer Bun-native APIs" 原则,列出 UUIDv7/SHA-256/sleep/Bun.file 的优先路径 - AGENTS.md 在 Code style (Biome) 段记录本次启用的 lint domain 与 noImportCycles 规则 验证:fix / typecheck / test 3/3 / build 568ms / compile 117M / docker compose 全套(migrate JSON 日志 ✓、UUIDv7 写入 ✓、SIGTERM shutdown 正确序列化 ✓)
This commit is contained in:
+2
-3
@@ -6,12 +6,11 @@ export default defineCommand({
|
||||
description: 'Apply pending database migrations',
|
||||
},
|
||||
async run() {
|
||||
const [{ env }, { drizzle }, { sql }, { embeddedMigrations }, { createHash }, { getLogger }] = await Promise.all([
|
||||
const [{ env }, { drizzle }, { sql }, { embeddedMigrations }, { getLogger }] = await Promise.all([
|
||||
import('@/env'),
|
||||
import('drizzle-orm/postgres-js'),
|
||||
import('drizzle-orm'),
|
||||
import('@/server/db/migrations.gen'),
|
||||
import('node:crypto'),
|
||||
import('@/server/logger'),
|
||||
])
|
||||
|
||||
@@ -22,7 +21,7 @@ export default defineCommand({
|
||||
return
|
||||
}
|
||||
|
||||
const sha256 = (s: string) => createHash('sha256').update(s).digest('hex')
|
||||
const sha256 = (s: string) => Bun.CryptoHasher.hash('sha256', s, 'hex')
|
||||
|
||||
const db = drizzle({ connection: { url: env.DATABASE_URL, max: 1, onnotice: () => {} } })
|
||||
try {
|
||||
|
||||
@@ -9,7 +9,9 @@ export const logError = (error: unknown) => {
|
||||
}
|
||||
|
||||
export const handleValidationError = (error: unknown) => {
|
||||
if (error instanceof ORPCError && error.code === 'BAD_REQUEST' && error.cause instanceof ValidationError) {
|
||||
if (!(error instanceof ORPCError) || !(error.cause instanceof ValidationError)) return
|
||||
|
||||
if (error.code === 'BAD_REQUEST') {
|
||||
// ORPC widens issues to the Standard Schema shape; every contract here is built from Zod/drizzle-zod,
|
||||
// so the runtime objects are Zod issues. Rehydrate to reuse z.prettifyError / z.flattenError.
|
||||
const zodError = new z.ZodError(error.cause.issues as z.core.$ZodIssue[])
|
||||
@@ -22,7 +24,7 @@ export const handleValidationError = (error: unknown) => {
|
||||
})
|
||||
}
|
||||
|
||||
if (error instanceof ORPCError && error.code === 'INTERNAL_SERVER_ERROR' && error.cause instanceof ValidationError) {
|
||||
if (error.code === 'INTERNAL_SERVER_ERROR') {
|
||||
throw new ORPCError('OUTPUT_VALIDATION_FAILED', {
|
||||
cause: error.cause,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { ContractRouterClient, InferContractRouterInputs, InferContractRouterOutputs } from '@orpc/contract'
|
||||
import type { ContractRouterClient, InferContractRouterOutputs } from '@orpc/contract'
|
||||
import type { Contract } from './contracts'
|
||||
|
||||
export type RouterClient = ContractRouterClient<Contract>
|
||||
export type RouterInputs = InferContractRouterInputs<Contract>
|
||||
export type RouterOutputs = InferContractRouterOutputs<Contract>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { timestamp, uuid } from 'drizzle-orm/pg-core'
|
||||
import { v7 as uuidv7 } from 'uuid'
|
||||
|
||||
export const generatedFields = {
|
||||
id: uuid('id')
|
||||
.primaryKey()
|
||||
.$defaultFn(() => uuidv7()),
|
||||
.$defaultFn(() => Bun.randomUUIDv7()),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true })
|
||||
.notNull()
|
||||
|
||||
@@ -7,7 +7,7 @@ export default () => {
|
||||
const logger = getLogger(['shutdown'])
|
||||
let exiting = false
|
||||
|
||||
const shutdown = (signal: NodeJS.Signals) => {
|
||||
const shutdown = async (signal: NodeJS.Signals) => {
|
||||
if (exiting) {
|
||||
logger.warn('Forcing exit on repeated signal', { signal })
|
||||
process.exit(0)
|
||||
@@ -15,14 +15,16 @@ export default () => {
|
||||
exiting = true
|
||||
logger.info('Draining for shutdown', { signal, graceMs: 500 })
|
||||
|
||||
setTimeout(() => {
|
||||
db.$client.end().finally(() => {
|
||||
logger.info('DB pool closed, exiting')
|
||||
process.exit(0)
|
||||
})
|
||||
}, 500)
|
||||
await Bun.sleep(500)
|
||||
try {
|
||||
await db.$client.end()
|
||||
logger.info('DB pool closed, exiting')
|
||||
} catch (error) {
|
||||
logger.error('DB pool close failed during shutdown', { error })
|
||||
}
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
process.on('SIGINT', () => shutdown('SIGINT'))
|
||||
process.on('SIGTERM', () => shutdown('SIGTERM'))
|
||||
process.on('SIGINT', () => void shutdown('SIGINT'))
|
||||
process.on('SIGTERM', () => void shutdown('SIGTERM'))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user