88 * artifact is available. No authentication, no Studio data, no control
99 * plane — REST routes are served unauthenticated.
1010 *
11- * Auto-detects the appropriate driver from the database URL:
11+ * Auto-detects the appropriate driver from the database URL scheme :
1212 * - `memory://*` → InMemoryDriver
1313 * - `libsql://`, `https://` → TursoDriver
14- * - `file:` / anything else → SqlDriver (better-sqlite3)
14+ * - `postgres[ql]://`, `pg://` → SqlDriver (pg)
15+ * - `mongodb[+srv]://` → MongoDBDriver (peer-dep `@objectstack/driver-mongodb`)
16+ * - `file:` / no scheme → SqlDriver (better-sqlite3)
17+ *
18+ * Unknown URL schemes throw — we never silently fall back to sqlite, since
19+ * that historically created bogus directories on disk (e.g. `mongodb:/`)
20+ * when an unsupported URL was treated as a file path.
1521 */
1622
1723import { resolve as resolvePath } from 'node:path' ;
@@ -22,7 +28,7 @@ import { z } from 'zod';
2228export const StandaloneStackConfigSchema = z . object ( {
2329 databaseUrl : z . string ( ) . optional ( ) ,
2430 databaseAuthToken : z . string ( ) . optional ( ) ,
25- databaseDriver : z . enum ( [ 'sqlite' , 'turso' , 'memory' , 'postgres' ] ) . optional ( ) ,
31+ databaseDriver : z . enum ( [ 'sqlite' , 'turso' , 'memory' , 'postgres' , 'mongodb' ] ) . optional ( ) ,
2632 projectId : z . string ( ) . optional ( ) ,
2733 artifactPath : z . string ( ) . optional ( ) ,
2834} ) ;
@@ -34,6 +40,22 @@ export interface StandaloneStackResult {
3440 api : { enableProjectScoping : false ; projectResolution : 'none' } ;
3541}
3642
43+ type ResolvedDriverKind = 'memory' | 'turso' | 'postgres' | 'mongodb' | 'sqlite' ;
44+
45+ function detectDriverFromUrl ( dbUrl : string ) : ResolvedDriverKind {
46+ if ( / ^ m e m o r y : \/ \/ / i. test ( dbUrl ) ) return 'memory' ;
47+ if ( / ^ ( l i b s q l | h t t p s ? ) : \/ \/ / i. test ( dbUrl ) ) return 'turso' ;
48+ if ( / ^ ( p o s t g r e s ( q l ) ? | p g ) : \/ \/ / i. test ( dbUrl ) ) return 'postgres' ;
49+ if ( / ^ m o n g o d b ( \+ s r v ) ? : \/ \/ / i. test ( dbUrl ) ) return 'mongodb' ;
50+ if ( / ^ f i l e : / i. test ( dbUrl ) ) return 'sqlite' ;
51+ // Bare path without a scheme — treat as a sqlite file path.
52+ if ( ! / ^ [ a - z ] [ a - z 0 - 9 + . - ] * : \/ \/ / i. test ( dbUrl ) ) return 'sqlite' ;
53+ throw new Error (
54+ `[StandaloneStack] Unsupported database URL scheme: ${ dbUrl } . ` +
55+ `Supported schemes: memory://, libsql://, https://, postgres://, pg://, mongodb://, mongodb+srv://, file:`
56+ ) ;
57+ }
58+
3759export async function createStandaloneStack ( config ?: StandaloneStackConfig ) : Promise < StandaloneStackResult > {
3860 const cfg = StandaloneStackConfigSchema . parse ( config ?? { } ) ;
3961
@@ -55,22 +77,51 @@ export async function createStandaloneStack(config?: StandaloneStackConfig): Pro
5577 const dbAuthToken = cfg . databaseAuthToken
5678 ?? process . env . OS_DATABASE_AUTH_TOKEN ?. trim ( )
5779 ?? process . env . TURSO_AUTH_TOKEN ?. trim ( ) ;
58- const dbDriver = cfg . databaseDriver
59- ?? process . env . OS_DATABASE_DRIVER ?. trim ( )
60- ?? ( / ^ ( l i b s q l | h t t p s ? ) : \/ \/ / i . test ( dbUrl ) ? 'turso' : 'sqlite' ) ;
80+ const explicitDriver = cfg . databaseDriver
81+ ?? ( process . env . OS_DATABASE_DRIVER ?. trim ( ) as ResolvedDriverKind | undefined ) ;
82+ const dbDriver : ResolvedDriverKind = explicitDriver ?? detectDriverFromUrl ( dbUrl ) ;
6183
6284 let driverPlugin : any ;
63- if ( dbDriver === 'memory' || dbUrl . startsWith ( 'memory://' ) ) {
85+ if ( dbDriver === 'memory' ) {
6486 const { InMemoryDriver } = await import ( '@objectstack/driver-memory' ) ;
6587 driverPlugin = new DriverPlugin ( new InMemoryDriver ( ) ) ;
66- } else if ( dbDriver === 'turso' || / ^ ( l i b s q l | h t t p s ? ) : \/ \/ / i . test ( dbUrl ) ) {
88+ } else if ( dbDriver === 'turso' ) {
6789 const { TursoDriver } = await import ( '@objectstack/driver-turso' ) ;
6890 driverPlugin = new DriverPlugin (
6991 new TursoDriver ( { url : dbUrl , authToken : dbAuthToken } ) as any ,
7092 ) ;
93+ } else if ( dbDriver === 'postgres' ) {
94+ const { SqlDriver } = await import ( '@objectstack/driver-sql' ) ;
95+ driverPlugin = new DriverPlugin (
96+ new SqlDriver ( {
97+ client : 'pg' ,
98+ connection : dbUrl ,
99+ pool : { min : 0 , max : 5 } ,
100+ } ) as any ,
101+ ) ;
102+ } else if ( dbDriver === 'mongodb' ) {
103+ // MongoDB driver is an optional peer dependency. Importing it lazily
104+ // avoids forcing every standalone consumer to install the mongo SDK.
105+ let MongoDBDriver : any ;
106+ try {
107+ ( { MongoDBDriver } = await import ( '@objectstack/driver-mongodb' as any ) ) ;
108+ } catch ( err : any ) {
109+ throw new Error (
110+ `[StandaloneStack] mongodb URL detected but @objectstack/driver-mongodb is not installed. ` +
111+ `Add it as a dependency or pass an explicit driverPlugin. (${ err ?. message ?? err } )`
112+ ) ;
113+ }
114+ driverPlugin = new DriverPlugin ( new MongoDBDriver ( { url : dbUrl } ) as any ) ;
71115 } else {
116+ // sqlite
72117 const { SqlDriver } = await import ( '@objectstack/driver-sql' ) ;
73118 const filename = dbUrl . replace ( / ^ f i l e : ( \/ \/ ) ? / , '' ) ;
119+ if ( ! filename || / ^ [ a - z ] [ a - z 0 - 9 + . - ] * : \/ \/ / i. test ( filename ) ) {
120+ throw new Error (
121+ `[StandaloneStack] sqlite driver was selected but the URL does not look like a file path: "${ dbUrl } ". ` +
122+ `Use file:/path/to/db.sqlite, or set OS_DATABASE_DRIVER explicitly.`
123+ ) ;
124+ }
74125 mkdirSync ( resolvePath ( filename , '..' ) , { recursive : true } ) ;
75126 driverPlugin = new DriverPlugin (
76127 new SqlDriver ( {
0 commit comments