npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@molecule/api-database-sqlite

v1.4.2

Published

SQLite database provider for molecule.dev

Readme

@molecule/api-database-sqlite

SQLite database provider for molecule.dev.

Provides DatabasePool and DataStore implementations backed by better-sqlite3. Ideal for lightweight services, status pages, and single-instance deployments.

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

Quick Start

import { setPool, setStore } from '@molecule/api-database'
import { pool, store } from '@molecule/api-database-sqlite'

setPool(pool)
setStore(store)

Type

provider

Installation

npm install @molecule/api-database-sqlite @molecule/api-bond @molecule/api-database @molecule/api-secrets better-sqlite3
npm install -D @types/better-sqlite3

API

Interfaces

MigrationOptions

Stable identities for apps that apply SQL from more than one directory.

interface MigrationOptions {
  /** Prefix for ledger keys. Omit for the application's existing migration history. */
  namespace?: string
  /**
   * Other directories that could own a legacy bare filename. Pass the complete
   * list when upgrading old history; duplicate basenames are refused because
   * the old ledger did not record which file ran. An empty list asserts that
   * this is the only migration directory.
   */
  legacyDirectories?: readonly string[]
}

SqliteConfig

Configuration for the SQLite database provider.

interface SqliteConfig {
  /** Path to the SQLite database file. Defaults to SQLITE_PATH env var or './data/app.db'. */
  path?: string
  /** Enable WAL mode for better concurrent read performance. Defaults to true. */
  walMode?: boolean
  /** Enable foreign key constraints. Defaults to true. */
  foreignKeys?: boolean
}

ProcessEnv

Environment variables consumed by the SQLite database provider.

interface ProcessEnv {
  SQLITE_PATH?: string
}

SqliteColumnMeta

Minimal shape of better-sqlite3's Statement.columns() entries we use.

interface SqliteColumnMeta {
  name: string
  type: string | null
}

Functions

defaultPoolCreated()

Whether the lazy default pool has been created.

Readiness and shutdown wiring must not FORCE a connection.

function defaultPoolCreated(): boolean

Returns: true once the default pool exists.

closeDefaultPool()

Close the lazy default pool, if it was created.

function closeDefaultPool(): Promise<void>

Returns: Resolves once the pool has ended.

registerLifecycleIntegration(resources)

Register the database readiness check and shutdown hook with the bonded process-lifecycle provider.

function registerLifecycleIntegration(resources: {
  ping: () => Promise<void>
  close: () => Promise<void>
  created: () => boolean
}): void
  • resources — The ping and close functions this bond owns.

createMigrator(migrationsDir, options)

Returns a runMigrations() bound to a migrations directory.

function createMigrator(migrationsDir: string, options?: MigrationOptions): () => Promise<void>
  • migrationsDir — Absolute path to the directory of ordered *.sql migration files, resolved from the API package directory.
  • options — A stable namespace for a second SQL directory and evidence for adopting legacy bare filenames without replaying their SQL.

Returns: A no-arg runMigrations() that opens the SQLite file (creating it and its parent directory if missing) and applies every migration file in lexical order using multi-statement exec(). Reads the file path from the SQLITE_PATH env var (default ./data/app.db), matching the pool.

createPool(config)

Creates a SQLite DatabasePool backed by better-sqlite3.

function createPool(config?: SqliteConfig): DatabasePool
  • config — SQLite configuration options.

Returns: A DatabasePool wrapping a synchronous better-sqlite3 connection.

createStore(pool)

Creates a DataStore backed by a SQLite DatabasePool.

function createStore(pool: DatabasePool): DataStore
  • pool — The SQLite DatabasePool to use for queries.

Returns: A DataStore that translates CRUD operations to SQLite-compatible SQL.

convertPlaceholders(text, values)

Convert PostgreSQL-style positional placeholders ($1, $2, ...) to SQLite-style (?) placeholders. Also reorders values array to match the placeholder order.

A statement whose placeholders and values disagree is REFUSED, with the same contract PostgreSQL enforces at bind time (bind message supplies 3 parameters, but prepared statement "" requires 2). This converter is the only place that sees both the SQL text and the values, and without the check a $N numbering slip — a list numbered from the loop index that restarts at $1 and collides with a $1 already in the statement, leaving a supplied value with no placeholder — returns WRONG ROWS with no error at all. Postgres rejects the same call outright, so an app developed on this bond (every scaffold's default) otherwise carries a class of bug only its production database would name.

function convertPlaceholders(text: string, values?: unknown[]): { text: string; values: unknown[] }
  • text — SQL query text with $N placeholders.
  • values — Parameter values ordered by $N index.

Returns: The query text with ? placeholders and reordered values.

translateDdlToSqlite(sql)

Translate PostgreSQL-dialect DDL to SQLite-compatible DDL at migration time.

Resource/template setup migrations are authored in PostgreSQL dialect, but a SQLite project applies them raw via the migrator's db.exec(). SQLite tolerates Postgres type names through affinity (uuid, timestamptz, jsonb, boolean), but it rejects Postgres function/cast/index SYNTAX, which raises near "(": syntax error and aborts the whole migration. This rewrites the handful of constructs that actually appear across the resource library so those migrations apply on SQLite:

  • DEFAULT gen_random_uuid() → DEFAULT (lower(hex(randomblob(16)))), the SQLite equivalent random-id default. Resource handlers set id = id || uuid() so they don't need it, but a custom handler using the bare create('t', {…}) (no explicit id — the documented pattern) DOES rely on the column default; stripping it left id … NOT NULL with no default, so every such insert hit NOT NULL constraint failed: t.id. Translating (not stripping) keeps both paths working: resource handlers pass an id that overrides the default, custom handlers get one generated.
  • now() / NOW(), and a literal DEFAULT CURRENT_TIMESTAMP (either case — the form 831 of the template migrations use), → DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')): ISO-8601, UTC, milliseconds. Those defaults previously became SQLite's current_timestamp, whose

(description truncated — read the full text in node_modules/@molecule/api-database-sqlite/dist/utilities.d.ts.)

function translateDdlToSqlite(sql: string): string
  • sql — Raw migration SQL, possibly in Postgres dialect.

Returns: SQLite-compatible SQL.

coerceSqliteParam(value)

Coerce a JS value into something better-sqlite3 can bind. better-sqlite3 only accepts numbers, strings, bigints, buffers, and null — a JS boolean, plain object, array, undefined, or Date throws at bind time, which would crash every create()/update() that passes a boolean or jsonb value (e.g. users.twoFactorEnabled, projects.packages). Mirrors what the Postgres driver accepts: booleans → 0/1, objects/arrays → JSON text, Date → ISO string, undefined → null.

function coerceSqliteParam(value: unknown): unknown
  • value — The value to bind.

Returns: A better-sqlite3-bindable primitive.

normalizeSqliteRows(rows, columns)

Normalize a SQLite result set back to JS types using the declared column types, so values round-trip like the Postgres bond instead of leaking SQLite's storage form: a BOOLEAN column's 0/1 → boolean, a JSON/JSONB column's text → parsed value. Columns with an unknown/expression type (null) and values that aren't in storage form pass through untouched. Mutates rows in place for speed.

function normalizeSqliteRows<T>(rows: Record<string, unknown>[], columns: SqliteColumnMeta[]): T[]
  • rows — Raw rows from stmt.all().
  • columns — stmt.columns() metadata for the result set.

Returns: The normalized rows.

Constants

sqlCapabilities

What the SQLite bond supports from PostgreSQL-dialect migrations.

fk-referential-action is native only because the pool turns on PRAGMA foreign_keys for every connection — the declaration and sqlite/src/pool.ts must stay in step.

const sqlCapabilities: SqlCapabilities

pool

The SQLite connection pool instance.

const pool: DatabasePool

store

Default DataStore proxy backed by the lazily-initialized default pool.

const store: DataStore

MIGRATION_LEDGER

The table that records which migration files have run.

const MIGRATION_LEDGER: '_molecule_migrations'

databaseSqliteSecretDefinitions

Secret definitions required by the SQLite database bond.

const databaseSqliteSecretDefinitions: SecretDefinition[]

Core Interface

Implements @molecule/api-database interface.

Bond Wiring

Setup function to register this provider with the core interface:

import { setPool, setStore } from '@molecule/api-database'
import { pool, store } from '@molecule/api-database-sqlite'

export function setupDatabaseSqlite(): void {
  setPool(pool)
  setStore(store)
}

Injection Notes

Requirements

Peer dependencies:

  • @molecule/api-bond ^1.0.1
  • @molecule/api-database ^1.2.0
  • @molecule/api-secrets ^1.0.1

Environment Variables

  • SQLITE_PATH (optional) — SQLite database path — default: ./data/app.db
    • Setup: Filesystem path of the SQLite database file (created on first run).
    • Example: ./data/app.db

Runtime Dependencies

  • @molecule/api-bond
  • @molecule/api-database
  • @molecule/api-secrets
  • better-sqlite3

Configure via the SQLITE_PATH environment variable (default: ./data/app.db). WAL mode and foreign key constraints are enabled by default.

  • Schema changes are *.sql files in the app's api/migrations/. A scaffolded API applies new files in lexical order each time it starts (server.ts runs createMigrator), so a new file applies on the next restart; write CREATE TABLE IF NOT EXISTS. Each file runs ONCE: the _molecule_migrations table records it, and an edit to a recorded file does not run again — except that a column added to its CREATE TABLE is still added. Every other change to an applied schema goes in a NEW file. Postgres-dialect DDL is translated (gen_random_uuid(), now() / CURRENT_TIMESTAMP, :: casts, USING <method>, ADD COLUMN IF NOT EXISTS), so the same file works on the postgres bond.
  • A @molecule/api-resource-* package's src/__setup__/*.sql is Postgres dialect and applies here as it is — copy the file into api/migrations/ unchanged. The migrator translates it on the way in (above), and a resource package's uuid/timestamptz/jsonb type names need no translation at all: SQLite takes them by affinity, and the store reads a JSON/JSONB column back as a parsed value. Never hand-write a SQLite version of a package's schema: a transcribed copy drifts from the schema that package's own queries expect, and the drift surfaces as no such column at runtime rather than at the migration.
  • A DEFAULT now() / DEFAULT CURRENT_TIMESTAMP column yields ISO-8601 UTC with milliseconds (2026-10-16T01:22:21.416Z) — the format the other bonds and new Date().toISOString() produce. SQLite's own current_timestamp is space-separated, Z-less and whole-second, which new Date() reparses as LOCAL time and which ties for two rows written in the same second; the migrator translates those defaults so stored values are ISO and compare as strings in time order. A column a caller writes explicitly is left alone.
  • pool.query(text, values) takes Postgres-style $1, $2 placeholders (converted to ? and reordered) or plain ? placeholders.
  • pool.transaction() calls and plain pool.query() calls are serialized behind an internal FIFO queue (better-sqlite3 has only ONE shared connection — there is no per-transaction isolation). A transaction() holds the queue for its whole BEGIN → … → COMMIT/ROLLBACK lifetime, so a concurrent transaction() or query() call waits its turn instead of racing BEGIN or silently running inside — and being discarded by — someone else's rollback. Don't hold a transaction open across an await on unrelated work; every other query on this pool queues behind it until it resolves.
  • All-or-nothing writes: withTransaction(async () => { … }) from @molecule/api-database. Every call inside it — the CRUD helpers (create, findMany, updateById, …) and query() — joins the one transaction; it commits when the callback resolves, rolls back when it throws. Never call the CRUD helpers between a manual pool.transaction() and its commit(): they queue behind the transaction they belong to, and fail after 30 s with an error naming withTransaction().
  • like is case-insensitive and does NOT escape the value — the caller's own %/_ are honored as wildcards, identical to the postgresql/mysql bonds. For human-typed search input, use ilike instead (escapes + auto- wraps %…%) — see WhereCondition['operator'] in @molecule/api-database.
  • A migration file with a genuine error (not an idempotent "already exists"/"duplicate column name") now FAILS the boot with every broken file named, instead of warn-logging and booting with a partial schema.