SQLite Driver
SQLite migration driver with automatic table recreation for limited ALTER TABLE support
Capabilities
| Feature | Supported |
|---|---|
| Native enums | No (CHECK constraint) |
| Native arrays | No (JSON) |
| Index types | btree only |
| Locking | File-based |
| Transactions | Full support |
Type Mappings
| VibORM Scalar | SQLite Type |
|---|---|
string() |
TEXT |
int() |
INTEGER |
bigint() |
INTEGER |
number() |
REAL |
decimal({ precision, scale }) |
checked scaled INTEGER coefficient |
decimal({ precision, scale }).array() |
TEXT containing coefficient-string JSON |
boolean() |
INTEGER (1/0) |
datetime() |
TEXT (ISO 8601) |
json() |
JSON |
blob() |
BLOB |
uuid() |
TEXT |
enumScalar() |
TEXT + CHECK constraint |
| Other array scalars | JSON |
The decimal check requires integer storage and enforces the declared coefficient range. Decimal-list members are JSON strings rather than numeric tokens, so coefficients above JavaScript’s safe-integer range remain exact. Typed operations apply the descriptor; raw SQLite reads expose the physical coefficient or container.
Auto-Increment
SQLite uses INTEGER PRIMARY KEY for auto-increment:
const User = model("users", {
id: int().primaryKey().autoIncrement(),
});
Generates:
CREATE TABLE "users" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT
);
Boolean Handling
SQLite stores booleans as integers:
const User = model("users", {
isActive: boolean().default(true),
});
Generates:
CREATE TABLE "users" (
"isActive" INTEGER NOT NULL DEFAULT 1
);
Values: 1 for true, 0 for false.
Enum Handling
SQLite doesn’t have native enums, so VibORM uses TEXT columns with CHECK constraints for validation:
const User = model("users", {
status: enumScalar(["active", "inactive", "pending"]),
});
Generates:
CREATE TABLE "users" (
"status" TEXT CHECK("status" IN ('active', 'inactive', 'pending')) NOT NULL
);
Enum Operations
Adding enum values: Requires table recreation to update the CHECK constraint.
Removing enum values: Requires table recreation to update the CHECK constraint. You must also handle existing data with the removed values.
Dropping an enum: Converts the column to plain TEXT (removes CHECK constraint) via table recreation.
Array Handling
Arrays are stored as JSON:
const User = model("users", {
tags: string().array(),
scores: int().array(),
});
Generates:
CREATE TABLE "users" (
"tags" JSON NOT NULL,
"scores" JSON NOT NULL
);
Values are JSON-encoded: ["tag1", "tag2"] or [100, 200, 300].
Supported Direct Operations
These operations work directly in SQLite:
| Operation | SQLite Version | DDL |
|---|---|---|
| Add column | 3.0+ | ALTER TABLE ... ADD COLUMN |
| Drop column | 3.35.0+ | ALTER TABLE ... DROP COLUMN |
| Rename column | 3.25.0+ | ALTER TABLE ... RENAME COLUMN |
| Rename table | All | ALTER TABLE ... RENAME TO |
| Create index | All | CREATE INDEX |
| Drop index | All | DROP INDEX |
Table Recreation
For operations SQLite doesn’t support natively, VibORM uses table recreation:
Operations Requiring Recreation
- Alter column (type, nullability, default changes)
- Add/drop foreign key
- Add/drop primary key
- Alter/drop enum (updating CHECK constraints)
These operations rewrite the whole table (create a new table, copy the data across, drop the old one, rename, recreate indexes) — slow on large tables. Renamed columns are mapped to their new names during the copy, new columns get their defaults, and dropped columns are excluded.
DDL Examples
Create Table with Enum
CREATE TABLE "users" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"email" TEXT NOT NULL,
"role" TEXT CHECK("role" IN ('admin', 'user', 'guest')) NOT NULL,
"tags" JSON,
"created_at" TEXT DEFAULT (datetime('now'))
);
Add Column
ALTER TABLE "users" ADD COLUMN "bio" TEXT;
Rename Column
ALTER TABLE "users" RENAME COLUMN "name" TO "full_name";
Drop Column
ALTER TABLE "users" DROP COLUMN "bio";
Limitations
| Limitation | Workaround |
|---|---|
| No native enums | CHECK constraints |
| No native arrays | JSON storage |
| Single index type | Only btree available |
| Limited ALTER TABLE | Table recreation |
| No concurrent writes | File-level locking |
Querying JSON Arrays
Querying JSON arrays requires SQLite’s JSON functions:
-- Check if array contains value
SELECT users.*
FROM users, json_each(users.tags)
WHERE json_each.value = 'admin';
LibSQL / Turso
LibSQL uses the SQLite-family snapshot, type mapping, introspection, and DDL renderer. That does not make its live execution guarantee equivalent to SQLite3 or Bun SQLite.
V1 refuses effectful apply, down, reset, verify, and push on LibSQL.
Its native constraint alteration does not validate existing rows, and safe
table reconstruction has not been proven together with the migration lock,
marker compare-and-swap, and final schema proof. Offline generate and
check, database status and log, and push({ dryRun: true }) remain
available.