@lyku/lockstep-sqlite
v0.1.1
Published
Schema-driven SQLite/D1 migration toolkit: DDL generation, introspection, and additive diff/migration for @lyku/lockstep-core models
Downloads
289
Maintainers
Readme
@lyku/lockstep-sqlite
Schema-driven SQLite / Cloudflare D1 migration toolkit for
@lyku/lockstep-core
models. The SQLite-dialect sibling of
@lyku/lockstep-pg: it takes
the same PostgresTableModel definitions and emits SQLite DDL, introspects a
live database, and generates additive migrations.
npm install -D @lyku/lockstep-sqlite @lyku/lockstep-coreGenerate the initial schema
import { generateCreateTablesSql } from '@lyku/lockstep-sqlite';
import { users, sessions, projects } from './my-models';
const sql = generateCreateTablesSql({ tables: { users, sessions, projects } });
// → CREATE TABLE IF NOT EXISTS ... + CREATE INDEX ...Apply to D1: wrangler d1 execute my-db --remote --file=schema.sql.
Evolve a live database
Introspection is driver-agnostic — pass any function that runs a read query and
returns rows. Works with bun:sqlite, better-sqlite3, or a D1 binding:
import { generateMigration } from '@lyku/lockstep-sqlite';
// bun:sqlite
const mig = await generateMigration(config, (sql) => db.query(sql).all());
// Cloudflare D1
const mig = await generateMigration(config, (sql) =>
env.DB.prepare(sql).all().then((r) => r.results),
);
console.log(mig.safe); // additive: CREATE TABLE / ADD COLUMN
console.log(mig.destructive); // DROP TABLE / DROP COLUMN — held back for review
console.log(mig.notes); // e.g. "required column added nullable"PostgreSQL → SQLite type mapping
| lockstep-core type | SQLite |
| ------------------------------------------ | ------------- |
| serial* / bigserial (as single PK) | INTEGER PRIMARY KEY AUTOINCREMENT |
| integer / bigint / smallint | INTEGER |
| snowflake | TEXT (see below) |
| real / double precision / numeric / money | REAL |
| boolean | INTEGER + CHECK (col IN (0,1)) |
| text / varchar / char / enum | TEXT (+ enum / length CHECKs) |
| jsonb / json / array / point | TEXT (serialized) |
| date / time / timestamp / timestamptz | TEXT (ISO-8601) |
| bytea | BLOB |
snowflake → TEXT, not INTEGER. SQLite stores 64-bit integers fine, but
D1 (and most SQLite drivers) return INTEGER columns to JavaScript as a
number (double), exact only up to 2^53 − 1. A snowflake is ~19 digits and
embeds a timestamp in its high bits, so it is always past that and would be
silently rounded on read. As TEXT it round-trips exactly; the app treats it as
a string / BigInt. (This diverges from @lyku/lockstep-pg, where
snowflake → int8 is safe — Postgres returns 64-bit values losslessly.)
bigint stays INTEGER (values are usually small — epoch-ms timestamps,
counters — and benefit from numeric ordering). If a bigint column can exceed
2^53, model it as snowflake (or text) instead.
What it does not do
- No triggers. SQLite has no PostgreSQL-style
updated_attrigger convention; keep timestamp bookkeeping in the application. - No in-place column-type changes. SQLite can't reliably
ALTERa column's type; type divergence is surfaced as anote, not an op.
See LLMs.md for the full API and architecture.
