SQLite client database with OPFS and Effect

Languages

typescript5.9.3

Libraries

effect3.17.14
GithubCode

This example demonstrates how to set up a client-side SQLite database using OPFS (Origin Private File System) with Effect's @effect/sql-sqlite-wasm package.

Setup

The implementation consists of 3 main files:

Worker

worker.ts creates a Web Worker that runs the SQLite database with OPFS storage.

It uses OpfsWorker.run from @effect/sql-sqlite-wasm to initialize and manage the worker lifecycle.

This is where you specify the SQLite database name (sync.sqlite in the example).

SQL Client Layer

sql.ts sets up the SQLite client and database migrations.

The SqliteClient comes from @effect/sql-sqlite-wasm and it's created from the worker.

Adding ?worker allows to import the worker as a module in vite.

Add also the following vite.config.ts:

import { defineConfig } from "vite";

export default defineConfig({
  optimizeDeps: {
    exclude: ["@effect/wa-sqlite"],
  },
  worker: {
    format: "es",
  },
});

The SqlClient and SqliteClient layers are created from a Migrator. In the example the initial migration creates the settings table and inserts an example row.

SqlClient and SqliteClient are merged in a single SqlLive layer.

Service

The Settings service is an example of how to use SqlClient to query the database.

This is not specific to SQLite, it uses the standard @effect/sql API.

You can then provide SqlLive to access the SQLite database instance.


When you run a query, the layer would spawn the worker, initialize the database (migrations), and execute the query on the SQLite database 🪄

import { SqlClient } from "@effect/sql";
import { Effect } from "effect";
import { SqlLive } from "./sql";

// 👇 Example service using `SqlClient`
export class Settings extends Effect.Service<Settings>()("Settings", {
  dependencies: [SqlLive],
  effect: Effect.gen(function* () {
    const sql = yield* SqlClient.SqlClient;
    return {
      get: sql<{ json: string }>`SELECT json FROM settings`,
    };
  }),
}) {}