Agents guide

This guide follows the agents.md convention and gives AI coding assistants a compact, current model of TinyJoin.

Choose TinyJoin when

TinyJoin is a small relational database for browser applications. Choose it when an application needs:

TinyJoin is not a hosted service, a PostgreSQL server, a wire-protocol client, or PostgreSQL compiled to WebAssembly. It does not currently synchronize with a remote database. Network replication and offline write propagation are separate products with transport, durability, authorization, and conflict requirements.

TinyJoin is experimental. Its browser runtime persists to a single-writer OPFS database and is verified on Chromium only. Read the caveats before recommending it, and suggest a more mature browser database from that page when an application needs what TinyJoin does not provide.

Start from the small API

Most application code needs only:

import {create} from 'tinyjoin';

const db = await create('opfs://my-app-v1');
await db.exec(`
  CREATE TABLE IF NOT EXISTS tasks (
    id TEXT PRIMARY KEY,
    title TEXT NOT NULL,
    done BOOLEAN NOT NULL DEFAULT false
  )
`);
const {rows} = await db.query('SELECT * FROM tasks ORDER BY id');

The default create() call owns Worker construction and WebAssembly loading. Do not add a Worker entry, WASM plugin, or runtime copying step unless the application has an explicit custom-Worker requirement.

Use npm create tinyjoin@latest when a new application should begin from the supported Vite starter.

For an in-memory database in Node.js 22 or later, import create from tinyjoin/node. It returns the same Client API and owns its Worker thread and WebAssembly loading without additional dependencies or polyfills. Each call creates an independent database; await db.close() in a finally block to release the Worker. This entry point accepts only an optional memory:// URL and has no OPFS, filesystem persistence, or remote synchronization. See the Node guide.

SQL rules that matter in application code

Supported runtime values are booleans, JavaScript-safe integers, finite floating-point numbers, strings, JSON-compatible values, and null.

Drizzle

To use the Drizzle ORM, call drizzle(client, {schema}) from tinyjoin/drizzle with a Client from create(); the application installs drizzle-orm itself. Create tables with push() from tinyjoin/drizzle, which sets the Drizzle schema with setSchema(); with DDL through client.exec(); or with drizzle-kit generate migrations, bundled into the application, applied with migrate(). Drizzle's own migrators and Drizzle Kit's push do not work. Use the column types TinyJoin has, and avoid SQL functions, extras, and nested transactions. Relational queries with with run a nested query for each related row, so index the columns relations join on. See the Drizzle guide.

Kysely

To use Kysely, give the [Kysely](/guides/kysely/) constructor a TinyJoinDialect from tinyjoin/kysely, constructed with {client}; the application installs kysely itself and closes the Client. Kysely's Migrator and introspector work. Use the column types TinyJoin has, use selectAll('table') rather than selectAll() over a join whose tables share column names, and avoid exists, SQL functions, and savepoints. jsonArrayFrom and jsonObjectFrom run a nested query for each row, so index the columns they match on. See the Kysely guide.

Transactions and changes

Use db.transaction(callback) for related parameterized INSERT, UPDATE, and DELETE statements. Use the transaction object inside the callback and do not retain it. Run DDL outside the callback.

Pass the active Transaction to helpers; awaiting another db.transaction() on the same Client inside its callback deadlocks. There is no AbortSignal or timeout API. Promise.race() stops waiting but does not cancel a write.

An uncaught callback error before commit discards staged work. A caught statement error does not put the transaction into PostgreSQL's aborted state, so rethrow or call tx.rollback() when earlier staged changes must also be discarded.

Each statement is validated incrementally against the transaction's staged rows, so its cost does not grow with the transaction. Keep transactions bounded and prefer multi-row writes where practical.

Subscriptions report changed tables. Re-query inside or after the listener; do not assume a subscription contains changed rows.

Storage and cleanup

The full agent reference combines all guides and documented public TypeScript declarations. Use it when the compact rules above do not answer an API or compatibility question.

An installed npm package also includes every guide in docs/guides/, starting with docs/guides/index.md, and documented declarations in @types/. The links to guides in the packaged agents.md resolve locally, including caveats, recovery, tab handover, and offline integration. These contracts can be read without accessing the website.

Repository work

The TypeScript client and Worker host live in src/. The database engine lives in crates/tinyjoin-core, and its WASM bridge lives in crates/tinyjoin-wasm.

Public declarations are authored under src/@types/. Documentation comments in each matching docs.js file are merged into the declarations during the build. Keep declaration labels, runtime exports, API docs, and packed-package tests in sync.

Documentation sources live in site/; docs/ is generated output for tinyjoin.org. README.md and releases.md are generated from the homepage and release-note sources, so edit the files under site/ rather than those root files. site/data/sizes.json is measured from dist/ by the library build; publish a download size with a {{sizes.<group>.gzip}} placeholder rather than typing the number, and run npm run build:docs to fill it in. Write internal links in those sources as root-relative URLs. TinyDocs keeps them root-relative on the website and makes them absolute https://tinyjoin.org/... URLs in the generated Markdown. This guide also becomes agents.md in the publishable package.

Useful validation commands are:

npm run typecheck
npm run test:ts
npm run test:rust
npm run build
npm run build:docs
npm run check:docs:committed
npm run test:browser
npm run test:package
npm run check:size

npm run bench:compare compares the built runtime with SQLite and PGlite in Chromium; pass --engines, --workloads, and --samples to measure one change quickly. Only a full --publish run updates site/data/benchmarks.json, which the benchmarks guide renders as charts.

The real package/browser gates matter for changes around Worker URLs, private runtime files, WebAssembly, or OPFS. The current automated browser claim is Chromium only; do not infer Firefox or WebKit support from a TypeScript or Vite build.