fix(service-datasource): datasource fail-fast 认得「工作区未构建」这个成因 (#5794) - #6302
Merged
Conversation
connect 失败的 fail-fast 报错原本只有一句收尾建议,不分成因: "Fix the datasource configuration, or set OS_ALLOW_DRIVER_CONNECT_FAILURE=1 to boot anyway"。对「数据库真连不上」它是对的;对「驱动包没构建」两半都有害 —— 配置本来就是好的,而设开关只会让半个工作区宣称启动成功、然后对每个请求 报错。唯一有效的修法 pnpm build 一个字都没提。 现在按成因选收尾句:底层错误是模块解析失败时(ESM import() 的 err.code === 'ERR_MODULE_NOT_FOUND'、CJS require() 的 'MODULE_NOT_FOUND'; code 被 re-throw 丢掉时退回 Cannot find module / Cannot find package 文本), 消息只给一个正确修法 `pnpm install && pnpm build`,并且不提那个逃生开关。 判据复用 @objectstack/types 的 isModuleNotFoundError(framework#3265 起的 唯一所有者),不另起一份;结构化的 err.code 原本在 handleFailure 只收 reason: string 时被丢弃,所以抛出值本身现在也一并传入。 纯诊断分类,零行为变化:fail-fast 的判定与时机、抛出的错误类型、保留的连接 状态、以及 OS_ALLOW_DRIVER_CONNECT_FAILURE 的降级启动路径全部不变;其它成因 的消息逐字未动。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015a5qkLzpGXhLL2F5gvJ7dD
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckNo hand-written docs reference the 1 changed package(s). ✅ |
hotlong
marked this pull request as ready for review
August 7, 2026 13:33
hotlong
enabled auto-merge
August 7, 2026 13:33
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5794
前提复核(先做的一步)
issue 正文锚在
:687-701;当前origin/main(dbe92a7e1)上该文件已长到 796 行,这段代码在
:754-758—— 按 issue 自己的提示以内容定位,措辞一字未变,前提成立。仓库里还有一份独立佐证:
scripts/check-dev-prereqs.mjs(#5795 / #5863)的头注释逐字写着那个 "separate services-lane issue" 就是本单。
抛出值上到底有什么(实测,不是猜)
判据要「结构化信号优先」,前提是先知道抛出值上真实可读的是什么。在本 worktree
(装了 node_modules、没 build)实测三种形状:
err.codeerr.message首行import('@objectstack/driver-sql')ERR_MODULE_NOT_FOUNDCannot find module '…/driver-sql/dist/index.mjs' imported from …require('@objectstack/driver-sql')MODULE_NOT_FOUNDCannot find module '…/driver-sql/dist/index.js'ERR_MODULE_NOT_FOUNDCannot find package '@objectstack/driver-sql' imported from …(第三行就是 issue 正文引的那种形状。)三种都带结构化
code。同时发现:这个信号原本在到达消息之前就被丢掉了。
attemptConnect的末尾 catch 只把
errMsg(err)传给handleFailure,而handleFailure的签名收的是reason: string—— 字符串化的那一刻
err.code就没了。所以本 PR 除了换文案,还要把抛出值本身传下去。
改了什么
新增
packages/services/service-datasource/src/connect-failure-remedy.ts:把 fail-fast报错的收尾句按成因分成两支,两段文案 + 为什么这么写的理由都放在这一个文件里。
@objectstack/types的isModuleNotFoundError,不另起一份。那是framework#3265 起这个判断的唯一所有者(CLI 的 optional-plugin 守卫、
requires能力解析器都走它);再抄一份就有权和它分叉,而分叉的方式恰好就是把 ESM 的
Cannot find package误判成崩溃 —— framework#1595 那个 bug 本身。它先看
err.code、再退回文本匹配。文本那一支在这条路径上不是备胎:factory 的sqlite-wasm和mongo两个 arm 会 catch 掉 import 失败、new Error(...)重抛,把原 message 插进去而丢掉 code,那两个 arm 到这里只剩文本信号。
逃生舱就是它被用掉的方式:一个已经卡住的人会去找最短的那行看起来能让他继续的话。
这与同包
datasource-pool-support.ts(datasourcepool声明在 sqlite / sqlite-wasm 驱动臂被静默丢弃(pg / mysql 生效) #5714 / datasourcepool声明在 memory 驱动臂同样被静默丢弃(#5714 的姊妹臂,裁决未覆盖) #5931)、与check:dev-prereqs(根
dev脚本没有任何一步确认工作区已构建 —— 前置检查只有 check:console-sha,未构建时放行进入 12 段噪音现场 #5795,它自己的 pin 断言红字里修法只出现一次)是同一条消息纪律。pnpm install && pnpm build的完整形而不是光pnpm build:ERR_MODULE_NOT_FOUND分不出「装了没构建」和「根本没装」,合起来这一条对两种都对 —— 和
check-dev-prereqs对无
node_modules工作区的选择一致。实际输出(用真实的
bootCriticaldefault datasource 跑出来的)改之前 / 其它成因(逐字未动):
改之后 / 未构建成因:
收尾句之前的部分(datasource 名、底层原因、绑定对象清单、ADR-0062 D5 的裁定)一字未改。
零行为变化
fail-fast 的判定与时机、抛出的错误类型(仍是 plain
Error)、保留的连接状态与上报给engine 的
markDatasourceUnavailable、以及设了OS_ALLOW_DRIVER_CONNECT_FAILURE时的降级启动路径与横幅 —— 全部不变,并且都单独钉住了。
反向验证(方向是先预判再跑的)
预判:把分类关掉(让
connectFailureRemedy恒返回通用文案),新的未构建 pin 应该全红,而所有「其它成因逐字不变」与「零行为变化」的 pin 应该全绿 —— 因为通用文案没被这次改动碰过。
实测与预判一致:
红的正好是「两个判据」那一组的 5 条,其余 60 条全绿。两点值得记下来,免得下一位读成别的:
keeps everything ABOVE the remedy这条没有变红,而且它就该不变红 —— 它只断言共享前缀,而分类开关碰不到前缀。
isUnbuiltWorkspaceFailure的那组单测也没有变红 —— 我关掉的是分派,不是分类器本身。也就是说分类器的 pin 和分派的 pin 各自独立承重,不是一组的复读。
测试
新增 20 条 pin,分四组:两个判据(code / message 各有覆盖)、其它成因逐字不变、
零行为变化、以及分类器本身的单测(含两条负例:真连接失败、以及
ERR_DLOPEN_FAILED的原生 addon ABI 不匹配 —— 那是要 rebuild,不是工作区没构建,必须不被认成未构建)。
消费半径已扫
改的是
handleFailure的消息,凡是跑到这条路径的包都读它,所以按调用者而不是按改动的包来扫。全仓 grep 那句通用文案,
packages/services/service-datasource之外没有任何测试钉它;唯一的另一处出现是
packages/objectql/src/driver-connect-errors.ts的DriverConnectError—— 另一个包、另一个类、另一条路径,本 PR 未触及(见下)。消费方
packages/runtime的 4 个 datasource 测试文件跑通,日志里能直接看到凭据失败与不受支持驱动这两个成因的消息逐字未变。
刻意划出去的边界
OS_ALLOW_DRIVER_CONNECT_FAILURE那一支)未改。 那一支不给建议,它描述的是使用者已经主动选进去的状态;而且 fail-fast 这句不再推销那个开关之后,
未构建这个成因基本就到不了那一支了 —— 承重的修法在 fail-fast 这边。
packages/objectql的DriverConnectError未改。 它带着同样的两句建议,但属于另一个包的另一条路径(driver 已注册、其
connect()拒绝),而未构建的工作区里 driver根本构造不出来、到不了那里。它是否真能被模块解析失败命中,我没有实测,所以不在
本 PR 里顺手改,按 Prime Directive chore: version packages #10 另立观察类 finding。
Generated by Claude Code