spaceship_db

Reusable database API for Gleam targeting JavaScript.

Installation

gleam add spaceship_db

Quick Start

import spaceship_db
import spaceship_db/value
import spaceship_db/drivers/sqlite

pub fn main() -> Nil {
  // Connect to database (auto-closes when done)
  use db <- spaceship_db.with_db(sqlite.driver(":memory:"))

  // Create table
  use prepared <- spaceship_db.prepare(db, "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
  use _ <- spaceship_db.exec(prepared, [])

  // Insert data
  use prepared <- spaceship_db.prepare(db, "INSERT INTO users (name) VALUES (?)")
  use _ <- spaceship_db.exec(prepared, [value.text("Alice")])

  // Query data
  use prepared <- spaceship_db.prepare(db, "SELECT * FROM users")
  use rows <- spaceship_db.exec(prepared, [])

  // Get first row
  let first = spaceship_db.get_one(rows)

  // Get all rows
  let all = spaceship_db.get_all(rows)
}

Type-Safe Decoding

Use gleam/dynamic/decode to decode query results into typed values:

import gleam/dynamic/decode
import spaceship_db
import spaceship_db/value
import spaceship_db/drivers/sqlite

type User {
  User(id: Int, name: String)
}

fn user_decoder() -> decode.Decoder(User) {
  use id <- decode.field(0, decode.int)
  use name <- decode.field(1, decode.string)
  decode.success(User(id:, name:))
}

pub fn main() -> Nil {
  use db <- spaceship_db.with_db(sqlite.driver(":memory:"))

  use prepared <- spaceship_db.prepare(db, "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
  use _ <- spaceship_db.exec(prepared, [])

  use prepared <- spaceship_db.prepare(db, "INSERT INTO users (name) VALUES (?)")
  use _ <- spaceship_db.exec(prepared, [value.text("Bob")])

  use prepared <- spaceship_db.prepare(db, "SELECT * FROM users")
  use rows <- spaceship_db.exec(prepared, [])

  // Decode all rows
  let users = spaceship_db.decode_all(rows, user_decoder())

  // Decode single row
  let user = spaceship_db.decode_one(rows, user_decoder())
}

API Reference

Connection

// Create connection (auto-closes when callback returns)
use db <- spaceship_db.with_db(sqlite.driver("path/to/db.sqlite"))

// The connection is automatically closed at the end

Prepared Statements

Statements are reusable with different parameters:

use prepared <- spaceship_db.prepare(db, "SELECT * FROM users WHERE id = ?")

// Execute with different params
use rows1 <- spaceship_db.exec(prepared, [value.int(1)])
use rows2 <- spaceship_db.exec(prepared, [value.int(2)])

Parameter Binding

value.int(42)          // Integer
value.float(3.14)      // Float
value.text("hello")    // Text
value.bool(True)       // Boolean
value.blob(<<1, 2, 3>>) // Binary
value.null()           // Null

Results

use rows <- spaceship_db.exec(prepared, [])

// Option(Dynamic) — None if empty
let first = spaceship_db.get_one(rows)

// List(Dynamic) — all rows
let all = spaceship_db.get_all(rows)

Transactions

spaceship_db.transaction(db, fn(tx) {
  use prepared <- spaceship_db.prepare(tx, "UPDATE accounts SET balance = balance - ?")
  use _ <- spaceship_db.exec(prepared, [value.float(100.0)])

  use prepared <- spaceship_db.prepare(tx, "UPDATE accounts SET balance = balance + ?")
  use _ <- spaceship_db.exec(prepared, [value.float(100.0)])

  Ok(Nil)
})

Error Handling

All operations return Result types. Errors propagate via use syntax:

use db <- spaceship_db.with_db(sqlite.driver("db.sqlite"))
// If connection fails, execution stops with Error(String)

use prepared <- spaceship_db.prepare(db, "INVALID SQL")
// If prepare fails, execution stops with Error(String)

Supported Drivers

DriverPackageConfig
SQLiteBuilt-insqlite.driver(path)
D1Built-ind1.driver(binding)
PostgreSQLComing soonpg(uri:)
MySQLComing soonmysql(uri:)
TursoBuilt-in HTTPturso.driver(url, api_token)

SQLite (Local Development)

import spaceship_db/drivers/sqlite

use db <- spaceship_db.with_db(sqlite.driver("./app.db"))

D1 (Cloudflare Workers)

import spaceship_db/drivers/d1

// In your Cloudflare entry point:
pub fn main(req, env, ctx) {
  // Initialize D1 driver with binding name "DB"
  use db <- spaceship_db.with_db(d1.driver("DB"))
  
  // Use database
  use prepared <- spaceship_db.prepare(db, "SELECT * FROM notes")
  use rows <- spaceship_db.exec(prepared, [])
  
  // Handle request
}

The D1 driver works with Cloudflare’s D1 database. The binding name should match what you defined in your wrangler.toml:

[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "xxx-xxx-xxx"

Turso (SQL over HTTP)

The Turso driver uses Turso’s /v2/pipeline HTTP API. It works in JavaScript runtimes with Fetch support and does not require the libSQL JavaScript client.

import gleam/dynamic.{type Dynamic}
import gleam/javascript/promise.{type Promise}
import spaceship_db
import spaceship_db/drivers/turso

pub fn list_users() -> Promise(Result(List(Dynamic), String)) {
  spaceship_db.with_async_db(
    turso.driver("https://example.turso.io", "your-database-token"),
    fn(db) {
      spaceship_db.prepare_async(db, "SELECT * FROM users", fn(statement) {
        spaceship_db.exec_async(statement, [], fn(rows) {
          promise.resolve(Ok(rows))
        })
      })
    },
  )
}

Turso URLs using turso:// or libsql:// are accepted and converted to HTTPS. The API token should come from a secret or environment variable.

The current async database API does not yet expose Turso’s baton-based interactive transactions. The Turso driver’s transaction methods return an explicit error until that API is available.

Requirements

License

Apache-2.0

Search Document