Skip to content
pre-alpha. The TypeScript API may still shift. The SQL won't.

Import surface

Sqlfu has a few public import paths. Pick the narrowest path that matches what the code is doing.

Use sqlfu in application runtime code. It is the light, portable entrypoint: client types, SQL tags, adapters, instrumentation, config typing, SQLite text helpers, and the small migration runner for generated bundles.

import {createNodeSqliteClient, sql} from 'sqlfu';
import {DatabaseSync} from 'node:sqlite'
const client = createNodeSqliteClient(new DatabaseSync('app.db'));
client.run(sql`
insert into posts(slug, title, body)
values ('hello-world', 'Hi world', 'How are you all doing')
`);

This entrypoint is our precious baby, and because it’s a precious baby, it is small and fiercely protected. There will never be any node:* imports and no heavy commands tooling. It’s what you should use for your app code, inline defineConfig(...) modules, generated query wrappers, workers, and even browser-safe bundles. It will very rarely have breaking changes.

Use sqlfu/api for programmatic access to the functionality behind npx sqlfu commands:

import {check, draft, format, migrate} from 'sqlfu/api';
import {createSqlfuApi} from 'sqlfu/api/core';
await check();
await draft({name: 'add-posts', confirm: (params) => params.body});
await migrate({confirm: (params) => params.body});
const formatted = await format('SELECT * FROM users WHERE id=1;');
const api = createSqlfuApi({projectRoot, config, host});

Note: Mutating commands require a confirm callback anywhere the CLI would ask the user to review SQL or generated file contents. Returning the body (or a modified body) accepts it; returning null or empty string cancels. For “yolo” mode just return params.body, or you can insert your own approval/modification workflow.

await format('select foo from bar') is slightly different to the npx sqlfu format command: instead of modifying files in place, it just formats the sql you pass to it.

The command functions (draft, generate, migrate, serve, etc.) dynamically load Node-only project/config code when called, so call those from Node or Bun tooling, not from runtime Worker request handlers.

createSqlfuApi lives on sqlfu/api/core rather than sqlfu/api because it is the host-explicit command facade. Use it when you are embedding sqlfu operations behind your own host, UI, tests, or runtime boundary.

Use sqlfu/api/core when you are embedding sqlfu operations behind your own host, UI, tests, or runtime boundary.

import {createSqlfuApi} from 'sqlfu/api/core';
const sqlfu = createSqlfuApi({projectRoot, config, host});
await sqlfu.check();
await sqlfu.draft({confirm});

This path has the same command-shaped methods as sqlfu/api, but you provide the SqlfuHost and config/project loading context yourself.

Use sqlfu/api/sync when you only want the runtime sync() primitive, without pulling in the full command facade from sqlfu/api.

import {sync} from 'sqlfu/api/sync';
sync(client, {
definitions: `
create table posts(slug text primary key, body text);
`,
scratchSchema: 'prefix',
});

Durable Objects should use scratchSchema: 'prefix' because they cannot create normal scratch databases. Other sync SQLite clients use scratchSchema: 'scratch-db' by default.

Use sqlfu/cloudflare for config-time helpers that point sqlfu at a Cloudflare D1 database: local sqlite for wrangler dev / alchemy v1’s Miniflare, or HTTP for deployed cloud D1 (alchemy v2, wrangler, Terraform, manual provisioning).

import {defineConfig} from 'sqlfu';
import {findMiniflareD1Path} from 'sqlfu/cloudflare';
export default defineConfig({
db: findMiniflareD1Path('my-dev-app-slug'),
migrations: {path: './migrations', preset: 'd1'},
definitions: './definitions.sql',
queries: './sql',
});

findMiniflareD1Path() walks up from process.cwd() until it finds a supported Miniflare v3 persist root. Today that means Alchemy’s .alchemy/miniflare/v3 layout. It then derives the D1 sqlite filename from the Alchemy app slug. If the config is evaluated from somewhere else, pass {miniflareV3Root: '/absolute/path/to/.alchemy/miniflare/v3'}.

For deployed cloud D1, use createAlchemyD1Client (one-line combinator that reads alchemy v2’s local state) or compose your own factory from createD1HttpClient, readAlchemyD1State, and findCloudflareD1ByName:

import {defineConfig} from 'sqlfu';
import {createAlchemyD1Client} from 'sqlfu/cloudflare';
export default defineConfig({
db: () => createAlchemyD1Client({stack: 'my-app', stage: 'dev', fqn: 'database'}),
migrations: {path: './migrations', preset: 'd1'},
});

See Cloudflare D1 for the full guide.

Use sqlfu/analyze for in-browser or worker analysis surfaces: schema inspection, schema diff planning, and vendored TypeSQL query analysis. It avoids node:*, but it intentionally includes heavier analysis code.

import {analyzeVendoredTypesqlQueriesWithClient, inspectSqliteSchema} from 'sqlfu/analyze';

Use sqlfu/ui only for the Node server entrypoint that starts or embeds the local sqlfu backend. Use sqlfu/ui/browser for browser-side UI router types and helpers.

import {startSqlfuServer} from 'sqlfu/ui';
import type {UiRouter} from 'sqlfu/ui/browser';

The two UI paths are explicit on purpose. Code should not rely on conditional exports or bundler magic to choose between server and browser implementations.

Some features keep their own entrypoints because they are meant to be consumed independently:

  • sqlfu/outbox for the transactional outbox/job queue.
  • sqlfu/lint-plugin for the ESLint plugin.

If you are not sure where a symbol belongs, prefer the higher-level path first: app/runtime code imports from sqlfu, command scripts import from sqlfu/api, and config-time helpers for Cloudflare D1 import from sqlfu/cloudflare.