@@ -10,9 +10,22 @@ import {
1010 resolveDriverType ,
1111 resolveStorageDefinition ,
1212 loadTursoDriverFactory ,
13+ isTursoDriverId ,
1314 MissingDriverPackageError ,
15+ TURSO_DRIVER_INSTALL_COMMAND ,
1416 UnsupportedDriverError ,
1517} from './storage-driver.js' ;
18+ // #6268: the OTHER host's bindings, imported under their own names so the
19+ // identity assertions below compare two real module paths rather than one alias
20+ // of the same import. `@objectstack/runtime` resolves to the BUILT package here,
21+ // exactly as it does for `storage-driver.ts` itself — which is what makes the
22+ // identity pinned below the identity that ships.
23+ import {
24+ loadTursoDriverFactory as loadRuntimeTursoDriverFactory ,
25+ isTursoDriverId as runtimeIsTursoDriverId ,
26+ MissingDriverPackageError as RuntimeMissingDriverPackageError ,
27+ TURSO_DRIVER_INSTALL_COMMAND as RUNTIME_TURSO_DRIVER_INSTALL_COMMAND ,
28+ } from '@objectstack/runtime' ;
1629
1730describe ( 'inferDriverTypeFromUrl' , ( ) => {
1831 it ( 'maps each recognized URL scheme to its canonical driver kind' , ( ) => {
@@ -397,3 +410,97 @@ describe('loadTursoDriverFactory: the optional driver package (#5602)', () => {
397410 expect ( pinned . authToken ) . toBe ( 'jwt-token' ) ;
398411 } ) ;
399412} ) ;
413+
414+ // #6268 — the loader has ONE owner (`@objectstack/runtime`), and this file's
415+ // exports are that owner's declarations rather than hand-aligned copies.
416+ //
417+ // The property under test is CLASS IDENTITY, not wording. `serve.ts:1136` decides
418+ // whether a boot failure is fatal with
419+ //
420+ // if (e instanceof MissingDriverPackageError) throw e;
421+ //
422+ // so a convergence that left two same-named classes — one per package — would
423+ // make that predicate stop matching and degrade a fatal branch to a non-fatal one
424+ // with no diagnostic anywhere. Nothing in the message would change, which is why
425+ // the first case below demonstrates, in-suite, that a message assertion is blind
426+ // to exactly this defect.
427+ describe ( '#6268 — one loader, one class identity across cli and runtime' , ( ) => {
428+ it ( 'the CLI export IS the runtime class object, not a same-named twin' , ( ) => {
429+ expect ( MissingDriverPackageError ) . toBe ( RuntimeMissingDriverPackageError ) ;
430+ expect ( isTursoDriverId ) . toBe ( runtimeIsTursoDriverId ) ;
431+ expect ( TURSO_DRIVER_INSTALL_COMMAND ) . toBe ( RUNTIME_TURSO_DRIVER_INSTALL_COMMAND ) ;
432+ } ) ;
433+
434+ // THE pin. An error raised by the RUNTIME loader must satisfy the instanceof
435+ // that `serve.ts` performs against the CLI-side binding.
436+ it ( "an error raised by the runtime loader satisfies serve.ts's CLI-side instanceof" , async ( ) => {
437+ const err = await loadRuntimeTursoDriverFactory ( {
438+ importDriverPackage : async ( ) => { throw new Error ( "Cannot find module '@objectstack/driver-turso'" ) ; } ,
439+ } ) . then ( ( ) => null , ( e : unknown ) => e ) ;
440+
441+ // The predicate serve.ts runs, spelled the way serve.ts spells it.
442+ expect ( err instanceof MissingDriverPackageError ) . toBe ( true ) ;
443+
444+ // …and the demonstration that a message assertion could NOT have caught a
445+ // broken identity: a twin declared right here carries the same message and
446+ // the same fields, and passes every assertion except the one above.
447+ class MissingDriverPackageErrorTwin extends Error {
448+ constructor ( readonly installCommand : string , message : string ) {
449+ super ( message ) ;
450+ this . name = 'MissingDriverPackageError' ;
451+ }
452+ }
453+ const twin = new MissingDriverPackageErrorTwin (
454+ ( err as MissingDriverPackageError ) . installCommand ,
455+ ( err as Error ) . message ,
456+ ) ;
457+ expect ( twin . message ) . toBe ( ( err as Error ) . message ) ;
458+ expect ( twin . name ) . toBe ( ( err as Error ) . name ) ;
459+ expect ( twin . installCommand ) . toBe ( TURSO_DRIVER_INSTALL_COMMAND ) ;
460+ expect ( twin instanceof MissingDriverPackageError ) . toBe ( false ) ;
461+ } ) ;
462+
463+ it ( 'and the reverse: the CLI loader raises an error the runtime binding matches' , async ( ) => {
464+ const err = await loadTursoDriverFactory ( {
465+ importDriverPackage : async ( ) => { throw new Error ( 'nope' ) ; } ,
466+ } ) . then ( ( ) => null , ( e : unknown ) => e ) ;
467+ expect ( err instanceof RuntimeMissingDriverPackageError ) . toBe ( true ) ;
468+ expect ( ( err as MissingDriverPackageError ) . installCommand ) . toBe ( 'npm install @objectstack/driver-turso' ) ;
469+ } ) ;
470+
471+ // The one thing the convergence deliberately did NOT move: the dynamic
472+ // import's specifier, whose RESOLUTION ROOT is the module that evaluates it.
473+ // `@objectstack/driver-turso` is an optional PEER of `@objectstack/cli` and is
474+ // not declared by `@objectstack/runtime` at all, so under pnpm's strict layout
475+ // it is linked into the CLI's node_modules and not the runtime's. Had the CLI
476+ // taken the runtime's default thunk, an operator who ran the exact install
477+ // command this error prints would still be told the package was missing.
478+ //
479+ // This case pins the CLI half — the default thunk finds the package that is
480+ // installed next to the CLI. The runtime half is pinned from the other side by
481+ // `standalone-stack.libsql.test.ts`, where a `libsql://` boot with no injected
482+ // thunk takes the missing-package arm precisely because the runtime does not
483+ // declare it. Together they assert that the two roots are still distinct.
484+ it ( 'resolves the optional package from the CLI’s own node_modules by default' , async ( ) => {
485+ const factory = await loadTursoDriverFactory ( ) ;
486+ expect ( factory . supports ( 'turso' ) ) . toBe ( true ) ;
487+ expect ( factory . supports ( 'libsql' ) ) . toBe ( true ) ;
488+ expect ( factory . supports ( 'sqlite' ) ) . toBe ( false ) ;
489+ } ) ;
490+
491+ // The CLI-only semantics the ruling keeps on this side: a url-less turso config
492+ // is refused as `UnsupportedDriverError`, which serve.ts re-throws as fatal —
493+ // while the runtime's own default keeps raising its `[StandaloneStack]` error
494+ // for the same message. Only the TYPE is host-chosen; the wording is shared.
495+ it ( 'keeps UnsupportedDriverError as the CLI’s url-less refusal, with the shared wording' , async ( ) => {
496+ const factory = await loadTursoDriverFactory ( {
497+ importDriverPackage : async ( ) => ( { TursoDriver : class { constructor ( _c : unknown ) { } } } ) ,
498+ } ) ;
499+ let err : unknown ;
500+ try { factory . create ( { name : 'default' , driver : 'turso' , config : { } } ) ; } catch ( e ) { err = e ; }
501+ expect ( err ) . toBeInstanceOf ( UnsupportedDriverError ) ;
502+ expect ( ( err as Error ) . message ) . toMatch ( / n e e d s a l i b S Q L u r l / ) ;
503+ // Not the standalone stack's prefix — that host keeps its own error type.
504+ expect ( ( err as Error ) . message ) . not . toMatch ( / \[ S t a n d a l o n e S t a c k \] / ) ;
505+ } ) ;
506+ } ) ;
0 commit comments