🚧 Glyx is pre-release software. APIs may change before v1.0. Get started →
Documentation
APIs & Bindings
db (SQLite)

db Stable WINMACLNX

Built-in SQLite 3 with WAL mode enabled by default. All calls are async (return Promise).

Requires capability: "db": true in glyx.config.json.

Capability Demo
glyx.config.ts
export default defineConfig({
            capabilities: [
    "db",
  ],
            })
db.query('SELECT * FROM notes')
[{ id: 1, title: "Meeting notes", body: "..." }]

Import

import { db } from '@glyx-dev/react'

Opening a database

// Open once at startup — auto-sets the default handle.
await db.open('app.db')
 
// Multi-DB: keep explicit handles.
const usersDb = await db.open('users.db')
const logsDb  = await db.open('logs.db')
db.setDefault(logsDb)

":memory:" opens an in-process ephemeral database. Paths are resolved relative to the app's data directory.

Queries

db.query(sql, params?)

SELECT and return all rows as plain objects:

const notes = await db.query('SELECT * FROM notes ORDER BY created_at DESC')
// → { id: number, title: string, body: string }[]
 
const note = await db.query('SELECT * FROM notes WHERE id = ?', [id])
// → use note[0] for a single row

db.run(sql, params?)

INSERT, UPDATE, DELETE, or DDL:

await db.run(`
  CREATE TABLE IF NOT EXISTS notes (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    title      TEXT NOT NULL,
    body       TEXT,
    created_at INTEGER DEFAULT (unixepoch())
  )
`)
 
const { lastInsertId, rowsAffected } = await db.run(
  'INSERT INTO notes (title, body) VALUES (?, ?)',
  ['My note', 'Content here']
)
console.log(lastInsertId)   // → 1
console.log(rowsAffected)   // → 1

db.transaction(stmts)

Run multiple statements atomically. Any failure rolls back all:

await db.transaction([
  { sql: 'INSERT INTO archived SELECT * FROM notes WHERE id = ?', params: [id] },
  { sql: 'DELETE FROM notes WHERE id = ?', params: [id] },
])

All methods accept an optional explicit handle as the first argument when working with multiple databases:

await db.run(logsDb, 'INSERT INTO events (msg) VALUES (?)', ['started'])
await db.query(usersDb, 'SELECT * FROM users')
await db.transaction(logsDb, [{ sql: 'DELETE FROM events WHERE ttl < ?', params: [Date.now()] }])

Migrations

db.migrate() tracks applied versions in _glyx_migrations and only runs pending entries. Each migration is committed atomically with its tracking record — a partial failure leaves the database clean.

await db.migrate([
  {
    version: 1,
    name:    'create_notes',
    up: `CREATE TABLE notes (
      id         INTEGER PRIMARY KEY AUTOINCREMENT,
      title      TEXT    NOT NULL DEFAULT '',
      body       TEXT    NOT NULL DEFAULT '',
      created_at INTEGER NOT NULL DEFAULT (unixepoch())
    )`,
  },
  {
    version: 2,
    name:    'add_pinned',
    up: `ALTER TABLE notes ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0`,
  },
  {
    version: 3,
    name:    'add_fts',
    // Multiple statements — pass an array:
    up: [
      `CREATE VIRTUAL TABLE notes_fts USING fts5(title, body, content=notes, content_rowid=id)`,
      `CREATE TRIGGER notes_ai AFTER INSERT ON notes BEGIN
         INSERT INTO notes_fts(rowid, title, body) VALUES (new.id, new.title, new.body);
       END`,
    ],
  },
])

up is a string (single statement) or an array of strings. Migrations run in ascending version order.

Seeding

db.seed() runs setup data after migrations:

// Always run — write idempotent SQL:
await db.seed(async () => {
  await db.run('INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)', ['theme', 'dark'])
})
 
// Run once per name — tracked in _glyx_seeds:
await db.seed('sample_notes', async () => {
  await db.run('INSERT INTO notes (title) VALUES (?)', ['Welcome'])
})

Backups

db.backup(destPath)

Create an atomic online backup using SQLite's VACUUM INTO. Works with WAL mode and does not block reads or writes on the source database.

await db.backup('./backups/app-manual.sqlite')
// With explicit handle:
await db.backup(usersDb, './backups/users-2026-07-09.sqlite')

The destination directory is created automatically. Any existing file at destPath is overwritten atomically.

db.config({ backup })

Schedule automatic backups on a timer:

db.config({
  backup: {
    dir:      './backups',  // destination directory (relative to app data dir, or absolute)
    interval: '1h',         // '1h' | '6h' | '12h' | '24h' | 'daily' | milliseconds
    keep:     5,            // number of backup files to retain (oldest pruned)
  }
})

Backup files are named backup-<ISO8601>.sqlite (e.g. backup-2026-07-09T14-00-00.sqlite). Old files beyond keep are pruned automatically.

Calling db.config() again with the same handle replaces the previous schedule. Omit backup to cancel:

db.config({})  // cancels auto-backup for the default handle
OptionTypeDefaultDescription
dirstring'./backups'Backup directory
intervalstring | number'24h'Schedule interval
keepnumber5Max backup files to retain

Using Drizzle ORM

@glyx-dev/drizzle bridges Drizzle ORM to the native Glyx SQLite bindings via the sqlite-proxy driver — no better-sqlite3 or any other native dependency needed. Full Drizzle docs at packages/drizzle.

npm install drizzle-orm @glyx-dev/drizzle

Define your schema:

src/schema.ts
import { sqliteTable, integer, text, sql } from 'drizzle-orm/sqlite-core'
 
export const notes = sqliteTable('notes', {
  id:        integer('id').primaryKey({ autoIncrement: true }),
  title:     text('title').notNull(),
  body:      text('body'),
  createdAt: integer('created_at').default(sql`(unixepoch())`),
})

Create the Drizzle instance from a Glyx db handle:

src/db.ts
import { db } from '@glyx-dev/react'
import { createDrizzle } from '@glyx-dev/drizzle'
import * as schema from './schema'
 
const handle = await db.open('app.db')
export const drizzleDb = createDrizzle(handle, schema)

Use it anywhere:

import { drizzleDb } from './db'
import { notes } from './schema'
import { desc, eq } from 'drizzle-orm'
 
const allNotes = await drizzleDb.select().from(notes).orderBy(desc(notes.createdAt))
 
const note = await drizzleDb.select().from(notes).where(eq(notes.id, 1))
 
await drizzleDb.insert(notes).values({ title: 'New note', body: '' })
 
await drizzleDb.delete(notes).where(eq(notes.id, id))

createDrizzle accepts an optional Drizzle relational schema as the second argument to enable the .query.* relational API.

Database location

Paths passed to db.open() are resolved relative to the app's data directory:

macOS:   ~/Library/Application Support/<app-name>/
Windows: %APPDATA%\<app-name>\
Linux:   ~/.local/share/<app-name>/

Use an absolute path to override:

await db.open('/tmp/test.db')
await db.open(':memory:')

API reference

MethodReturnsDescription
db.open(path)Promise<number>Open or create a database; returns handle
db.close(handle?)Promise<void>Close and release connections
db.setDefault(handle)voidSet the handle used when none is passed
db.query(sql, params?)Promise<object[]>SELECT — returns all rows
db.run(sql, params?)Promise<{rowsAffected, lastInsertId}>INSERT / UPDATE / DELETE / DDL
db.transaction(stmts)Promise<void>Atomic batch of {sql, params?} statements
db.migrate(migrations)Promise<number>Apply pending {version, name, up} migrations
db.seed(name?, fn)Promise<void>Run setup data (tracked or always)
db.backup(destPath)Promise<void>Atomic online backup via VACUUM INTO
db.config(opts)voidConfigure auto-backup schedule

All methods accept an optional explicit handle as the first argument.

See the Database guide for full patterns and examples.