feat(runtime): standalone stack 认 mysql://,并对 OS_DATABASE_DRIVER 做真校验 (#6265) - #6344
Merged
qq9340100 merged 1 commit intoAug 7, 2026
Merged
Conversation
…DATABASE_DRIVER (#6265) Two halves of one defect family in `standalone-stack.ts` — a driver selection this stack could not dispatch. (a) `mysql://` — the #5820 split with a different scheme. The CLI has classified `mysql[2]://` as `mysql` since forever (`inferDriverTypeFromUrl`), the shared factory has always been able to build it (`kind === 'mysql'` -> SqlDriver on `mysql2`), and the docs' URL-inference table lists it; only `detectDriverFromUrl` here had no arm, so one `OS_DATABASE_URL=mysql://…` booted under `os start` and died under `os migrate` with `Unsupported database URL scheme`. - detection arm uses character-for-character the CLI's regex; dispatch declares `{ driver: 'mysql', config: { url } }` for the shared factory, like postgres. - no dependency change: `mysql2` is already an optional peer of `@objectstack/driver-sql`, the same posture `pg` has. - `sqliteFile` stays null for a MySQL target (occupancy-gate semantics). (b) `OS_DATABASE_DRIVER` was a bare `as` cast while `cfg.databaseDriver` went through a zod enum. An unknown value matched no dispatch arm and landed in the chain's trailing `else`: SQLite, silently (#3276 class). - one declaration (`StandaloneDatabaseDriverSchema`) now feeds the config key, the env value and the `ResolvedDriverKind` union; the refusal enumerates `.options` instead of a fourth hand-written list. - unknown value -> loud throw naming the value and every legal driver; the env value is lower-cased first, matching the CLI's reader of the same variable. - the trailing `else` is a `never` guard now, so the next kind added without a dispatch arm is a compile error rather than a wrong database. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wbxm29qPKnLf44AbSxizqW
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 21 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
Collaborator
Author
|
范围外发现已归档:#6345(未认领、未贴标签,留给 PM 分诊)。 本 PR 正文「范围声明」里提到的两点分叉,加上第三点,已合并记入 #6345:
三条都不在本 PR 的裁决面内(#6265 裁的是「mysql 识别」+「env 值真校验」),顺手改等于未裁扩/收公开配置契约,故不在此 PR 内动。 Generated by Claude Code |
qq9340100
marked this pull request as ready for review
August 7, 2026 14:37
qq9340100
deleted the
claude/issue-6265-standalone-mysql-and-driver-validation
branch
August 7, 2026 15:02
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 #6265
两半同批,同属一个缺陷家族:一个本栈无法 dispatch 的 driver 选择。落点全部在
packages/runtime/src/standalone-stack.ts。前提重验(当前
origin/main=db59e9c5f,#6272 已并入,行号按合并后实读)mysql[2]://if (/^mysql2?:\/\//i.test(u)) return 'mysql';,resolveStorageDefinition有mysql臂,产出{ driverId: 'mysql', config: { url, ...autoMigrate } }packages/cli/src/utils/storage-driver.ts:128/:288if (kind === 'mysql') { const { SqlDriver } = await import('@objectstack/driver-sql'); … client: 'mysql2' }packages/services/service-datasource/src/default-datasource-driver-factory.ts:423detectDriverFromUrl()依次 memory / postgres / mongodb / libsql / turso-https / wasm / file / 裸路径,随后 throw;无 mysqlstandalone-stack.ts:164(改前)?? (process.env.OS_DATABASE_DRIVER?.trim() as ResolvedDriverKind | undefined)standalone-stack.ts:236(改前)} else { // sqlite (better-sqlite3) …standalone-stack.ts:367(改前)三条全部坐实,前提有效。补充两条实读旁证:
content/docs/deployment/environment-variables.mdx:52已把mysql列为OS_DATABASE_DRIVER合法值,content/docs/data-modeling/drivers.mdx的 URL 推断表也已列mysql://…—— 文档早就在宣传本栈不认的能力(declared ≠ enforced),本 PR 让文档变真,故不需要改文档;mysql2是@objectstack/driver-sql的 optional peer(与pg完全同姿势),而@objectstack/driver-sql已是@objectstack/runtime的 dependency —— 所以本 PR 零依赖变更,不碰package.json/pnpm-lock.yaml。半 (a):
mysql://识别 + 定义detectDriverFromUrl()补一臂,正则与 CLI 逐字同款/^mysql2?:\/\//i。理由同 feat(runtime): standalone stack 分派libsql://,经可选 Turso driver 包 (#5820) #6272 的 turso 臂:两个函数对同一个OS_DATABASE_URL回答同一个问题,分叉本身就是病因(os start起得来、os migrate起不来)。createStandaloneStack()补{ driver: 'mysql', config: { url } }交给共享工厂 —— 与 postgres 臂同形。不牵扯可选包,不需要 turso 那样的 host factory seam。databaseDriverenum 补'mysql';Supported schemes:列表补mysql://, mysql2://。sqliteFile恒null(occupancy gate 语义,参照 feat(runtime): standalone stack 分派libsql://,经可选 Turso driver 包 (#5820) #6272 对 turso 的处理):CLI 的migrate-occupancy-gate.ts只读target.sqliteFile,远端 server 无话可说,更不能把 DSN 当文件路径读。半 (b):
OS_DATABASE_DRIVER真校验 —— 两条口径归一改前
cfg.databaseDriver走 zod enum(响亮拒绝),env 那一路是as断言(运行期零校验)。未知值不匹配任何分派臂,落到末尾else= SQLite,一声不吭:OS_DATABASE_DRIVER=mysql且未设 URL ⇒ 默认file:…/standalone.db⇒ 静默建 SQLite 库,操作者以为在连 MySQL;mysq1/postgress)同样静默落 SQLite;sqlite driver was selected but the URL does not look like a file path—— 对一个从没选过 sqlite 的操作者双重误导。修法(不留第三份清单):
StandaloneDatabaseDriverSchema作为唯一一份 driver 词表:databaseDriver用它、env 值用它safeParse、ResolvedDriverKind由它z.infer推出;.options.join(', ')推导输出(⛔ 不手写数组,加一个 kind 不会留下过期清单);Unsupported OS_DATABASE_DRIVER value: "…"vs 既有Unsupported database URL scheme: …,互不冒充(有钉子);toLowerCase():CLI 读同一个变量时就是(explicitDriver ?? '').toLowerCase().trim()。若这里大小写敏感,等于把本 issue 要消灭的分叉换个更窄的形状再开一遍。被接受的词表没有变 —— 就是那个 enum,别无其他;else不再等于 sqlite,而是const unreachable: never = dbDriver;—— 下一个加进 enum 却忘了补臂的 kind 是编译错误,不再是一个连错库的运行时。反向验证(先预测方向,再跑)
两轮拆除,方向都事先写死再执行。
Pass A —— 摘掉半 (a)(detect 臂 + dispatch 臂 + 消息里的
mysql://)。预测: 走 detect 的钉子红(Unsupported scheme);走 explicit driver 的钉子仍绿(它们根本不经过 detect);supports('mysql')那条仍绿(它钉的是共享工厂那一端,不是本文件)。实测 9 红 22 绿,逐条吻合:同一状态下
pnpm --filter @objectstack/runtime typecheck也红 —— 这正是never卫兵的价值,值得单列:注意这里是「诊断变多/变早」而非单纯变红:改前同样的遗漏是静默 sqlite,现在是编译期报错。
Pass B —— 把半 (b) 原样还原(裸
as+ 末尾else= sqlite)。预测: 三条「响亮拒绝」钉子红,外加大小写归一那条红;「两类错误可分辨」和「空白值 = 未设」两条按构造仍绿。实测 4 红 27 绿,逐条吻合:两轮后源码逐字还原,复跑 31/31 绿。
测试与门禁
packages/runtime/src/standalone-stack.mysql.test.ts:31 条,含识别、整栈定义、env 校验(有 URL / 无 URL 两种情形)、正对照全家(含 libsql/turso)、wat://nope不变(fix(cli): os migrate --json 成功时不再把耗时毫秒当成退出码 (#4873) #6220 e2e 从 CLI 端钉着同一条消息)。pnpm --filter @objectstack/runtime test⇒ 108 files / 1578 tests passed(feat(runtime): standalone stack 分派libsql://,经可选 Turso driver 包 (#5820) #6272 刚落的 31 条 libsql 钉子在内,其断言面未被触碰)。pnpm --filter @objectstack/runtime typecheck⇒ 干净。npx eslint --no-inline-config改动两文件 ⇒ 无输出。node scripts/check-nul-bytes.mjs⇒ OK;改动文件另做一次grep -naP控制字符自扫,无命中。resolveStandaloneDatabase的跨包消费方只有packages/cli/src/utils/migrate-occupancy-gate.ts(只读sqliteFile)与 barrel 导出;Unsupported database URL scheme的仓内断言只有packages/cli/test/migrate-exit-code.e2e.test.ts:210(wat://nope,行为未变)。changeset
.changeset/standalone-stack-mysql-and-driver-validation.md=@objectstack/runtime: minor。判据同 #6272 的 minor 判例:新增可用 URL scheme + enum 扩值属功能新增;半 (b) 的响亮拒绝是行为变更,但它替换掉的是「静默连错库」,没有任何人能依赖那个旧行为。范围声明
不碰
content/docs/releases/、package.json、pnpm-lock.yaml;不改 assignee、不自贴标签。explicit-driver 的别名词表(CLI 收pg/mysql2/mongo/libsql等,本栈 enum 只收规范拼写)与「显式非文件 driver 却没给 URL」这两点仍有分叉,另行开 issue 记录,不在本 PR 内顺手扩契约。Generated by Claude Code