Skip to content

Multiplatform setup with the SqlJs Driver

First apply the gradle plugin in your project.

plugins {
  id("app.cash.sqldelight") version "2.0.0-alpha05"
}

repositories {
  google()
  mavenCentral()
}

sqldelight {
  databases {
    create("Database") {
      packageName.set("com.example")
    }
  }
}
plugins {
  id "app.cash.sqldelight" version "2.0.0-alpha05"
}

repositories {
  google()
  mavenCentral()
}

sqldelight {
  databases {
    Database { // This will be the name of the generated database class.
      packageName = "com.example"
    }
  }
}

Put your SQL statements in a .sq file under src/main/sqldelight. Typically the first statement in the SQL file creates a table.

-- src/main/sqldelight/com/example/sqldelight/hockey/data/Player.sq

CREATE TABLE hockeyPlayer (
  player_number INTEGER PRIMARY KEY NOT NULL,
  full_name TEXT NOT NULL
);

CREATE INDEX hockeyPlayer_full_name ON hockeyPlayer(full_name);

INSERT INTO hockeyPlayer (player_number, full_name)
VALUES (15, 'Ryan Getzlaf');

From this SQLDelight will generate a Database Kotlin class with an associated Schema object that can be used to create your database and run your statements on it. Doing this also requires a driver, which SQLDelight provides implementations of:

kotlin {
  // The drivers needed will change depending on what platforms you target:

  sourceSets.androidMain.dependencies {
    implementation "app.cash.sqldelight:android-driver:2.0.0-alpha05"
  }

  // or sourceSets.iosMain, sourceSets.windowsMain, etc.
  sourceSets.nativeMain.dependencies {
    implementation "app.cash.sqldelight:native-driver:2.0.0-alpha05"
  }

  sourceSets.jvmMain.dependencies {
    implementation "app.cash.sqldelight:sqlite-driver:2.0.0-alpha05"
  }

  sourceSets.jsMain.dependencies {
    implementation "app.cash.sqldelight:sqljs-driver:2.0.0-alpha05"
    implementation npm("sql.js", "1.6.2")
    implementation devNpm("copy-webpack-plugin", "9.1.0")
  }
}

Because the SqlJs driver must be initialized asynchronously, the drivers for other platforms must be initialized in a compatible way to be usable in a common source set.

The drivers can be initialized in a coroutine, and a higher-order function can be used to ensure that the driver is initialized before executing a block of code that requires the database:

// in src/commonMain/kotlin
expect suspend fun provideDbDriver(schema: SqlDriver.Schema): SqlDriver

class SharedDatabase(
    private val driverProvider: suspend (SqlDriver.Schema) -> SqlDriver
) {
    private var database: Database? = null

    suspend fun initDatabase() {
        if (database == null) {
            database = driverProvider(Database.Schema).createDatabase()
        }
    }

    suspend operator fun <R> invoke(block: suspend (Database) -> R): R {
        initDatabase()
        return block(database!!)
    }

    private fun SqlDriver.createDatabase(): Database { /* ... */ }
}

val sharedDb = SharedDatabase(::createTestDbDriver)
class DataRepository(
    private val withDatabase: SharedDatabase = sharedDb
) {
    suspend fun getData() = withDatabase { database ->
        /* Do something with the database */
    }
}

// in src/jsMain/kotlin
actual suspend fun provideDbDriver(schema: SqlDriver.Schema): SqlDriver {
    return initSqlDriver(schema).await()
}

// in src/nativeMain/kotlin
actual suspend fun provideDbDriver(schema: SqlDriver.Schema): SqlDriver {
    return NativeSqliteDriver(schema, "test.db")
}

// in src/jvmMain/kotlin
actual suspend fun provideDbDriver(schema: SqlDriver.Schema): SqlDriver {
    return JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY).also { driver ->
        schema.create(driver)
    }
}