Skip to content

Commit 090f230

Browse files
Elon Muskclaude
andauthored
fix(datasource,runtime): declare the guarded optional-driver loads as optional peers (#12943) (#13401)
Five guarded `await import(...)` loads of workspace driver packages sat in published `src/**` with no manifest declaration an installing consumer could see. `@objectstack/service-datasource` reaches driver-turso, driver-sqlite-wasm (two sites) and driver-mongodb; `@objectstack/runtime` reaches driver-turso. Two of them were devDependencies, which tells a consumer nothing; driver-turso was in no section of either manifest. Each is now an optional `peerDependencies` entry plus `peerDependenciesMeta: { optional: true }` — the form `@objectstack/cli` already uses for driver-turso. Nothing is installed and no code path changes: an optional peer declares a relationship that already existed at runtime. The `rest` to `objectql` occurrence of the same shape is deliberately left alone; rest's non-coupling to the data engine is a stated architectural position, not a hygiene gap. pnpm links an optional workspace peer, so three test pins that had reached their missing-package arm with no stub stop doing so. Each had said in advance what to do about it. All three now stage the absence with `vi.doMock` and keep every assertion, including the typed-identity ones behind serve.ts's `e instanceof MissingDriverPackageError` boot-fatality branch. The five `optional-runtime-probe` ledger rows the declarations make stale are deleted, as that gate demands and only as far as it demands. Claude-Session: https://claude.ai/code/session_012WkdHQwHr2KQmaX7P1BHzi Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2be4a67 commit 090f230

11 files changed

Lines changed: 237 additions & 117 deletions
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
"@objectstack/service-datasource": patch
3+
"@objectstack/runtime": patch
4+
---
5+
6+
fix(datasource,runtime): declare the guarded optional-driver loads as optional peers, so a consumer is told at install time (#12943)
7+
8+
Five guarded `await import(...)` loads of workspace driver packages sat in
9+
published `src/**` with no manifest declaration a consumer could see:
10+
`@objectstack/service-datasource` reaches `driver-turso`, `driver-sqlite-wasm`
11+
(twice) and `driver-mongodb`, and `@objectstack/runtime` reaches `driver-turso`.
12+
`driver-mongodb` and `driver-sqlite-wasm` were `devDependencies` of
13+
`service-datasource`, which tells an installing consumer nothing at all;
14+
`driver-turso` was in no section of either manifest.
15+
16+
Each is now an optional `peerDependencies` entry with
17+
`peerDependenciesMeta: { optional: true }` — the form `@objectstack/cli` already
18+
uses for `driver-turso`. **Nothing is installed and no code path changes**: an
19+
optional peer declares a relationship that already existed at runtime, so
20+
`npm ls`, a lockfile, an audit tool and a reader of the manifest can all see the
21+
driver a datasource may ask for, instead of learning about it only by hitting
22+
the failure arm. The runtime errors were already good — each carries its install
23+
command as data — but they arrive at the moment of failure rather than at
24+
install time.
25+
26+
The `rest` to `objectql` occurrence of the same shape is deliberately left
27+
alone: `@objectstack/rest`'s non-coupling to the data engine is a stated
28+
architectural position, not a hygiene gap.
29+
30+
Three test pins had reached their missing-package arm with no stub, because the
31+
undeclared package genuinely did not resolve from the importing package. pnpm
32+
links an optional workspace peer, so that is no longer true, and each pin's own
33+
comment had said in advance what to do about it. All three now stage the absence
34+
(`vi.doMock` with the resolver's own `ERR_MODULE_NOT_FOUND`) and keep every
35+
assertion, including the typed-identity ones that make `serve.ts`'s
36+
`e instanceof MissingDriverPackageError` boot-fatality branch meaningful.

packages/cli/src/utils/storage-driver.test.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -563,17 +563,22 @@ describe('#6268 — one loader, one class identity across cli and runtime', () =
563563

564564
// The one thing the convergence deliberately did NOT move: the dynamic
565565
// import's specifier, whose RESOLUTION ROOT is the module that evaluates it.
566-
// `@objectstack/driver-turso` is an optional PEER of `@objectstack/cli` and is
567-
// not declared by `@objectstack/runtime` at all, so under pnpm's strict layout
568-
// it is linked into the CLI's node_modules and not the runtime's. Had the CLI
569-
// taken the runtime's default thunk, an operator who ran the exact install
570-
// command this error prints would still be told the package was missing.
566+
// `@objectstack/driver-turso` is an optional PEER of `@objectstack/cli` and,
567+
// since #12943, of `@objectstack/runtime` too — an optional peer names the
568+
// relationship and installs nothing, so the package still sits in whichever
569+
// tree the operator installed it into. Had the CLI taken the runtime's default
570+
// thunk, an operator who ran the exact install command this error prints would
571+
// still be told the package was missing.
571572
//
572-
// This case pins the CLI half — the default thunk finds the package that is
573-
// installed next to the CLI. The runtime half is pinned from the other side by
573+
// This case pins the CLI half — the default thunk finds the package installed
574+
// next to the CLI, through the CLI's own `devDependencies` entry. ⚠️ The
575+
// runtime half USED to be pinned from the other side by
574576
// `standalone-stack.libsql.test.ts`, where a `libsql://` boot with no injected
575-
// thunk takes the missing-package arm precisely because the runtime does not
576-
// declare it. Together they assert that the two roots are still distinct.
577+
// thunk took the missing-package arm because nothing linked the package under
578+
// the runtime. #12943's optional peer makes pnpm link it there too, so that
579+
// case now STAGES the absence rather than relying on the layout to supply it.
580+
// The pair still asserts that the two roots are distinct; what neither can
581+
// assert any more is that one of them is empty.
577582
it('resolves the optional package from the CLI’s own node_modules by default', async () => {
578583
const factory = await loadTursoDriverFactory();
579584
expect(factory.supports('turso')).toBe(true);

packages/runtime/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@
4141
"zod": "^4.4.3",
4242
"@objectstack/metadata-core": "workspace:*"
4343
},
44+
"peerDependencies": {
45+
"@objectstack/driver-turso": "workspace:^"
46+
},
47+
"peerDependenciesMeta": {
48+
"@objectstack/driver-turso": {
49+
"optional": true
50+
}
51+
},
4452
"optionalDependencies": {
4553
"@objectstack/driver-mongodb": "workspace:*"
4654
},

packages/runtime/src/standalone-stack.libsql.test.ts

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@
1919
// 3. the whole boot — `createStandaloneStack({ databaseUrl: 'libsql://…' })`
2020
// no longer produces the "unsupported scheme" refusal.
2121
//
22-
// No test here touches a real Turso endpoint: the package is substituted through
23-
// `importDriverPackage`, which is what makes the "package missing" arm testable
24-
// even in a workspace where the package happens to be installed.
25-
26-
import { describe, it, expect, afterEach } from 'vitest';
22+
// No test here touches a real Turso endpoint. Every loader-level case
23+
// substitutes the package through `importDriverPackage`; the whole-boot case in
24+
// ③ has no such seam and stages absence with `vi.doMock` instead (#12943).
25+
// Both make the "package missing" arm testable in a workspace where the package
26+
// IS installed — which, since `@objectstack/driver-turso` became a declared
27+
// optional peer of this package, is now every workspace.
28+
29+
import { describe, it, expect, afterEach, vi } from 'vitest';
2730
import {
2831
resolveStandaloneDatabase,
2932
resolveDatabaseAuthToken,
@@ -287,18 +290,49 @@ describe('loadTursoDriverFactory — the OPTIONAL driver package, both ways (#58
287290
});
288291
});
289292

290-
// ③ The whole boot, on the URL the issue is about. `@objectstack/driver-turso`
291-
// is deliberately NOT a dependency of `@objectstack/runtime` — that is what
292-
// "optional" means here — so in this workspace the boot takes the missing-package
293-
// arm. What matters either way is the FIRST assertion: the refusal is no longer
294-
// "unsupported scheme". (Should the package ever become a dependency of this one,
295-
// this case turns red and names exactly why in this comment.)
293+
// ③ The whole boot, on the URL the issue is about.
294+
//
295+
// ⭐ This case's old comment predicted its own future and was right: "Should the
296+
// package ever become a dependency of this one, this case turns red and names
297+
// exactly why in this comment." #12943 declared `@objectstack/driver-turso` an
298+
// OPTIONAL PEER of `@objectstack/runtime` — install-time honesty for a
299+
// relationship the source already had, installing nothing for a consumer — and
300+
// pnpm LINKS an optional workspace peer. Measured on that change: the boot
301+
// stopped taking the missing-package arm and SUCCEEDED, building a real driver
302+
// against `libsql://my-db.turso.io`. Red, and in the worse of the two
303+
// directions: a green-looking boot pointed at a remote endpoint.
304+
//
305+
// So absence is STAGED now. `createStandaloneStack` has no `importDriverPackage`
306+
// seam of its own — it calls `loadTursoDriverFactory()` bare, which is exactly
307+
// the standalone default this case exists to cover — so the specifier is mocked
308+
// with a factory that throws the resolver's own error. ⛔ No `vi.resetModules()`:
309+
// the assertion below is an `instanceof` against the binding this file imported,
310+
// and a reset would hand the arm a different class object and make that false
311+
// for a correct error.
312+
//
313+
// What matters either way is still the FIRST assertion: the refusal is no longer
314+
// "unsupported scheme".
296315
describe('createStandaloneStack — a libsql:// boot is dispatched, not refused as unknown (#5820)', () => {
297316
it('fails with the install command instead of "Unsupported database URL scheme"', async () => {
298317
clearUrlEnv();
318+
vi.doMock('@objectstack/driver-turso', () => {
319+
throw Object.assign(
320+
new Error("Cannot find module '@objectstack/driver-turso' imported from /app/node_modules/x.mjs"),
321+
{ code: 'ERR_MODULE_NOT_FOUND' },
322+
);
323+
});
299324
const err = await createStandaloneStack({ databaseUrl: 'libsql://my-db.turso.io' })
300-
.then(() => null, (e: unknown) => e);
301-
325+
.then(() => null, (e: unknown) => e)
326+
.finally(() => { vi.doUnmock('@objectstack/driver-turso'); });
327+
328+
if (err === null) {
329+
throw new Error(
330+
'staging @objectstack/driver-turso as absent no longer makes the standalone libsql boot '
331+
+ 'refuse, so this case has stopped exercising the missing-package arm — it booted a real '
332+
+ 'driver against a remote endpoint instead. ⛔ Do not delete it and do not weaken it: '
333+
+ 'find out why the stub stops short of the arm.',
334+
);
335+
}
302336
expect(err).not.toBeNull();
303337
expect(String((err as Error).message)).not.toMatch(/Unsupported database URL scheme/);
304338
expect(err).toBeInstanceOf(MissingDriverPackageError);

packages/runtime/src/turso-driver-factory.convergence.test.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
// No test here touches a real libSQL endpoint: the optional package is
3636
// substituted through `importDriverPackage`.
3737

38-
import { describe, it, expect } from 'vitest';
38+
import { describe, it, expect, vi } from 'vitest';
3939
import {
4040
buildTursoDriverConfig,
4141
createDefaultDatasourceDriverFactory,
@@ -167,22 +167,46 @@ describe('#7314 point 2 — one MissingDriverPackageError class across the seam'
167167
// Until #7314 that arm raised a plain `Error` and this assertion could not
168168
// have been written.
169169
it('the open-core arm raises an error the runtime binding matches', async () => {
170-
// `@objectstack/driver-turso` is deliberately not a dependency of
171-
// `@objectstack/service-datasource` — that is what "optional" means — so its
172-
// missing-package arm is reachable here without a stub.
170+
// ⭐ STAGED absence since #12943. `@objectstack/driver-turso` is now an
171+
// OPTIONAL PEER of `@objectstack/service-datasource` and of this package —
172+
// the honest install-time declaration of a relationship the source already
173+
// had. It installs nothing for a consumer, but pnpm LINKS an optional
174+
// workspace peer, so the package resolves here and the bare form stopped
175+
// entering the missing-package arm. That is precisely the transition this
176+
// pin's old notice named, and this is it carried out.
177+
//
178+
// ⛔ Mocked WITHOUT `vi.resetModules()`, and that is load-bearing rather
179+
// than a shortcut: this pin is about CLASS IDENTITY ACROSS THE SEAM, and a
180+
// reset re-evaluates `missing-driver-package-error.js` inside
181+
// `@objectstack/service-datasource`. The arm would then raise a fresh class
182+
// object, `instanceof` against the runtime binding would be FALSE for a
183+
// perfectly correct error, and the reset would have destroyed the very fact
184+
// under test. No reset is needed here: nothing in this file imports the
185+
// driver package before this point, so the factory's lazy
186+
// `await import(...)` is the first one and the mock is what it finds.
187+
vi.doMock('@objectstack/driver-turso', () => {
188+
throw Object.assign(
189+
new Error("Cannot find package '@objectstack/driver-turso' imported from /app/node_modules/x.mjs"),
190+
{ code: 'ERR_MODULE_NOT_FOUND' },
191+
);
192+
});
173193
let err: unknown = null;
174194
try {
175195
await createDefaultDatasourceDriverFactory()
176196
.create({ name: 'warehouse', driver: 'turso', config: { url: 'libsql://my-db.turso.io' } });
177197
} catch (e) {
178198
err = e;
199+
} finally {
200+
vi.doUnmock('@objectstack/driver-turso');
179201
}
180202

181203
if (err === null) {
182204
throw new Error(
183-
'@objectstack/driver-turso resolved from @objectstack/service-datasource, so this case no '
184-
+ 'longer exercises the missing-package arm. If the package was made a dependency, this '
185-
+ 'assertion is the notice that the pin needs a stubbed import instead.',
205+
'staging @objectstack/driver-turso as absent no longer makes the open-core turso arm '
206+
+ 'raise, so this case has stopped exercising the missing-package arm. ⛔ Do not delete '
207+
+ 'it and do not weaken it: find out why the stub stops short of the arm. An arm no test '
208+
+ 'can enter is a decoration, and this one is the whole reason serve.ts can decide boot '
209+
+ 'fatality on a failure the OPEN-CORE factory raised.',
186210
);
187211
}
188212
expect(err).toBeInstanceOf(MissingDriverPackageError);

packages/runtime/src/turso-driver-factory.ts

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,21 @@
4141
* duplicate:
4242
*
4343
* - **{@link LoadTursoDriverFactoryOptions.importDriverPackage} — the module
44-
* resolution root.** `@objectstack/driver-turso` is an optional PEER of
45-
* `@objectstack/cli` and is not declared by `@objectstack/runtime` at all.
46-
* A bare `import('@objectstack/driver-turso')` resolves from the node_modules
47-
* tree of the module that *evaluates* it, so moving the CLI's import into
48-
* this file would look for the package under `@objectstack/runtime` — which
49-
* under pnpm's strict layout does not link it. An operator who ran the
50-
* install command the error tells them to run would still be told the package
51-
* is missing. The CLI therefore keeps passing its own thunk; the default
52-
* below is this package's own root, for the standalone stack.
44+
* resolution root.** `@objectstack/driver-turso` is an OPTIONAL PEER of
45+
* `@objectstack/cli` and, since #12943, of `@objectstack/runtime` too. An
46+
* optional peer NAMES the relationship and installs nothing, so the package
47+
* still sits in whichever tree the operator installed it into — and a bare
48+
* `import('@objectstack/driver-turso')` resolves from the node_modules tree
49+
* of the module that *evaluates* it. That is what keeps this a host-supplied
50+
* input rather than a duplicate: an operator who installed the package next
51+
* to the CLI put it in the CLI's tree, so moving the CLI's import into this
52+
* file would look for it under `@objectstack/runtime` and tell them it is
53+
* missing right after they ran the exact command the error printed. The CLI
54+
* therefore keeps passing its own thunk; the default below is this package's
55+
* own root, for the standalone stack.
56+
* ⚠️ Inside THIS workspace pnpm links an optional peer, so the default thunk
57+
* resolves here. Every test covering the missing-package arm therefore stages
58+
* the absence rather than relying on the layout to supply it (#12943).
5359
* - **{@link LoadTursoDriverFactoryOptions.missingUrlError} — the error TYPE
5460
* for a config with no url.** The CLI raises its own `UnsupportedDriverError`
5561
* (a CLI-only semantic: `serve.ts` re-throws it as a fatal boot error), which
@@ -149,15 +155,17 @@ export interface LoadTursoDriverFactoryOptions {
149155
*
150156
* NOT merely a test seam: the specifier resolves from the node_modules tree of
151157
* whichever module evaluates the `import()`, and the package is an optional
152-
* peer of `@objectstack/cli` while `@objectstack/runtime` does not declare it.
153-
* The CLI passes its own thunk so its operators keep resolving the package
154-
* they installed next to the CLI (see the module docstring). The default is
155-
* this package's own root, which is what the standalone stack wants.
158+
* peer of BOTH `@objectstack/cli` and `@objectstack/runtime` (#12943) — a
159+
* declaration that installs nothing, so which tree actually holds the package
160+
* is still decided by where the operator installed it. The CLI passes its own
161+
* thunk so its operators keep resolving the package they installed next to the
162+
* CLI (see the module docstring). The default is this package's own root,
163+
* which is what the standalone stack wants.
156164
*
157165
* Tests pass a stub module (dispatch WITH the package) or a rejecting thunk
158166
* (dispatch WITHOUT it) — neither needs a real Turso endpoint, and the
159-
* missing-package path must stay testable in a workspace where the package
160-
* happens to be installed.
167+
* missing-package path must stay testable in a workspace where the package IS
168+
* installed, which since the optional peer landed is every workspace.
161169
*/
162170
importDriverPackage?: () => Promise<unknown>;
163171
/**
@@ -193,10 +201,12 @@ export interface LoadTursoDriverFactoryOptions {
193201
export async function loadTursoDriverFactory(
194202
opts: LoadTursoDriverFactoryOptions = {},
195203
): Promise<IDatasourceDriverFactory> {
196-
// `as any` on the specifier: the package is deliberately NOT a dependency of
197-
// `@objectstack/runtime` (that is what "optional" means here), so the literal
198-
// must not be type-resolved. Same shape the shared factory uses for the other
199-
// optional drivers (`default-datasource-driver-factory.ts`).
204+
// `as any` on the specifier: the package is an OPTIONAL PEER of
205+
// `@objectstack/runtime`, never a dependency (that is what "optional" means
206+
// here — #12943 declared the relationship, and an optional peer installs
207+
// nothing), so the literal must not be type-resolved: a consumer who did not
208+
// install it must still compile. Same shape the shared factory uses for the
209+
// other optional drivers (`default-datasource-driver-factory.ts`).
200210
const load = opts.importDriverPackage ?? (() => import('@objectstack/driver-turso' as any));
201211
const missingUrlError =
202212
opts.missingUrlError ?? ((message: string) => new Error(`[StandaloneStack] ${message}`));

packages/services/service-datasource/package.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,22 @@
3333
"@objectstack/types": "workspace:*",
3434
"pg-connection-string": "^2.14.0"
3535
},
36+
"peerDependencies": {
37+
"@objectstack/driver-mongodb": "workspace:^",
38+
"@objectstack/driver-sqlite-wasm": "workspace:^",
39+
"@objectstack/driver-turso": "workspace:^"
40+
},
41+
"peerDependenciesMeta": {
42+
"@objectstack/driver-mongodb": {
43+
"optional": true
44+
},
45+
"@objectstack/driver-sqlite-wasm": {
46+
"optional": true
47+
},
48+
"@objectstack/driver-turso": {
49+
"optional": true
50+
}
51+
},
3652
"devDependencies": {
3753
"@objectstack/driver-mongodb": "workspace:*",
3854
"@objectstack/driver-sqlite-wasm": "workspace:*",

0 commit comments

Comments
 (0)