@@ -104,6 +104,7 @@ import {
104104 devAutoMigrateConfig ,
105105 loadTursoDriverFactory as loadRuntimeTursoDriverFactory ,
106106 namesRetiredMemoryEngine ,
107+ normalizeDatabaseUrl ,
107108 retiredMemoryEngineMessage ,
108109 type LoadTursoDriverFactoryOptions ,
109110} from '@objectstack/runtime' ;
@@ -291,9 +292,11 @@ function unsupportedUrlSchemeMessage(scheme: string): string {
291292 *
292293 * The arms are the runtime host's (`detectDriverFromUrl` in
293294 * `packages/runtime/src/standalone-stack.ts`), so the two hosts give one answer
294- * per URL: `pg://` is postgres, and a value with no `<scheme>://` is a SQLite
295- * file path, whatever its extension. The runtime refuses every other
296- * `<scheme>://`, so refusing it here takes away nothing that host accepts.
295+ * per URL: `pg://` is postgres, and a value with no scheme is a SQLite file
296+ * path, whatever its extension. The runtime refuses every other
297+ * `<scheme>://`, so refusing it here takes away nothing that host means to
298+ * accept. The one difference is an OPAQUE scheme (`jdbc:…`, `redis:h`), which
299+ * this host refuses and the runtime host still opens as a file (#23765).
297300 */
298301export function inferDriverTypeFromUrl ( url : string | undefined ) : string {
299302 if ( ! url ) return '' ;
@@ -330,20 +333,29 @@ export function inferDriverTypeFromUrl(url: string | undefined): string {
330333 ) ;
331334 }
332335 if ( / ^ f i l e : / i. test ( u ) || / ^ s q l i t e : / i. test ( u ) ) return 'sqlite' ;
333- // No `<scheme>://` at all: a SQLite file path — `:memory:`, `./app.db`,
334- // `data/app`. The runtime host's arm, character for character. It is what
335- // keeps a `.db` name from rescuing an unrecognised scheme: `redis://h/x.db`
336- // is refused below, as the runtime refuses it, rather than handed to SQLite
337- // as a file path.
338- if ( ! / ^ [ a - z ] [ a - z 0 - 9 + . - ] * : \/ \/ / i. test ( u ) ) return 'sqlite' ;
339- const scheme = `${ / ^ [ a - z ] [ a - z 0 - 9 + . - ] * / i. exec ( u ) ! [ 0 ] } ://` ;
340- throw new UnsupportedDriverError (
341- scheme ,
342- unsupportedUrlSchemeMessage ( scheme ) ,
343- // Not a kind this resolver can produce — `database-driver-allowlist.pin`
344- // reads `recognized` to tell the two apart.
345- { recognized : false } ,
346- ) ;
336+ // What is left with a scheme is refused, naming the scheme and never the URL:
337+ // - an unrecognised `<scheme>://` (`redis://…`). Refusing it here, rather
338+ // than letting a name that ends `.db` reach SQLite, is what keeps
339+ // `redis://h/x.db` refused, as the runtime host refuses it;
340+ // - an OPAQUE scheme, two or more characters and a `:` with no `//`
341+ // (`jdbc:postgresql://…`, `redis:h`). The runtime host's scheme-less arm
342+ // opens these as a SQLite file at a path made of the URL. That is an
343+ // implementation gap on that host (#23765), against its own rule that an
344+ // unknown scheme never falls back to SQLite, so it is not copied here.
345+ // A single letter and a `:` is a drive (`C:\data\app`, `C:/data/app`), not
346+ // a scheme, and stays a path.
347+ const scheme = / ^ [ a - z ] [ a - z 0 - 9 + . - ] * : \/ \/ / i. exec ( u ) ?. [ 0 ] ?? / ^ [ a - z ] [ a - z 0 - 9 + . - ] + : / i. exec ( u ) ?. [ 0 ] ;
348+ if ( scheme ) {
349+ throw new UnsupportedDriverError (
350+ scheme ,
351+ unsupportedUrlSchemeMessage ( scheme ) ,
352+ // Not a kind this resolver can produce — `database-driver-allowlist.pin`
353+ // reads `recognized` to tell the two apart.
354+ { recognized : false } ,
355+ ) ;
356+ }
357+ // No scheme at all: a SQLite file path — `:memory:`, `./app.db`, `data/app`.
358+ return 'sqlite' ;
347359}
348360
349361/**
@@ -500,9 +512,12 @@ export function resolveStorageDefinition(
500512 }
501513
502514 if ( kind === 'sqlite' ) {
503- const filePath = ( databaseUrl ?? ':memory:' )
515+ // `sqlite:` and `sqlite://` are aliases of `file:`, translated by the runtime
516+ // host's own rule (`normalizeDatabaseUrl`) rather than a second copy of it,
517+ // so both hosts open one file: `sqlite://x.db` is `x.db`, never `//x.db`
518+ // (the filesystem root), and `sqlite:///abs/x.db` is `/abs/x.db`.
519+ const filePath = normalizeDatabaseUrl ( databaseUrl ?? ':memory:' )
504520 . replace ( / ^ f i l e : / , '' )
505- . replace ( / ^ s q l i t e : / , '' )
506521 . replace ( / ^ s q l : \/ \/ / , '' ) ;
507522 return {
508523 driverId : 'sqlite' ,
0 commit comments