Kysely
Run Kysely queries against Minnow in the main thread or a worker.
@minnowdb/kysely lets an existing Kysely application use Minnow as its browser-local database.
It uses Kysely's PostgreSQL compiler, so generated statements use double-quoted identifiers, $1
parameters, RETURNING, and the PostgreSQL forms Minnow supports.
npm install @minnowdb/core @minnowdb/kysely kyselyThe adapter supports Kysely 0.29.x.
Define once and connect
Pass the same schema you migrate to createKysely. It derives Kysely's complete DB map, so
tables and columns are not retyped. migrate() changes the database; createKysely() only creates
the query interface, so keep the explicit migration call:
import { createKysely } from "@minnowdb/kysely";
import { MinnowDatabase, column, schema, table } from "@minnowdb/core";
import { IndexedDbBlockStore } from "@minnowdb/core/storage";
const orders = table("orders", {
order_id: column.integer().unique().autoIncrement(),
customer: column.string(),
total: column.numeric({ precision: 12, scale: 2 }).default("0"),
status: column.enum(["open", "closed"]).default("open"),
note: column.string().nullable(),
});
const appSchema = schema([orders]);
const database = new MinnowDatabase(await IndexedDbBlockStore.open({ name: "shop" }));
await database.migrate(appSchema);
const db = createKysely({ driver: database, schema: appSchema });
const orderRows = await db
.selectFrom("orders")
.select(["order_id", "total"])
.where("total", ">=", 25)
.orderBy("total", "desc")
.execute();The inferred Kysely columns retain Minnow's separate select, insert, and update types:
- defaults and nullable columns are optional on insert;
- enum values remain literal unions;
- exact
NUMERICselects as a losslessstring, while writes and predicates naturally acceptstring | number; - scalar and composite primary-key columns cannot be updated;
- declared views select normally and accept no column values on insert or update.
If an existing factory constructs Kysely, use the exported type directly:
import { Kysely } from "kysely";
import { MinnowDialect, type InferKyselyDatabase } from "@minnowdb/kysely";
type DB = InferKyselyDatabase<typeof appSchema>;
const db = new Kysely<DB>({
dialect: new MinnowDialect({ driver: database, schema: appSchema }),
});Pass schema to the dialect when you construct Kysely yourself. It supplies the exact DB type and
lets the compiler normalize a batch of empty value objects. Literal and SQL-expression defaults
live only in Minnow's catalog, so Kysely, raw SQL, batches, workers, and other tabs all get the same
behavior. There is no adapter-side generation.
An empty Kysely value object compiles to PostgreSQL's normal DEFAULT VALUES form. A batch of
empty objects uses DEFAULT value slots, so catalog defaults run once per row without
adapter-specific SQL.
The generated SQL stays visible and ordinary. Omitted object properties compile to SQL omission
or DEFAULT; explicit null remains a bound NULL:
const query = db.insertInto("orders").values({ customer: "Ada" });
const compiled = query.compile();
// compiled.sql:
// insert into "orders" ("customer") values ($1)
// compiled.parameters: ["Ada"]The catalog fills order_id, total, and status when the statement executes; Kysely does not
hide generated parameters in the compiled query. INSERT ... SELECT may omit default-bearing
target columns for the same reason: the engine evaluates their stored SQL once per selected row.
MinnowDatabaseClient works in the same position, so moving the engine to a worker changes only
how database is constructed. Both implement MinnowSqlDriver structurally.
Supported Kysely operations
- Reads, inserts, updates, deletes, and their
$nparameters. RETURNINGrows and Kysely's affected-row counts.db.transaction().execute(...)through Minnow'sBEGIN,COMMIT, andROLLBACK.- Raw SQL savepoints inside that transaction.
- Kysely schema statements that stay inside Minnow's supported DDL profile.
db.introspection: tables, views, columns, nullability, defaults, auto-increment, and logical types. ExactNUMERIC(p, s), JSON/JSONB, UUID, TIME, INTERVAL, arrays, and enum names are reported instead of being flattened totext.
Minnow has one logical connection. Kysely serializes work through it and a transaction sees its
own staged writes. db.destroy() releases Kysely but does not close the caller-owned Minnow
database or worker client; close that driver separately when the application is done.
Limits
Minnow uses PostgreSQL-style SQL, but it is an embedded database rather than a PostgreSQL server:
getSchemas()returns an empty list. There are no catalogs or schemas to invent.- Transaction access modes and isolation levels are rejected; Minnow has one snapshot model.
- The engine accepts
SAVEPOINT,ROLLBACK TO, andRELEASE; Kysely's dialect interface exposes one top-level transaction rather than a nested transaction helper. - Streaming is rejected because Minnow returns a completed result set.
- Parameters must be
boolean,number,string,Date, ornull; BigInt, byte arrays, and driver-native PostgreSQL objects have no Minnow parameter representation. Exact decimals accept numbers or strings as inputs and return lossless strings. JSON/JSONB, UUID, arrays,TIME, intervals, and enums cross this adapter as strings. - Kysely can generate PostgreSQL features outside Minnow's profile. Check the executable feature matrix; the engine rejects unsupported SQL explicitly.
Kysely reserves the connection during a migration, so one Kysely instance cannot race itself.
That does not coordinate migrations across tabs. For shared application startup, use Minnow's
schema declaration and database.migrate(schema), which coordinates through Minnow's catalog.
Compatibility checks
Adapter tests compile and execute DDL, inserts, queries, RETURNING, commits, rollbacks, and
introspection. Minnow's broader SQL compatibility suite compares supported
PostgreSQL reads and writes with PGlite and checks every documented difference and exclusion.