import type { Dialect } from './dialect.js'; export type QueryArg = null | string | number | bigint | Uint8Array | boolean; export type ResultRow = object; export interface SqlFragmentNoArgs { sql: string; args: []; } export interface SqlFragment { sql: string; args: QueryArg[]; } export interface SqlQuery extends SqlFragment { name?: string; __sqlfuType?: TType; } export interface SqlQueryNoArgs extends SqlFragmentNoArgs { name?: string; __sqlfuType?: TType; } export interface QueryMetadata { rowsAffected?: number; lastInsertRowid?: string | number | bigint | null; } export type RunResult = QueryMetadata; export type QueryResultMode = 'many' | 'nullableOne' | 'one' | 'metadata'; export interface SqlTypedQueryNoArgs extends SqlQueryNoArgs { mode: TMode; __sqlfuType?: TType; } export interface SqlTypedQuery extends SqlQuery { mode: TMode; __sqlfuType?: TType; } export interface SqlModeTag { (strings: TemplateStringsArray): SqlTypedQueryNoArgs; (strings: TemplateStringsArray, ...values: SqlValue[]): SqlTypedQuery; } export interface RootSqlTag { (strings: TemplateStringsArray): SqlQueryNoArgs; (strings: TemplateStringsArray, ...values: SqlValue[]): SqlQuery; many: SqlModeTag<'many'>; nullableOne: SqlModeTag<'nullableOne'>; one: SqlModeTag<'one'>; run: SqlModeTag<'metadata'>; metadata: SqlModeTag<'metadata'>; } /** * Loose param shape accepted by `prepare()` handles. Either positional * (`QueryArg[]`) or named (`Record` keyed by the bare param * name — `:slug` matches `{slug: ...}`). Each adapter translates this to its * driver's binding shape; positional-only drivers (D1, DO, turso-serverless, * expo) route named params through the shared binding helpers in * `sql-params.ts`. */ export type PreparedStatementParams = Record | QueryArg[]; export interface SyncPreparedStatement { all(params?: PreparedStatementParams): TRow[]; run(params?: PreparedStatementParams): RunResult; iterate(params?: PreparedStatementParams): Iterable; [Symbol.dispose](): void; } export interface PreparedStatement { all(params?: PreparedStatementParams): Promise; run(params?: PreparedStatementParams): Promise; iterate(params?: PreparedStatementParams): AsyncIterable; [Symbol.asyncDispose](): Promise; } export interface SyncClient { driver: TDriver; /** OTel `db.system.name`. Stamped by each adapter ('sqlite', 'postgresql', etc.). */ system: string; /** `true` for a `SyncClient`; lets callers route sync/async logic without probing. */ sync: true; all(query: SqlQuery): TRow[]; run(query: SqlQuery): RunResult; raw(sql: string): RunResult; iterate(query: SqlQuery): Iterable; /** * Prepare a SQL string once and reuse the resulting handle for many * `.all` / `.run` / `.iterate` calls. The handle wraps the driver's native * prepared statement where available; on drivers without a real prepare * concept (Durable Objects, sqlite-wasm) it's a shim that re-issues the * driver's exec on every call. */ prepare(sql: string): SyncPreparedStatement; transaction(fn: (tx: SyncClient) => TResult): TResult; transaction(fn: (tx: SyncClient) => Promise): Promise; sql: SyncSqlTag; } export interface AsyncClient { driver: TDriver; /** OTel `db.system.name`. Stamped by each adapter ('sqlite', 'postgresql', etc.). */ system: string; /** `false` for an `AsyncClient`; lets callers route sync/async logic without probing. */ sync: false; all(query: SqlQuery): Promise; run(query: SqlQuery): Promise; raw(sql: string): Promise; iterate(query: SqlQuery): AsyncIterable; /** See {@link SyncClient.prepare}. Async variant; handle uses `Symbol.asyncDispose`. */ prepare(sql: string): PreparedStatement; transaction(fn: (tx: AsyncClient) => Promise | TResult): Promise; sql: AsyncSqlTag; } export type Client = SyncClient | AsyncClient; export interface SyncSqlTag { (strings: TemplateStringsArray, ...values: SqlValue[]): SqlRowsPromise; all(strings: TemplateStringsArray, ...values: SqlValue[]): TRow[]; run(strings: TemplateStringsArray, ...values: SqlValue[]): RunResult; } export interface AsyncSqlTag { (strings: TemplateStringsArray, ...values: SqlValue[]): SqlRowsPromise; all(strings: TemplateStringsArray, ...values: SqlValue[]): Promise; run(strings: TemplateStringsArray, ...values: SqlValue[]): Promise; } export type SqlTag = SyncSqlTag | AsyncSqlTag; export interface SqlRowsPromise extends PromiseLike { catch(onrejected?: ((reason: unknown) => TResult | PromiseLike) | null): Promise; finally(onfinally?: (() => void) | null): Promise; } export type SqlValue = QueryArg | SqlFragment; export type SqlfuValidator = 'arktype' | 'valibot' | 'zod' | 'zod-mini'; export type SqlfuGenerateRuntime = 'sqlfu' | 'effect-v3' | 'effect-v4-unstable' | 'node:sqlite' | 'better-sqlite3' | 'bun:sqlite' | 'libsql' | '@libsql/client'; export type SqlfuGenerateCasing = 'camel' | 'preserve'; /** * Schema source of truth for `sqlfu generate`. Controls where typegen reads * the schema from when building the query catalog. Defaults to * `'desired_schema'` — typegen reads `definitions.sql` directly, so `generate` * works even without a live database. * * - `'desired_schema'` — read `definitions.sql` verbatim. Fastest, most * deterministic, requires no DB. Types follow intent; any drift from * migrations is surfaced by `sqlfu check`. * - `'migrations'` — replay `migrations/*.sql` into a scratch DB and extract * the resulting schema. Types follow what the migrator actually produces; * catches drift from `definitions.sql` implicitly. * - `'migration_history'` — read `sqlfu_migrations` from `config.db`, replay * only the files listed there (in order), extract. Throws if a recorded * migration is missing from `migrations/`. Useful when types should match * what's actually deployed. * - `'live_schema'` — extract schema directly from `config.db`. Requires the * DB to be populated up-front; was the default before the factory form of * `config.db` landed. */ export type SqlfuAuthority = 'desired_schema' | 'migrations' | 'migration_history' | 'live_schema'; export interface SqlfuGenerateConfig { /** * Where typegen reads the schema from. Default `'desired_schema'` — * `definitions.sql` is the source of truth and no DB is required. See * {@link SqlfuAuthority} for each value's semantics. */ authority?: SqlfuAuthority; /** * Emit runtime validation schemas as the source of truth for each generated query's params and result. * * - `null` / `undefined` / omitted = plain TypeScript types, no runtime validation (the default). * - `'arktype'` = generated wrappers declare [arktype](https://arktype.io) schemas via the * `type(...)` constructor, validate through Standard Schema, and derive types from `Schema.infer`. * - `'valibot'` = [valibot](https://valibot.dev) schemas (smaller bundle, functional API). * - `'zod'` = [zod](https://zod.dev) schemas with `.parse()` / `.safeParse()` and `z.infer`. * - `'zod-mini'` = same schema primitives as zod, imported from `zod/mini` and called via the * functional `z.parse(Schema, input)` API (smaller bundle than standard zod). */ validator?: SqlfuValidator | null; /** * When true (default), the generated wrapper catches validation errors thrown by `.parse()` and * re-throws them with a readable, indented message built from the Standard Schema issues list. * When false, the raw error from the underlying validator library passes through untouched. * * No effect when `validator` is null/undefined (plain TS types never throw validation errors). */ prettyErrors?: boolean; /** * When true, generated wrappers take a `SyncClient` and return values synchronously (no * `async`/`await`, no `Promise<...>` return types). Default false. * * Use this when you know your app always runs against a sync driver (`node:sqlite`, * `better-sqlite3`, `bun:sqlite`). The resulting wrappers are easier to call from * non-async contexts (constructors, non-async callbacks). */ sync?: boolean; /** * Experimental JSON type handling. When true, JSON logical columns opt into * generated wrapper handling such as `JSON.stringify` for inputs and * `JSON.parse` for selected result columns. * * Today this covers SQLite columns declared exactly as `json` and exposes them * as `unknown`. The same flag is reserved for typed JSON metadata/schema work * such as reserved `sqlfu_types` declarations. */ experimentalJsonTypes?: boolean; /** * Property casing for generated SQL-derived TypeScript shapes. Default `'camel'`. * * - `'camel'` = column-derived `Data` / `Result` fields use camelCase at the generated query boundary. * - `'preserve'` = generated properties keep the SQL-derived names. * * User-authored placeholder `Params` preserve the names written in SQL in both modes. */ casing?: SqlfuGenerateCasing; /** * Runtime API emitted by `sqlfu generate`. * * - `'sqlfu'` / omitted = existing sqlfu `Client` wrappers. * - `'effect-v3'` = generated functions return Effect values and require * `@effect/sql`'s `SqlClient.SqlClient` from the Effect environment. * - `'effect-v4-unstable'` = generated functions return Effect values and * require Effect v4 beta's `effect/unstable/sql` `SqlClient.SqlClient` * from the Effect environment. * - `'node:sqlite'`, `'better-sqlite3'`, `'bun:sqlite'`, `'libsql'`, and * `'@libsql/client'` = generated functions take that driver directly and * do not import `sqlfu`. * * Non-`sqlfu` runtimes are experimental. */ runtime?: SqlfuGenerateRuntime; /** * Extension used in generated `.generated/index.ts` barrel re-exports (`./tables.js` vs * `./tables.ts`). If omitted, sqlfu infers it from the nearest `tsconfig.json`: * `.ts` when `allowImportingTsExtensions` / `rewriteRelativeImportExtensions` is on, * otherwise `.js`. */ importExtension?: '.js' | '.ts'; } /** * Prefix format used when drafting new migration filenames. * - `'iso'` (default): `2026-04-22T10.30.45.123Z_.sql` * - `'four-digit'`: `0000_.sql`, `0001_.sql`, … (next-integer-after-max of existing * `^\d{4}_` files; starts at `0000` in an empty directory) */ export type SqlfuMigrationPrefix = 'iso' | 'four-digit'; /** * Which ecosystem sqlfu's migration bookkeeping plays nicely with. * * - `'sqlfu'` (default): sqlfu's own `sqlfu_migrations` table with * `(name text primary key, checksum text not null, applied_at text not null)`. * Detects "migration file edited after apply" via checksum. * - `'d1'`: alchemy/wrangler-compatible `d1_migrations` table with * `(id text primary key, name text not null, applied_at text not null)`. Used * by Cloudflare D1 projects that want sqlfu to fully take over migration * ownership from alchemy. No checksum column, so the "edited after apply" * check is skipped under this preset. */ export type SqlfuMigrationPreset = 'sqlfu' | 'd1'; export interface SqlfuMigrationsConfig { path: string; /** * Filename prefix format for newly drafted migrations. Optional; when * omitted, sqlfu derives the default from `preset` (`'sqlfu'` → `'iso'`, * `'d1'` → `'four-digit'`). */ prefix?: SqlfuMigrationPrefix; /** * Bookkeeping preset. Optional; defaults to `'sqlfu'`. */ preset?: SqlfuMigrationPreset; } export interface ResolvedMigrationsConfig { path: string; prefix: SqlfuMigrationPrefix; preset: SqlfuMigrationPreset; } /** * A disposable wrapper around a sync or async SQL client. Accepted by the UI * host so embedded runtimes like Durable Objects can expose their native sync * SQLite handle without wrapping every operation in promises. */ export interface DisposableClient { client: Client; [Symbol.asyncDispose](): Promise; } /** * A disposable wrapper around an `AsyncClient`. Returned by `SqlfuHost.openDb` * and by user-provided `SqlfuDbFactory` callbacks. The `[Symbol.asyncDispose]` * method runs when an `await using` scope exits, letting sqlfu pair each command * with a clean connection lifecycle regardless of where the client came from. */ export interface DisposableAsyncClient { client: AsyncClient; [Symbol.asyncDispose](): Promise; } /** * A factory that produces a fresh disposable client whenever sqlfu needs to touch * the configured database. Invoked on every `host.openDb(config)` call; users * memoize inside the factory if they want to share an expensive resource (e.g. a * Miniflare instance) across multiple sqlfu commands in one process. */ export type SqlfuDbFactory = () => DisposableAsyncClient | Promise; export interface SqlfuConfig { /** * The database sqlfu talks to. Either a filesystem path to a local sqlite * file (sugar for opening it via `node:sqlite`), or a factory that returns a * `DisposableAsyncClient` — use the callback form to point sqlfu at an * adapter-mediated DB (D1, Turso, libsql, miniflare bindings, …) so * `migrate`, `check`, `sync`, `goto`, `baseline`, and the UI all operate on * the same database your app reads from. If omitted, Node-hosted database * commands use `.sqlfu/app.db`. `sqlfu generate` with `authority` set to * `'desired_schema'` or `'migrations'` still does not need a live DB. */ db?: string | SqlfuDbFactory; /** * Migrations directory. Pass a string for the default ISO-timestamp prefix, or * `{ path, prefix: 'four-digit' }` to use `0000_*.sql`, `0001_*.sql`, … for newly * drafted migrations. Omit entirely if your project doesn't use migrations * (e.g. library-author use cases where definitions.sql alone is the source of truth). */ migrations?: string | SqlfuMigrationsConfig; definitions: string; queries: string; generate?: SqlfuGenerateConfig; /** * Dialect-specific behavior (schema diff, formatting, identifier quoting, * migration table DDL, optional locking primitive). Defaults to the built-in * `sqliteDialect`. Set to `pgDialect` from `@sqlfu/pg` to target postgres. */ dialect?: Dialect; } export interface SqlfuProjectConfig { projectRoot: string; db?: string | SqlfuDbFactory; migrations?: ResolvedMigrationsConfig; definitions: string; queries: string; generate: { validator: SqlfuValidator | null; prettyErrors: boolean; sync: boolean; experimentalJsonTypes: boolean; casing: SqlfuGenerateCasing; runtime: SqlfuGenerateRuntime; importExtension: '.js' | '.ts'; authority: SqlfuAuthority; }; /** * The resolved dialect — always set; defaults to `sqliteDialect` when the * user-facing config omits `dialect`. Internal code should consume the * dialect from here rather than checking `config.dialect` directly. */ dialect: Dialect; } export interface MigrateDiffResult { drift: boolean; output: string; }