Skip to content

mobiletoly/sqlitenow-kmp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

174 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SQLiteNow

Kotlin Multiplatform Maven Central CI License

If SQLiteNow saves you time, please consider starring ⭐ the repository - it helps more KMP and Flutter/Dart developers find it.

SQLiteNow is SQL-first tooling for type-safe SQLite access in Kotlin Multiplatform and Flutter/Dart apps. Write schema, migration, and query files in SQL, then generate platform-native code with typed parameters, typed results, migrations, transactions, and reactive invalidation.

Kotlin Multiplatform apps use the dev.goquick.sqlitenow Gradle plugin and KMP runtime libraries.

Flutter and Dart apps use sqlitenow_runtime plus sqlitenow_cli. You might see a little documentation bias toward KMP at first, because originally project was started as KMP-only, but eventually it will have a full docs parity.

Sync-Ready: SQLiteNow includes Oversqlite, a synchronization system for multi-device applications with conflict resolution and offline-first behavior.

Overview

SQLiteNow generates code from your SQL files, giving you full control over your queries while maintaining type safety. Unlike SQLDelight (for KMP), which supports multiple database engines, SQLiteNow is focused exclusively on SQLite, allowing for deeper integration and SQLite-specific optimizations.

Full documentation is available at https://mobiletoly.github.io/sqlitenow-kmp/.

First Run

Kotlin Multiplatform

[plugins]
sqlitenow = { id = "dev.goquick.sqlitenow", version = "0.9.1" }

[libraries]
sqlitenow-core = { module = "dev.goquick.sqlitenow:core", version = "0.9.1" }
sqliteNow {
    databases {
        create("SampleDatabase") {
            packageName.set("com.example.app.db")
        }
    }
}

Start here: https://mobiletoly.github.io/sqlitenow-kmp/kmp/

While we have added includes few sample projects to this repository, but for a very simple end-to-end walkthrough, follow the Mood Tracker tutorial series and browse the accompanying sample project.

Flutter/Dart

dependencies:
  sqlitenow_runtime: ^0.9.0

dev_dependencies:
  sqlitenow_cli: ^0.9.0
flutter pub run sqlitenow_cli generate

For pure Dart packages, use:

dart run sqlitenow_cli generate

The released Dart CLI package embeds the SQLiteNow compiler jar. Flutter and Dart users do not pass --compiler-jar for normal consumption.

Start here: https://mobiletoly.github.io/sqlitenow-kmp/flutter/

Key Features

Supported Platforms

  • Android (Kotlin/JVM via AndroidX bundled SQLite driver)
  • iOS (Kotlin/Native with bundled SQLite)
  • macOS native (macosArm64, macosX64) with bundled SQLite
  • Linux native (linuxX64, linuxArm64) with bundled SQLite
  • JVM desktop/server targets
  • JavaScript (browser) via SQL.js with optional IndexedDB persistence
  • Kotlin/Wasm (browser) using the same SQL.js runtime with automatic OPFS or IndexedDB persistence
  • Flutter native runtimes through Dart VM and package:sqlite3

Type-Safe SQL Generation

  • Pure SQL Control - Write your queries in SQL files, get type-safe generated code
  • Comment-based Annotations - Control code generation using simple -- @@{ annotations } comments in your SQL.
  • No IDE Plugin Required - Works with any editor
  • Multiple Consumption Paths - Flutter/Dart packages use the Dart CLI; KMP apps use the Gradle plugin
  • SQLite Focused - Optimized specifically for SQLite features and capabilities
  • Migration support - Migration scripts are supported to manage database schema changes

Optional Multi-Device Synchronization

  • Built-in Sync System - Complete synchronization solution for multi-device applications
  • Conflict Resolution - Automatic conflict resolution with pluggable strategies (Server Wins, Client Wins, etc.)
  • Change Tracking - Automatic tracking of INSERT, UPDATE, DELETE operations
  • Offline-First - Works seamlessly offline, syncs when connection is available
  • JWT Authentication - Secure sync with customizable authentication via HttpClient
  • Incremental Sync - Efficient sync with pagination and change-based updates

Components

Client-side framework components:

  • SQLiteNow Generator - The code generation component of the SQLiteNow framework that generates type-safe Kotlin or Dart code from SQL files.
  • SQLiteNow Library - The core library that provides convenient APIs for database access.
  • OverSqlite - The sync component of the SQLiteNow framework that enables seamless data sharing across multiple devices with automatic conflict resolution, change tracking, and offline-first capabilities.

Server-side components:

  • OverSync - Sync server that provides an adapter library for data synchronization. Currently we have go-oversync implementation in Go with PostgreSQL as data store. Visit this link for more information: https://github.com/mobiletoly/go-oversync

It is important to mention that you can use SQLiteNow Generator and SQLiteNow Library without using OverSqlite for synchronization. And vice versa - you can use OverSqlite for synchronization of SQLite database with PostgreSQL without using SQLiteNow Generator and SQLiteNow Library.

Why SQLiteNow exists if SQLDelight is really awesome

Even if you don't care about multi-device synchronization, SQLiteNow has few key differences from SQLDelight:

First of all, I wanted to target specifically SQLite in Kotlin Multiplatform and Dart environments, these are my platform of choice as of now for mobile development.

Second, since we use comment-based annotations, no plugin is required (like in case of SQLDelight), so you can easily use just a regular SQL files that will be validated by your IDE or other external tools for correctness.

Third, I wanted to have a more flexible and extensible code generation system that can be easily extended and customized. I use hexagonal architecture in my code, but sometimes converting between multiple layers is tiresome, so I wanted to have a way to generate code that will be very close to my domain layer, without sacrificing the ability to write pure SQL queries.

Here is the brief example:

-- @@{ queryResult=PersonWithAddresses }
SELECT p.id,
       p.first_name,
       p.last_name,
       p.email,
       p.created_at,

       a.address_type,
       a.postal_code,
       a.country,
       a.street,
       a.city,
       a.state

/* @@{ dynamicField=addresses,
       mappingType=collection,
       propertyType=List<Address>,
       sourceTable=a,
       collectionKey=address_id } */

FROM Person p
     LEFT JOIN PersonAddress a ON p.id = a.person_id
ORDER BY p.id, a.address_type
LIMIT :limit OFFSET :offset

This will generate PersonWithAddresses data class. This class has addresses: List<Address> property that contains all home addresses for the person. Another class PersonQuery.SelectAllWithAddresses.Params will be generated as well with limit and offset parameters to pass parameters to the query.

And you can define your own adapters to convert between SQLite and your domain types and register them for seamless integration. We provide few built-in adapters as well, such as converting from TEXT to Kotlin's date/time etc.

You can always shape your data even more with mapTo annotation.

Full example is available in the /sample-kmp directory (for KMP) and in [/dart/examples] for Dart/Flutter.

Multi-Device Synchronization (optional)

SQLiteNow includes a complete synchronization system for building multi-device applications. Sync setup requires three explicit pieces on each client:

  • the SQLiteNow runtime package
  • the Oversqlite runtime package
  • generated database configuration with Oversqlite enabled

The sync system automatically handles:

  • Change tracking for all sync-enabled tables
  • Conflict resolution for concurrent writes across devices
  • Incremental sync to minimize bandwidth usage
  • Chunked push upload for large dirty sets without a total-row hard ceiling
  • Error handling and retry logic
  • Authentication via customizable HTTP clients

If you have a table to sync, annotate it with enableSync=true:

-- Enable sync for this table
-- @@{ enableSync=true }
CREATE TABLE person (
    id TEXT PRIMARY KEY NOT NULL,
    first_name TEXT NOT NULL,
    last_name TEXT NOT NULL,
    email TEXT UNIQUE,
    created_at INTEGER NOT NULL DEFAULT (unixepoch())
);

Sync-enabled tables must use exactly one local PRIMARY KEY column of type TEXT or BLOB. INTEGER/BIGINT sync keys are rejected, and local sync-enabled tables must not model the reserved server scope column _sync_scope_id.

Kotlin Multiplatform

Add the KMP runtime artifacts and enable Oversqlite in the Gradle DSL:

commonMain.dependencies {
    implementation("dev.goquick.sqlitenow:core:<version>")
    implementation("dev.goquick.sqlitenow:oversqlite:<version>")
}

sqliteNow {
    databases {
        create("AppDatabase") {
            packageName = "com.example.app.db"
            oversqlite = true
        }
    }
}

Then use the generated sync client in your application:

// Create authenticated HTTP client with JWT token refresh and base URL
val httpClient = HttpClient {
    install(Auth) {
        bearer {
            loadTokens { /* load saved token */ }
            refreshTokens { /* refresh when expired */ }
        }
    }
    defaultRequest {
        url("https://api.myapp.com")
    }
}

// Create sync client
val syncClient = db.newOversqliteClient(
    schema = "myapp",
    httpClient = httpClient,
    resolver = ServerWinsResolver
)

// Open local runtime and attach the authenticated account.
syncClient.open().getOrThrow()
syncClient.attach(userId = "user123").getOrThrow()

// Perform full sync (upload local changes, download remote changes)
syncClient.sync().getOrThrow()

KMP sync example: /samplesync-kmp.

Flutter/Dart

Add the Dart runtime packages and enable Oversqlite in sqlitenow.yaml:

dependencies:
  sqlitenow_runtime: ^0.9.0
  sqlitenow_oversqlite: ^0.9.0

dev_dependencies:
  sqlitenow_cli: ^0.9.0
databases:
  AppDatabase:
    input: lib/db/sql/AppDatabase
    output: lib/db/generated
    package: app.db
    runtime: dart
    oversqlite: true

Then use the generated sync client in your application:

final httpClient = IoOversqliteHttpClient(
  baseUri: Uri.parse('https://api.myapp.com'),
  defaultHeaders: {
    HttpHeaders.authorizationHeader: 'Bearer $token',
  },
);

final syncClient = db.newOversqliteClient(
  schema: 'myapp',
  httpClient: httpClient,
);

await syncClient.open();
await syncClient.attach('user123');
await syncClient.sync();

Dart sync package and realserver coverage: /dart/packages/sqlitenow_oversqlite.

Documentation

Full documentation is available in the https://mobiletoly.github.io/sqlitenow-kmp/