Repository navigation
Commit af52253
fix(service-datasource): testing a datasource draft no longer creates its SQLite database file (#27603)
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuPMZ8BHWtPf6nBTp51qSM64)_
Fixes #27512
Clause-②: no
Metadata-protocol: none
Skills-docs: none — Metadata-protocol is none and there is no narrowing
arm; nothing an author writes changes.
## 维护者速读
- **改了什么**:数据源草稿的「测试连接」(`POST /api/v1/datasources/test`)遇到不存在的 SQLite
文件时,改为回报「文件不存在」,不再在磁盘上创建这个文件和它缺失的目录。
- **为什么改**:测试连接什么都不保存,却会按「启动写库」的方式打开
SQLite,管理员输错路径就会在服务器上留下空数据库文件(sqlite、sqlite-wasm、turso `file:`
三种驱动实测都会建)。
- **风险与回滚**:只改草稿探测这一条路,判断在探测内部、建驱动之前;导出类型与默认工厂与 main
字节一致,保存数据源、启动恢复、热注册连接池仍照旧建文件。回滚即还原 `datasource-admin-plugin.ts`。
- **先看**:`datasource-admin-plugin.ts` 的 `probe`(调工厂前的
`absentDatabaseFileRefusal` 一行)及文件尾部的 `probedDatabaseFile` /
`absentDatabaseFileRefusal`。
- **两分钟验证**:`pnpm --filter @objectstack/service-datasource exec vitest
run src/__tests__/draft-probe-creates-no-database-file.test.ts`,预期 15/15
通过。
- **需要你做的**:无,席位落地。
## What changes
- `DatasourceAdminServicePlugin.probe` (the draft probe behind `POST
/api/v1/datasources/test`) judges a file-backed draft before it asks the
driver factory for anything: `sqlite` / `sqlite3` and `sqlite-wasm`
(`config.filename`, absent ⇒ `:memory:`), and `turso` / `libsql` with a
`file:` url (scheme in any letter case, path as driver-turso's local
engine opens it). A draft whose file does not exist is answered on the
probe's existing verdict door, HTTP `200` with `result.ok: false` and an
error naming the file (`SQLite database file 'PATH' does not exist.
Testing a connection never creates it: …`); a path that can be neither
opened nor created gets driver-sql's own `SqliteDatabaseFileError` text.
Nothing is written to find out.
- The existence judgement is driver-sql's one write-free check,
`resolveSqliteAbsentFileTarget` (an absent file reads
`openedEmptyInMemory`; an unopenable one throws), so no second
`existsSync` is open-coded.
- Internal only: the two helpers are module-private in
`datasource-admin-plugin.ts`. `contracts/datasource-driver-factory.ts`,
`default-datasource-driver-factory.ts` and every export are
byte-identical to `main`. Because the check is the probe's own, it holds
whichever driver factory the host registers.
- Unchanged: Save, the boot restore, hot pools and
`DatasourceConnectionService` build through the factory exactly as
before and still create the file; `:memory:` and server databases are
never judged; the Setup page already renders `result.error`.
## Tests
New `src/__tests__/draft-probe-creates-no-database-file.test.ts`,
through the real door (real `HonoHttpServer`, real admin routes, real
plugin probe, default driver factory unless the case says otherwise):
- 10 cases: each of `sqlite`, `sqlite3`, `sqlite-wasm`, `turso`,
`libsql` × a missing flat path and a missing nested path → `200`,
`success: true`, `result.ok: false`, error names the file, the file does
not exist and the temp root is still empty (no directory made).
- A host factory (a fake whose `create` is a spy) is never asked to
build any of the five drafts on a missing path; all answer `ok: false`;
nothing created.
- A path under a regular file (can be neither opened nor created) → `ok:
false` naming the file; only the blocker file remains.
- Controls: an existing SQLite file still probes `ok: true` and nothing
else appears beside it; a `:memory:` draft probes `ok: true`; the
default factory, used directly as Save uses it, still creates the file
on connect.
## Acceptance notes
- **PM assumption measured.** Before the fix, a probe on a missing path
created the file AND its missing directories for `sqlite`, `sqlite-wasm`
and `turso` (`file:` url); measured through
`DatasourceAdminServicePlugin.probe` with the default factory on the
built tree (all three answered `ok: true` and left `nested/dir/X.db`
behind). `memory` with file persistence creates nothing on a probe
(measured), so it is not judged.
- **The existing `sqliteAbsentFile: 'empty-in-memory'` option was not
used**, as the order said: it is factory-wide and answers `ok` on a
missing file.
- **Shape changed in review (seat REWORK on `d9b68b90`).** The first
shape carried a `fileMustExist` intent on `DatasourceConnectionSpec`, an
exported type, which widened the published surface (Clause-② would have
been yes). The judgement now lives inside the probe; the contract and
the default factory are back to `main`'s bytes, so `Clause-②: no` holds.
- **"code + status" read as the probe's verdict door.** The draft probe
answers every failure as `200 { result: { ok: false, error } }`;
`TestConnectionResult` has no `code` field, and adding one is a
response-shape addition this card did not ask for, so it was not made.
The door pin asserts status `200`, `ok: false`, the named file and the
untouched filesystem; the kind is carried by the error's text, as for
every other probe failure.
- **Read-only open not added.** Triage said "read-only and must-exist".
Must-exist is what stops the file being created. For an EXISTING file,
`SqlDriver.connect()` already writes nothing to a file whose pragmas are
set (#21734); a read-only open would instead fail the probe on a legacy
`auto_vacuum=NONE` file, whose connect issues the setter (read from the
code, not measured). Left as is.
- **Embedded replica.** A `turso` draft whose local replica is a `file:`
url that does not exist yet is now refused by the probe too (a first
connect would have created the replica file). Consistent with the card:
the probe creates nothing.
- The door pin asserts the error names the file and contains `does not
exist`: the card's acceptance is literally that the probe reports the
file does not exist, and the door carries no code, so that phrase is the
one kind-bearing fact the Setup page shows.
- Branch is the card thread's harness-pinned
`claude/project-thread-yhiysu` (the claim names it).
## Verification
All readings on head `d3b198ec` unless the line says otherwise.
<details><summary>Red first, then green (the new test file)</summary>
- Before any fix (the base `72b08eec` + the first version of the test):
`Tests 11 failed | 3 passed (14)`, every door case `expected true to be
false` (the probe answered `ok: true` and left the file).
- Final head: `Tests 15 passed (15)`.
</details>
<details><summary>Package suite and typecheck</summary>
- `pnpm --filter @objectstack/service-datasource test`: `Test Files 59
passed (59)` · `Tests 999 passed (999)`; `typecheck` exit 0 (tsconfig
includes `src/**`, tests included). Lock VERDICT command-exit 0.
</details>
<details><summary>Ablation (scripts/ablation-replace.mjs, restored blob
== HEAD)</summary>
- The probe's guard line (`if (fileRefusal) return …`) deleted on
`d3b198ec`: mutation landed (anchor 1 → 0, blob `9c6083e9` → `abcba580`)
→ `Tests 11 failed | 4 passed (15)`: the ten door cases and the
host-factory pin red → restored, blob == HEAD `9c6083e9`, `git diff
HEAD` empty.
- The unopenable-path case stays green under the ablation, as expected:
without the guard the real driver's connect refuses that path itself and
creates nothing; it pins the probe's answer for it, not the guard.
- Subjects are imported from `src/` by relative path: no rebuild in the
path.
</details>
<details><summary>Derived gates and the --ran reconciliation</summary>
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` on `d3b198ec` → 59 commands, each run with
its exit captured before any pipe: 59 × `exit 0` (full build present,
`service-datasource` rebuilt on this head, so
`check:dual-build-cjs-loads` measured).
- `--ran`: `59 derived, 59 run, 0 NOT-MEASURED, 0 UNRUN`.
- The run on the first shape (`bf1d2ab4`) caught
`check:dispatcher-error-vocabulary` reading a literal `ENOENT` class
field as an unregistered ADR-0112 code; that refusal class no longer
exists in this shape.
</details>
<details><summary>Lint, conformance ledger, public face,
merge-tree</summary>
- `eslint packages/services/service-datasource --no-inline-config` on
`d3b198ec`: exit 0. Repo-wide `pnpm lint` exit 0 on the first head
`bf1d2ab4`.
- `check:driver-conformance` before (merge base) and after: `55 covered
cell(s), 0 in the DEBT ledger, 0 exempt` both times. No driver package
is touched.
- `git diff` of `contracts/`, `default-datasource-driver-factory.ts` and
`index.ts` against the merged `main` (`1ed33609`): empty.
- `git merge-tree` against the one other open PR on `service-datasource`
(#27574): clean, no shared file.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 0e33108 commit af52253
3 files changed
Lines changed: 267 additions & 0 deletions
File tree
- .changeset
- packages/services/service-datasource/src
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
7 | 8 | | |
8 | 9 | | |
9 | 10 | | |
| |||
21 | 22 | | |
22 | 23 | | |
23 | 24 | | |
| 25 | + | |
24 | 26 | | |
25 | 27 | | |
26 | 28 | | |
| |||
872 | 874 | | |
873 | 875 | | |
874 | 876 | | |
| 877 | + | |
| 878 | + | |
| 879 | + | |
| 880 | + | |
| 881 | + | |
875 | 882 | | |
876 | 883 | | |
877 | 884 | | |
| |||
958 | 965 | | |
959 | 966 | | |
960 | 967 | | |
| 968 | + | |
| 969 | + | |
| 970 | + | |
| 971 | + | |
| 972 | + | |
| 973 | + | |
| 974 | + | |
| 975 | + | |
| 976 | + | |
| 977 | + | |
| 978 | + | |
| 979 | + | |
| 980 | + | |
| 981 | + | |
| 982 | + | |
| 983 | + | |
| 984 | + | |
| 985 | + | |
| 986 | + | |
| 987 | + | |
| 988 | + | |
| 989 | + | |
| 990 | + | |
| 991 | + | |
| 992 | + | |
| 993 | + | |
| 994 | + | |
| 995 | + | |
| 996 | + | |
| 997 | + | |
| 998 | + | |
| 999 | + | |
| 1000 | + | |
| 1001 | + | |
| 1002 | + | |
| 1003 | + | |
| 1004 | + | |
| 1005 | + | |
| 1006 | + | |
| 1007 | + | |
| 1008 | + | |
| 1009 | + | |
| 1010 | + | |
| 1011 | + | |
| 1012 | + | |
| 1013 | + | |
| 1014 | + | |
| 1015 | + | |
| 1016 | + | |
| 1017 | + | |
| 1018 | + | |
| 1019 | + | |
| 1020 | + | |
| 1021 | + | |
| 1022 | + | |
| 1023 | + | |
961 | 1024 | | |
962 | 1025 | | |
963 | 1026 | | |
| |||
0 commit comments