@@ -939,14 +939,25 @@ export class HttpDispatcher {
939939 /**
940940 * Cloud / Environment Control-Plane routes.
941941 *
942+ * - GET /cloud/drivers → list registered ObjectQL drivers (for env provisioning)
942943 * - GET /cloud/environments → list
943- * - POST /cloud/environments → provision
944+ * - POST /cloud/environments → provision (driver: memory | turso | <any registered driver>)
944945 * - GET /cloud/environments/:id → detail (+ db, credential, membership)
945946 * - PATCH /cloud/environments/:id → update displayName / plan / status / isDefault / metadata
946947 * - POST /cloud/environments/:id/activate → mark as active for session (stub)
947948 * - POST /cloud/environments/:id/credentials/rotate → rotate credential
948949 * - GET /cloud/environments/:id/members → list members
949950 *
951+ * Driver binding
952+ * --------------
953+ * Environments are not tied to any specific driver. At provisioning time the
954+ * caller passes `driver` (a short name such as `memory`, `turso`, or any
955+ * future `sql` / `postgres` driver). The dispatcher validates the name
956+ * against the kernel's registered driver services (`driver.<name>`) and
957+ * derives an appropriate placeholder `database_url` for the chosen driver.
958+ * If `driver` is omitted, the dispatcher auto-selects the first available
959+ * in preference order: turso → memory → any other registered driver.
960+ *
950961 * Backed by ObjectQL sys__environment / sys__environment_database /
951962 * sys__database_credential / sys__environment_member tables (registered
952963 * by `@objectstack/service-tenant`'s `createTenantPlugin`).
@@ -966,6 +977,54 @@ export class HttpDispatcher {
966977 const CRED = 'sys__database_credential' ;
967978 const MEM = 'sys__environment_member' ;
968979
980+ // Enumerate registered ObjectQL drivers. Driver services are registered
981+ // by `DriverPlugin` under the key `driver.<driver.name>` where
982+ // `driver.name` is typically the full FQN like `com.objectstack.driver.memory`.
983+ // We derive a short name by stripping the `com.objectstack.driver.` prefix.
984+ const toShortName = ( driverId : string ) : string => {
985+ const prefix = 'com.objectstack.driver.' ;
986+ return driverId . startsWith ( prefix ) ? driverId . slice ( prefix . length ) : driverId ;
987+ } ;
988+ const listRegisteredDrivers = ( ) : Array < { name : string ; driverId : string } > => {
989+ const services = this . getServicesMap ( ) ;
990+ const drivers : Array < { name : string ; driverId : string } > = [ ] ;
991+ for ( const [ serviceKey , svc ] of Object . entries ( services ) ) {
992+ if ( ! serviceKey . startsWith ( 'driver.' ) ) continue ;
993+ const raw = serviceKey . slice ( 'driver.' . length ) ;
994+ if ( ! raw || raw === 'unknown' ) continue ;
995+ const driverId = ( svc as any ) ?. name ?? raw ;
996+ drivers . push ( { name : toShortName ( driverId ) , driverId } ) ;
997+ }
998+ return drivers ;
999+ } ;
1000+
1001+ const resolveDriver = ( requested : string | undefined ) : { name : string ; driverId : string } | undefined => {
1002+ const registered = listRegisteredDrivers ( ) ;
1003+ if ( requested ) {
1004+ const wanted = String ( requested ) . toLowerCase ( ) ;
1005+ return registered . find ( ( d ) => d . name === wanted || d . driverId === wanted ) ;
1006+ }
1007+ // Auto-pick: prefer turso, then memory, then whatever is available.
1008+ return (
1009+ registered . find ( ( d ) => d . name === 'turso' ) ??
1010+ registered . find ( ( d ) => d . name === 'memory' ) ??
1011+ registered [ 0 ]
1012+ ) ;
1013+ } ;
1014+
1015+ const buildDatabaseUrl = ( driverName : string , environmentId : string ) : string => {
1016+ const dbName = `env-${ environmentId } ` ;
1017+ switch ( driverName ) {
1018+ case 'memory' :
1019+ return `memory://${ dbName } ` ;
1020+ case 'turso' :
1021+ return `libsql://${ dbName } .mock-turso.local` ;
1022+ default :
1023+ // Generic placeholder for future SQL / postgres / mysql drivers.
1024+ return `${ driverName } ://${ dbName } ` ;
1025+ }
1026+ } ;
1027+
9691028 const findOne = async ( obj : string , where : Record < string , unknown > ) : Promise < any | undefined > => {
9701029 let rows = await ql . find ( obj , { where } as any ) ;
9711030 if ( rows && ( rows as any ) . value ) rows = ( rows as any ) . value ;
@@ -974,6 +1033,12 @@ export class HttpDispatcher {
9741033 } ;
9751034
9761035 try {
1036+ // ----- /cloud/drivers ------------------------------------------
1037+ if ( parts . length === 1 && parts [ 0 ] === 'drivers' && m === 'GET' ) {
1038+ const drivers = listRegisteredDrivers ( ) ;
1039+ return { handled : true , response : this . success ( { drivers, total : drivers . length } ) } ;
1040+ }
1041+
9771042 // ----- /cloud/environments collection routes -----
9781043 if ( parts . length === 1 && parts [ 0 ] === 'environments' && m === 'GET' ) {
9791044 const where : Record < string , unknown > = { } ;
@@ -995,10 +1060,34 @@ export class HttpDispatcher {
9951060 const environmentDatabaseId = randomUUID ( ) ;
9961061 const credentialId = randomUUID ( ) ;
9971062 const nowIso = new Date ( ) . toISOString ( ) ;
998- const driver = req . driver ?? 'turso' ;
1063+
1064+ // Bind environment to a driver. `req.driver` is optional — any
1065+ // registered ObjectQL driver is accepted (memory / turso / future
1066+ // sql / postgres). If omitted, pick the best default available.
1067+ const resolved = resolveDriver ( req . driver ) ;
1068+ if ( ! resolved ) {
1069+ const available = listRegisteredDrivers ( ) . map ( ( d ) => d . name ) ;
1070+ if ( req . driver ) {
1071+ return {
1072+ handled : true ,
1073+ response : this . error (
1074+ `Unknown driver '${ req . driver } '. Available drivers: [${ available . join ( ', ' ) || 'none' } ]` ,
1075+ 400 ,
1076+ ) ,
1077+ } ;
1078+ }
1079+ return {
1080+ handled : true ,
1081+ response : this . error (
1082+ 'No ObjectQL driver is registered. Register at least one DriverPlugin (e.g. InMemoryDriver or TursoDriver).' ,
1083+ 503 ,
1084+ ) ,
1085+ } ;
1086+ }
1087+ const driver = resolved . name ;
9991088 const region = req . region ?? 'us-east-1' ;
10001089 const databaseName = `env-${ environmentId } ` ;
1001- const databaseUrl = `libsql:// ${ databaseName } .mock- ${ driver } .local` ;
1090+ const databaseUrl = buildDatabaseUrl ( driver , environmentId ) ;
10021091 const plaintextSecret = `mock-token-${ environmentId } ` ;
10031092
10041093 await ql . insert ( ENV , {
0 commit comments