Repository navigation
Commit b811c73
fix(service-datasource): a missing optional driver package (turso, mongodb, sqlite-wasm) is refused with the factory's install alone, not the unbuilt-workspace build advice (#24971)
Fixes #24937
Clause-②: no
## What was wrong
A `turso`, `mongodb` or `sqlite-wasm` datasource whose optional driver
package is not installed is refused at boot. The factory's own text
already says so and names the install (`npm install
@objectstack/driver-turso`, with the `pnpm add` and `yarn add` spellings
beside it). The refusal then ended on `UNBUILT_WORKSPACE_REMEDY` anyway:
"… That is a build precondition … Run `pnpm install && pnpm build`, then
start again." That is two fixes that contradict each other, and the
second does nothing for a project, which adds these packages itself.
`connectFailureRemedy()` chose the unbuilt remedy for every
module-not-found except the three database clients PR #24933 split off
(`pg`, `mysql2`, `better-sqlite3`). A bare `@objectstack/driver-*`
specifier still classified as unbuilt, and four factory classifier pins
asserted that.
## What changed
Seat ruling **B** on the card. The code change is in
`packages/services/service-datasource/src/connect-failure-remedy.ts`,
and it is a message branch only.
- New closed list `OPTIONAL_DRIVER_PACKAGES`, built from the factory's
own constants (`TURSO_DRIVER_PACKAGE`, `MONGODB_DRIVER_PACKAGE`,
`SQLITE_WASM_DRIVER_PACKAGE`). A pin holds it equal to the optional
peers `package.json` declares (`peerDependenciesMeta.*.optional`).
Measured set: exactly those three.
- `missingOptionalDriverPackage(cause)` reads the same first `Cannot
find module|package 'X'` specifier PR #24933 introduced. It answers X
only when X is one of those three, as a bare name.
- `connectFailureRemedy()` then ends on
`missingOptionalDriverPackageRemedy(X)`. That is ONE sentence deferring
to the install the factory text already names: "The fix is the install
of `X` named above, then start again: it is an OPTIONAL driver package
the project adds itself, so a build cannot produce it and no boot-time
override can stand in for it." It names no command, no build, no
environment variable and no configuration key.
- `isUnbuiltWorkspaceFailure()` answers false for that case.
- These keep their remedies:
- A PATH into one of those packages (`…/driver-turso/dist/index.mjs`)
keeps `UNBUILT_WORKSPACE_REMEDY`, and so does a subpath of one.
- `@objectstack/driver-sql` and any other `@objectstack/…` name keep it
too.
- The three clients keep PR #24933's remedy.
- Every other cause keeps `GENERIC_CONNECT_FAILURE_REMEDY`, byte for
byte.
- The module note gains a section for this case.
Unchanged, per the hard stops:
- The factory's own messages: `default-datasource-driver-factory.ts` is
not in the diff.
- When boot is refused, the error type, the retained state and the
`OS_ALLOW_DRIVER_CONNECT_FAILURE` path.
- `sqlite-driver-fallback.ts`, `packages/types/**`,
`packages/drivers/**` and `packages/spec/**` are not touched.
- No new env var, flag or gate.
## Premise re-check (the ruling's P), measured before any edit
P: in this monorepo, once `pnpm install` has run, a missing or unbuilt
optional driver surfaces as a PATH, not as a bare `Cannot find package`
name.
Setup: a fresh worktree at `8cfe1f94cb`, `pnpm install
--frozen-lockfile`, no build. All three packages are linked into
`service-datasource/node_modules/@objectstack/`.
- **Resolver leg** (no build at all): Node 22, from
`packages/services/service-datasource`. For each package, ESM and CJS
both answer a path:
- ESM: `ERR_MODULE_NOT_FOUND | Cannot find module
'…/service-datasource/node_modules/@objectstack/driver-turso/dist/index.mjs'
imported from …`
- CJS: `MODULE_NOT_FOUND | Cannot find module
'…/driver-turso/dist/index.js'`
- `driver-mongodb` and `driver-sqlite-wasm` answer the same shape.
- **Boot leg**: the service's closure built EXCEPT the three drivers,
then a datasource bound to `my_app_note` booted per driver through the
real factory and the real `DatasourceConnectionService` (built `dist/`).
Every arm's first unresolved specifier is the
`…/driver-X/dist/index.mjs` PATH.
P holds, so B ships. A contributor with an unbuilt checkout is still
told to build. That is unchanged by this PR, and the pins for it are now
path-shaped.
## Measured on the card's path, before and after
The published `@objectstack/service-datasource@18.0.0` was installed
alone in an empty directory, with no driver packages (`npm install
--ignore-scripts`). A declared datasource bound to one object was booted
per optional driver through the published factory and
`DatasourceConnectionService`, under both `dist/index.js` (ESM) and
`dist/index.cjs` (CJS).
- **Before (18.0.0):**
- the first unresolved specifier is the bare `@objectstack/driver-turso`
(`-mongodb`, `-sqlite-wasm`);
- the refusal names `npm install` once AND ends on "… Run `pnpm install
&& pnpm build`, then start again."
- **After:** the same directory with this branch's packed build
installed over the published one (`pnpm pack` of `service-datasource`,
marker grep: positive control 1 hit, the 18.0.0 copy 0 hits):
- all three arms, ESM and CJS, end on "The fix is the install of
`@objectstack/driver-X` named above, then start again: …";
- `npm install` appears exactly once in the whole refusal;
- `pnpm install` and `pnpm build` are absent.
- **NOT MEASURED:** the `os dev` scaffold path itself
(`create-objectstack@18.0.0`, a turso datasource). Reason: the CLI
reaches this refusal through the same factory and
`DatasourceConnectionService` measured above. The card measured the
scaffold's before-state, and a scaffold after-state needs this
unpublished build overridden into a full CLI install.
## Tests (head `87b717e5a0`)
- `pnpm --filter @objectstack/service-datasource typecheck`: `tsc
--noEmit`, exit 0. `--listFiles` includes both edited test files.
- `pnpm --filter @objectstack/service-datasource test`: `Test Files 49
passed (49)`, `Tests 899 passed (899)`.
- New pins (`datasource-connection-service.test.ts`):
- Each optional package × {ESM `Cannot find package 'X' imported from …`
with `ERR_MODULE_NOT_FOUND`, CJS `Cannot find module 'X'` plus `Require
stack` with `MODULE_NOT_FOUND`} goes through the factory's real message
builders (the turso one as `MissingDriverPackageError`). Each ends on
the deferring sentence. The whole refusal holds exactly one `npm
install`, no `pnpm build`, no `pnpm install`, no unbuilt remedy and no
generic advice.
- A path into each package keeps the unbuilt remedy, and so does a
subpath.
- The remedy is one sentence with no command, `OS_*` variable or
`--database`.
- The escape hatch still boots degraded.
- The list equals the manifest's optional peers.
- Classifier units: bare name only, module-not-found only,
`@objectstack/driver-sql` is not on the list, a code-only error names
nothing.
- The existing #24933 client pins and the `ECONNREFUSED` / `28P01`
generic pins are unchanged and green.
- Revised factory classifier pins
(`default-datasource-driver-factory.test.ts`). The card says three;
there are FOUR sites asserting `isUnbuiltWorkspaceFailure(...)` true on
a bare-package cause: the turso, sqlite-wasm and mongodb "keeps the
import error verbatim" cases, and the three-arm parity case.
- Each now asserts the unbuilt reading on a PATH-shaped cause
(`unbuiltCause(pkg)`, the measured shape).
- Each asserts the bare name is that package's missing install.
- The parity block gains one case for the bare reading.
- No real-path pin was added, because none is cheap here. vitest wraps
every mock-factory throw in its own "[vitest] There was an error when
mocking a module" error (`@vitest/mocker` `createHelpfulError`, measured
in the installed 4.1.11). A staged absence therefore cannot carry Node's
text to the classifier. The factory has no import seam, and its file is
off this PR's surface. The before/after run above is the real-path
evidence instead.
## Ablation (one leg per changed behaviour; fix committed first; `node
scripts/ablation-replace.mjs`, WRAP mode)
Each leg ran the three suites `datasource-connection-service`,
`default-datasource-driver-factory` and
`missing-driver-client-boot-refusal`. The subjects resolve through
relative `src` imports, so no `dist/` leg applies.
1. **Delete the `connectFailureRemedy` branch** (`if (driverPackage !==
undefined) return missingOptionalDriverPackageRemedy(driverPackage);`).
- On disk: anchor 1 → 0, blob `e6403dd54e18` → `bf36838df528`.
- Predicted red. Result: `Tests 6 failed | 150 passed`, exactly the six
ESM/CJS deferring-remedy pins.
- Restored by `git checkout HEAD --`: blob `e6403dd54e18` == HEAD, `git
diff HEAD` empty, `git status` clean.
2. **Drop the `isUnbuiltWorkspaceFailure` exclusion** (`&&
missingOptionalDriverPackage(cause) === undefined`).
- The first attempt was a no-op. The tool refused it on its own
replacement-count check (newline replacement), restored, and never ran
the suites.
- Re-run with a distinctive replacement. On disk: anchor 1 → 0,
replacement 0 → 1, blob `e6403dd54e18` → `ebf06e9d96f4`.
- Predicted: the classifier pins go red, and the service-level remedy
pins stay green because the branch runs first. Result: `Tests 5 failed |
151 passed`, exactly the five classifier pins. The four factory sites
plus the unit failed; the remedy pins stayed green.
- Restored: blob == HEAD, `git diff HEAD` empty.
## Gates (local subset, head `87b717e5a0`)
- Derivation: `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`, 66 commands, 4 changed paths vs merge base
`8cfe1f94c`.
- Each command was run once, exit captured before any pipe.
- Result: 65 exit 0, and 1 NOT MEASURED, `pnpm
check:dual-build-cjs-loads`. It exited 3 with `PREREQUISITE NOT MET`: it
reads every package's `dist/`, and this worktree builds only the
`service-datasource` closure. CI's `Lint & Repo Gates` runs it.
- `--ran` reconciliation: `✓ dispatch-gates --ran: 66 derived famil(ies)
accounted for — 65 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit
3)`, exit 0.
- The repo-wide scans and the CI-only families (`Test Core`, `Build
Core`, the type-check lanes) are CI's.
## Acceptance notes
- **The #24909 changeset goes partly false when this lands.** Its
"Unchanged" bullet in `.changeset/24909-missing-driver-client-remedy.md`
is still unreleased. It reads "an `@objectstack/…` driver package that
is not installed, still ends on `pnpm install && pnpm build`". That
stops being true for the three optional driver packages.
- The file is outside this PR's declared surface, so it is not edited
here.
- Suggested one-word narrowing before the next release consumes it: "or
a required `@objectstack/…` driver package that is not installed".
- This PR's own changeset states the new behaviour.
- **One factory comment drifts slightly.** In
`default-datasource-driver-factory.ts`, the `missingTursoDriverMessage`
doc says the classifier "can only recognise an unbuilt/uninstalled
workspace" from the interpolated text. With this change an uninstalled
optional package is read as an install, not as unbuilt. The file is
off-surface by the hard stop, and nothing else in it is made false.
Carrier: none.
- **The workspace mirror is unchanged.** In this monorepo with an
UNBUILT optional driver, the factory's text says "is not installed …
`npm install @objectstack/driver-X`", and the refusal then correctly
ends on the build advice. The boot-leg measurement above shows `npm
install` and `pnpm build` both present. That is the factory message's
wording, which is out of scope here by the hard stop, and ruling B
accepted the workspace reading. It was measured at the service seam
only, with no public door. Carrier: none.
> **Seat's append** (`domain:services` seat 1,
`session_011TdZ8GjixNZFLz1xJD9zzK`): the dev's round-2 markdown from
os-dev report `6300109209` on #24937, verbatim. The dev writes the body
once and never PATCHes it. The seat's written confirmation of the
DELIBERATE CORRECTION is in its ACCEPT on this PR.
## Round 2: the client-remedy changeset's "Unchanged" bullet (seat
ruling A on open question 1)
`.changeset/24909-missing-driver-client-remedy.md` is still unreleased.
Its "Unchanged" bullet said "an `@objectstack/…` driver package that is
not installed, still ends on `pnpm install && pnpm build`", which this
PR makes false for the three optional driver packages. That one bullet
now reads:
> A driver package whose build output is missing, or a required
`@objectstack/…` driver package that is not installed (any but the
optional `@objectstack/driver-turso`, `@objectstack/driver-mongodb` and
`@objectstack/driver-sqlite-wasm`), still ends on `pnpm install && pnpm
build`.
- It is a one-line edit (`1 insertion(+), 1 deletion(-)`). No other line
of that file changed, and no other file changed in this round.
- It matches the code. A path into any driver package's `dist/` is still
the unbuilt remedy, and so is a bare `@objectstack/…` name off
`OPTIONAL_DRIVER_PACKAGES` (e.g. `@objectstack/driver-sql`). A bare name
on that list ends on `missingOptionalDriverPackageRemedy`.
- `origin/main` `693352e4c2` was merged first. It brings PR #24955 on
`sqlite-driver-fallback.ts`, disjoint from this diff.
- Head `e759c6c8e9`:
- `pnpm --filter @objectstack/service-datasource typecheck`: exit 0.
- `pnpm --filter @objectstack/service-datasource test`: `Test Files 50
passed (50)`, `Tests 908 passed (908)`. That includes #24955's new file.
- Gates: re-derived with `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack`. That gives 5 paths vs merge base
`693352e4c` and 66 commands, the same set as round 1 (the new path adds
no family). All 66 were re-run at this head under the lock, `node
scripts/check-empty-changeset.mjs --base origin/main` among them.
Results: 64 exit 0; 1 NOT MEASURED, `pnpm check:dual-build-cjs-loads`
(exit 3, `PREREQUISITE NOT MET`, it reads every package's `dist/`); and
1 red BY DESIGN, `node scripts/check-empty-changeset.mjs --base
origin/main` (exit 1). `--ran` reconciliation: `✓ dispatch-gates --ran:
66 derived famil(ies) accounted for — 65 run, 1 NOT-MEASURED (1 DERIVED
from a recorded exit 3)`, exit 0.
### `Check Changeset` stays red, and that is the gate's DELIBERATE
CORRECTION class
`check-empty-changeset` refuses any PR that changes a changeset it did
not add. Here that is
`.changeset/24909-missing-driver-client-remedy.md`, `M` against merge
base `693352e4c`, beside this PR's own `A`
`.changeset/24937-optional-driver-package-remedy.md`. The gate names two
classes with opposite remedies. This is the second one, not a filename
collision: this PR's own code change made that PENDING release note
false, and this round rewrites only the sentence it falsified. Per the
gate's own remedy text, which `pr-automation.yml`'s route 0 repeats for
the same class:
- The note is NOT restored from the base. That would republish the false
sentence.
- `skip-changeset` is NOT applied. Ruled on #18375 (ruling D): never on
a PR that edits an existing changeset.
- The check is left red. `Check Changeset` is not one of the five
required contexts, so it blocks no merge.
The note is `.changeset/24909-missing-driver-client-remedy.md`. What
changed under it: this PR routes a bare `@objectstack/driver-turso` /
`-mongodb` / `-sqlite-wasm` module-not-found to the factory's install
instead of the build advice. Only its "Unchanged" bullet's second clause
is narrowed to match. The correction was ruled by the `domain:services`
seat 1 PM (ruling A on open question 1 of the os-dev report on #24937).
The seat records that confirmation on this PR in writing.
---
_Generated by [Claude
Code](https://claude.ai/code/session_011TdZ8GjixNZFLz1xJD9zzK)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 3a7a60a commit b811c73
5 files changed
Lines changed: 350 additions & 22 deletions
File tree
- .changeset
- packages/services/service-datasource/src
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
| 11 | + | |
12 | 12 | | |
13 | 13 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
| 16 | + | |
15 | 17 | | |
16 | 18 | | |
17 | 19 | | |
18 | 20 | | |
| 21 | + | |
| 22 | + | |
19 | 23 | | |
20 | | - | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
21 | 34 | | |
22 | 35 | | |
23 | 36 | | |
| |||
673 | 686 | | |
674 | 687 | | |
675 | 688 | | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
| 706 | + | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
| 712 | + | |
| 713 | + | |
| 714 | + | |
| 715 | + | |
| 716 | + | |
| 717 | + | |
| 718 | + | |
| 719 | + | |
| 720 | + | |
| 721 | + | |
| 722 | + | |
| 723 | + | |
| 724 | + | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
| 728 | + | |
| 729 | + | |
| 730 | + | |
| 731 | + | |
| 732 | + | |
| 733 | + | |
| 734 | + | |
| 735 | + | |
| 736 | + | |
| 737 | + | |
| 738 | + | |
| 739 | + | |
| 740 | + | |
| 741 | + | |
| 742 | + | |
| 743 | + | |
| 744 | + | |
| 745 | + | |
| 746 | + | |
| 747 | + | |
| 748 | + | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
| 752 | + | |
| 753 | + | |
| 754 | + | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
| 759 | + | |
| 760 | + | |
| 761 | + | |
| 762 | + | |
| 763 | + | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
| 770 | + | |
| 771 | + | |
| 772 | + | |
| 773 | + | |
| 774 | + | |
| 775 | + | |
| 776 | + | |
| 777 | + | |
| 778 | + | |
| 779 | + | |
| 780 | + | |
| 781 | + | |
| 782 | + | |
| 783 | + | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
676 | 795 | | |
677 | 796 | | |
678 | 797 | | |
| |||
791 | 910 | | |
792 | 911 | | |
793 | 912 | | |
| 913 | + | |
| 914 | + | |
| 915 | + | |
| 916 | + | |
| 917 | + | |
| 918 | + | |
| 919 | + | |
| 920 | + | |
| 921 | + | |
| 922 | + | |
| 923 | + | |
| 924 | + | |
| 925 | + | |
| 926 | + | |
| 927 | + | |
| 928 | + | |
| 929 | + | |
| 930 | + | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
| 940 | + | |
| 941 | + | |
| 942 | + | |
| 943 | + | |
| 944 | + | |
| 945 | + | |
| 946 | + | |
| 947 | + | |
| 948 | + | |
| 949 | + | |
| 950 | + | |
| 951 | + | |
| 952 | + | |
794 | 953 | | |
795 | 954 | | |
796 | 955 | | |
| |||
0 commit comments