Repository navigation
fix(service-datasource): a missing database client (pg, mysql2, better-sqlite3) is refused with its install, not the unbuilt-workspace build advice - #24933
Conversation
…s install, not the unbuilt-workspace build advice connectFailureRemedy() chose the unbuilt-workspace remedy for every module-not-found. knex reports a missing pg / mysql2 / better-sqlite3 as a bare specifier (and drops the code), which is a missing install in the project, not a build fault; those three now end on the install of that client. Paths and @objectstack packages keep the unbuilt remedy. Claude-Session: https://claude.ai/code/session_011TdZ8GjixNZFLz1xJD9zzK Co-authored-by: Claude <noreply@anthropic.com>
… and on the real boot-refusal path Unit: each measured knex client shape (pg, mysql2, better-sqlite3) ends on the install of that client; paths, @objectstack packages, subpaths and hard dependencies keep the unbuilt remedy; ECONNREFUSED and 28P01 keep the generic sentence. Real path: the real factory and connection service refuse the boot-critical default with the client made unresolvable through Node's own resolver, with a client-present control. Claude-Session: https://claude.ai/code/session_011TdZ8GjixNZFLz1xJD9zzK Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TdZ8GjixNZFLz1xJD9zzK Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TdZ8GjixNZFLz1xJD9zzK Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check7 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 2 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 75f389d7f0af8b64feb94e807a82a08b502878f4 && git checkout 75f389d7f0af8b64feb94e807a82a08b502878f4
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 71696121a64d8bd057fac969a72a84295890ed37 907bd42fdbe04a4174fbe07a32d5acf9cc49b438 && git checkout -B drift-repro 71696121a64d8bd057fac969a72a84295890ed37 && git merge --no-ff 907bd42fdbe04a4174fbe07a32d5acf9cc49b438
node scripts/docs-audit/affected-docs.mjs --json 71696121a64d8bd057fac969a72a84295890ed37 |
Claude-Session: https://claude.ai/code/session_011TdZ8GjixNZFLz1xJD9zzK Co-authored-by: Claude <noreply@anthropic.com>
…eed their client installed The package table and the paragraph under it said the runtime brings the database drivers and that pointing OS_DATABASE_URL at Postgres, MySQL or MongoDB needs no code changes. SQLite and MongoDB do work as installed; pg and mysql2 are optional peers of driver-sql the project installs itself, and without them the boot is refused. Claude-Session: https://claude.ai/code/session_011TdZ8GjixNZFLz1xJD9zzK Co-authored-by: Claude <noreply@anthropic.com>
席位核对 · ACCEPT ·
|
Fixes #24909
Clause-②: no
What was wrong
When a boot-critical datasource could not connect because a module could not be resolved,
connectFailureRemedy()always ended the refusal onUNBUILT_WORKSPACE_REMEDY: "… That is a build precondition … Runpnpm install && pnpm build, then start again." That is right for the case it was written for, a monorepo worktree whose driverdist/is missing. It is wrong for a project that points at PostgreSQL or MySQL without installing the database client.pgandmysql2are optional peers of@objectstack/driver-sql, andbetter-sqlite3is an optional dependency of it. The project installs them itself, so neither command adds them, and a reader who followed the advice was refused again with the same text.What changed
All of the code change is in
packages/services/service-datasource/src/connect-failure-remedy.ts. It is a message branch only.Cannot find module|package 'X'in the error text.OPTIONAL_DRIVER_CLIENT_PACKAGES(pg,mysql2,better-sqlite3), the refusal ends onmissingDriverClientRemedy(X). That list is the clients the factory's knex arms load. The remedy names the install of that client once (npm install X, withpnpm add X / yarn add Xbeside it). It names no build step, no environment variable and no configuration key.isUnbuiltWorkspaceFailure()no longer answers true for that case.@objectstack/…package, a subpath and any other bare name keepUNBUILT_WORKSPACE_REMEDY. Every cause that is not a module-not-found keepsGENERIC_CONNECT_FAILURE_REMEDYbyte for byte. When the boot is refused, the thrown error type, the retained connection state and theOS_ALLOW_DRIVER_CONNECT_FAILUREdegraded boot do not change.packages/types/**,packages/spec/**andpackages/drivers/**are untouched.isModuleNotFoundErroris still the gate in front of the new read.The outside suggestion in comment 6292687799 proposed the same split: keep the unbuilt remedy for missing build output, add an install remedy for a missing optional peer, pin both, and keep
ECONNREFUSEDunchanged. This PR adopts that idea. Its point about keeping the original thrown error long enough to classify it was already true on this path, becausehandleFailurereceives the thrown value. The measurement below shows the specifier exists only in the text anyway.Measurements (dispatch assumptions H1 to H4)
Taken on the branch base
b42c621c05. Between the@objectstack/service-datasource@18.0.0tag and that base,connect-failure-remedy.tsandpackages/types/src/module-not-found.tsare byte-identical (emptygit diff), so the readings hold for the published release.H1, confirmed. The base chooser was
isModuleNotFoundError(cause) ? UNBUILT : GENERIC. Nothing on the path read the specifier.H2, measured. The real factory was run (
createDefaultDatasourceDriverFactory({ dev: false }), built ESM and CJS dist), with the client made unresolvable through Node's own resolver. postgres/pg, mysql/mysql2and sqlite/better-sqlite3all throw knex's wrapperKnex: run/$ npm install X --save/Cannot find module 'X'/Require stack: …, withcodeundefined. The signal is in the text only, the same as for the sqlite-wasm and mongodb arms. Node's own errors for a bare specifier carry no field naming it either: CJS hascodeandrequireStack, and ESMCannot find packagehascodeonly.H3, measured on every arm, ESM and CJS.
Cannot find package '@objectstack/driver-…'under ESM,Cannot find module '@objectstack/driver-…'under CJS).…/dist/index.mjsor…/dist/index.cjs.The split holds on every arm. A missing
@objectstack/driver-turso(and mongodb, sqlite-wasm) stays on the unbuilt remedy in this PR; see open question 2.H4. The server cannot know the package manager. The chosen wording is one command,
npm install X, with the pnpm and yarn spellings of the same command beside it. That is how the factory's optional-driver messages in the same package already spell an install. See open question 1.The card's own path: a published 18.0.0 scaffold outside any workspace
npm create objectstack@18.0.0 my-app -- --skip-skills(installed with pnpm, nopg), thennpx os dev --no-watch -p PORT -d postgres://postgres@127.0.0.1:1/db:pnpm install && pnpm build, then start again." This is the card's refusal, reproduced.pgis not installed, so no connection was attempted. … Install it next to the server that opens this datasource —npm install pg(pnpm add pg / yarn add pg) — then start again."-d mysql://…gives the same ending withmysql2.pnpm add pg(exit 0), then start again. The boot gets past the client and is refused for the next real fault,connect ECONNREFUSED 127.0.0.1:1, ending on the generic sentence unchanged.Tests (head
649414d3b8)pnpm --filter @objectstack/service-datasource typecheck: exit 0.tsc --listFilesconfirms it compiles both changed test files.pnpm --filter @objectstack/service-datasource exec vitest run --maxWorkers=2: 49 files, 883 tests passed.datasource-connection-service.test.ts):pg,mysql2,better-sqlite3), and ESMCannot find package 'pg', ends on that client's install. The remedy containsnpm install Xexactly once and neverpnpm build. NoOS_variable or--databaseappears.UNBUILT_WORKSPACE_REMEDY:@objectstack/driver-sqlnot installed, unbuiltdist, a file insidepg, thepg/lib/nativesubpath, andknexmissing.ECONNREFUSEDand28P01(with their codes) still end on the generic sentence.missing-driver-client-boot-refusal.test.ts):DatasourceConnectionServiceconnect the host's boot-criticaldefaultunderdeclared-auto. The client is made unresolvable through Node's own resolver for that one specifier, so the error is Node's and knex's, not the test's.dev: false), the boot is still refused with a plainError. The message carriesCannot find module 'X', ends on that client's install and never namespnpm build. The verdict is retained (failed-degraded, availabilityfailed).ECONNREFUSEDand ends on the generic sentence.node scripts/ablation-replace.mjs(anchor hit 1 to 0 on disk, blob changed). The subject is imported fromsrc, so no rebuild is involved. Each restore was proven by blob equal to HEAD (78e1c14f40fb) and an emptygit diff HEAD.connectFailureRemedy: 7 red, exactly the predicted set (the 3 knex unit cases, the ESMpgcase and the 3 real-path arms). The 83 others stayed green.isUnbuiltWorkspaceFailure: 1 red, the classifier pin. The 89 others stayed green.Gates
Local, on head
649414d3b8. The list was derived withnode scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths; merge base74fcec1ed). Each exit code was captured before any pipe.pnpm check:dual-build-cjs-loadsexited 3 with PREREQUISITE NOT MET. That gate reads every package's built output, and this worktree built only the service-datasource closure. CI runs it.--ranreconciliation, with each exit recorded: "66 derived famil(ies) accounted for — 65 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)".check:test-source-alias,check:cross-package-test-inputs,check:engine-double-contract,check:doc-authoring,check:nul-bytes,check:type-check-coverage,check:type-check-debt,check-adr-0087-registration --base origin/mainandcheck-empty-changeset --base origin/main.test:repovitest project (the diff touches none of its tests);pnpm lintfirst among them;check:dev-prereqsandcheck:skill-frame-freshness, which nothing in the diff triggers (nopackages/specbuild and no skill-frame file).Open questions
npm install Xonly. B: the module name and a neutral sentence ("addXwith your package manager"). C (chosen):npm install Xwithpnpm add X / yarn add Xbeside it.detect-package-manager.ts), and the card's reader usednpm createand then pnpm. So both npm and pnpm projects are real. A serves only one of them; B and C serve both.npm installinside a pnpm project. B leaves the reader to work out the command.@objectstack/driver-*package (turso, mongodb, sqlite-wasm), reachable today. Measured throughos devon the published 18.0.0 scaffold: a declared non-defaulttursodatasource with a bound object, and no@objectstack/driver-tursoinstalled. The refusal names two fixes. The factory's own text saysnpm install @objectstack/driver-turso, and the appended remedy says "That is a build precondition … Runpnpm install && pnpm build". This PR leaves that case on the unbuilt remedy, because the bare specifier means two things. In a scaffold it is a missing install. In the workspace it is a stale install, whichpnpm installrepairs, andnpm installinside a pnpm workspace would be wrong. The [finding][services] Themongodbandsqlite-wasmarms of the same factory still answer a missing optional package with the fault and no remedy #7385 factory pins also assert that these texts classify as unbuilt.mongodbandsqlite-wasmarms of the same factory still answer a missing optional package with the fault and no remedy #7385 classifier pins to path-shaped causes.@objectstack/driver-tursoonly. The CLI and runtime depend on mongodb and sqlite-wasm outright, so a scaffold normally has them.Acceptance notes
content/docs/getting-started/your-first-project.mdx(the package table and the paragraph under it) says@objectstack/runtime"brings the database drivers with it — you do not install one separately". It also says to "pointOS_DATABASE_URLat Postgres / MySQL / MongoDB for production — no code changes". It never says thatpgormysql2must be installed. This is the step the card's reproduction followed. Not changed here: it does not quote the refusal. It is reported to the seat as a finding.drivers.mdxalready sayspgandmysql2are optional peers to install yourself. No page quotes the old refusal for the missing-client case, so no doc changed.pgwould now be toldnpm install pg (pnpm add pg / …)rather thanpnpm install. pnpm linkspginto the workspace as a devDependency ofdriver-sqlon every install, so this needs an install that predatespg. The module note says so.CHANGELOG.mdorcontent/docs/releases/edit. The changeset.changeset/24909-missing-driver-client-remedy.mdis apatchfor@objectstack/service-datasource.Round 2: the getting-started page (claim amendment 6295994725)
content/docs/getting-started/your-first-project.mdx: only the package table's@objectstack/runtimerow and the paragraph under it changed. No new section.driver-mongodbas an optional dependency.OS_DATABASE_URL.npm install pg/npm install mysql2, the page's npm spelling). It is an optional peer ofdriver-sql, and without it the boot is refused.Measured on a fresh published
create-objectstack@18.0.0scaffold (pnpm-installed, outside any workspace) and on the 18.0.0 npm manifests:@objectstack/runtime@18.0.0: dependenciesdriver-sql,driver-memory,driver-sqlite-wasm; optionalDependenciesdriver-mongodb; optional peerdriver-turso. The workspace head has the same edges.@objectstack/cli@18.0.0: dependenciesdriver-sql,driver-memory,driver-mongodb,driver-sqlite-wasm; optional peerdriver-turso.@objectstack/driver-mongodb@18.0.0depends onmongodb.@objectstack/driver-sql@18.0.0has optional peerspgandmysql2and the optional dependencybetter-sqlite3.driver-mongodb,driver-sqlite-wasmanddriver-sqlresolve from service-datasource, andmongodbresolves fromdriver-mongodb.pgandmysql2do not resolve.os devwith no database flag: "Server is ready" onSqlDriver(better-sqlite3), andGET /api/v1/discoveryanswered 200. The server was stopped afterwards.os dev -d mongodb://127.0.0.1:1/db: exit 1 onconnect ECONNREFUSED 127.0.0.1:1with the generic sentence. The driver and client loaded; only the absent server refused.A correction to round 1: open question 2 said "the CLI and runtime depend on mongodb and sqlite-wasm outright". That holds for the CLI. The runtime carries
driver-mongodbas an optionalDependency, which is installed by default and skipped under--omit=optional.The changeset is unchanged:
content/docs/**publishes nothing, and the existingpatchchangeset covers the code.Gates on head
907bd42fdb, re-derived: 95 commands (the 66 from round 1 plus 29 docs families). The 29 added ones and 8 docs-reading round-1 families (check:doc-authoringamong them) were re-run, each under the lock. All exit 0 once thelint,formulaandclient-reactclosures were built. Before those builds, 4 had exited 3 (prerequisite). The derived link family ischeck:doc-anchors, exit 0; CI's lychee lane isCheck Documentation Links.--ran: 95 accounted, 94 run, 1 NOT MEASURED (check:dual-build-cjs-loads, which needs every package built).Generated by Claude Code