Skip to content
tinyplexPublic

About

A tiny, worker-first relational database for browser apps.

Resources

Stars

24 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

433 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

The smallest, fastest relational database for your web app.

Smaller than SQLite and PGlite, and faster than both. Written in Rust, supporting PostgreSQL-shaped SQL, and running locally, away from the browser's main UI thread.

Get started Try the demos Read the API

Small enough not to notice

TinyJoin is only 354 KiB to download, compressed, smaller than SQLite (457 KiB) and way smaller than PGlite (5.6 MiB).

Of that, only 8 KiB runs on the UI thread!

Componentgzip
Main JS8 KiB
Worker JS17 KiB
Engine WASM329 KiB
Everything354 KiB

Faster than SQLite and PGlite

We test each engine running the same SQL in a browser Worker, with its database on OPFS. TinyJoin is the fastest in all 20 of the workloads we measure, from opening a database to reading, writing, joining, and committing.

These charts show the median of 9 runs, fastest first. Brackets span the fastest to the slowest run. The Benchmarks guide has every result in detail, and how to run them yourself.

Median timeTinyJoinSQLitePGlite
Open a new database55.8 ms83.2 ms1.12 s
Read 10,000 rows6.90 ms45.3 ms44.7 ms
1,000 updates by key22.0 ms24.2 ms127 ms

Your first TinyJoin app

Scaffold a complete local todo app in JS or TS - and with its relational data saved in TinyJoin across reloads - in less than 60s. Write its queries in SQL, or with the Drizzle ORM or the Kysely query builder.

> npm create tinyjoin@latest

πŸŽ‰ Welcome to TinyJoin!

βœ” Project name: … my-tinyjoin-app
βœ” Language: β€Ί TypeScript
βœ” Queries: β€Ί Drizzle ORM
βœ” Todo data: β€Ί Save data across reloads (recommended)
βœ” Install dependencies and start the app? … yes

πŸ“¦ Creating your project...

Or add it to an existing app

Installing TinyJoin is simple. There are no runtime dependencies, no servers to run, no accounts to create, and no native toolchains.

npm install tinyjoin

Open a database

create() owns Worker construction and WebAssembly loading, and resolves once the database is ready. Use opfs://[name] when data should survive reloads in the same browser, or call it with no argument for ephemeral in-memory storage.

import {create} from 'tinyjoin';

const db = await create('opfs://my-app');

Set up a schema

exec() runs a parameter-free script as one implicit transaction, so schema setup stays a single call that is safe to run again on every load.

await db.exec(`
  CREATE TABLE IF NOT EXISTS tasks (
    id TEXT PRIMARY KEY,
    title TEXT NOT NULL,
    done BOOLEAN NOT NULL DEFAULT false
  )
`);

Write with parameters

query() runs one read or write statement. Application values go in the $n array and never reach the SQL text.

const id = crypto.randomUUID();

await db.query(
  'INSERT INTO tasks (id, title) VALUES ($1, $2)',
  [id, 'Try TinyJoin'],
);

Or tag a template

The sql tagged template is the same parameterized call in a shorter form. It takes values only, so there is no way to interpolate raw SQL by accident.

const title = 'Written with a tag';

await db.sql`
  INSERT INTO tasks (id, title)
  VALUES (${crypto.randomUUID()}, ${title})
`;

Read rows back

Results use the familiar rows, fields, affectedRows, command, and rowCount shape. The row type is yours to declare, and TinyJoin adds a database revision and the tables a statement touched.

type Task = {id: string; title: string};

const {rows} = await db.query<Task>(
  'SELECT id, title FROM tasks ORDER BY title',
);

transaction() stages its writes and publishes them once. Reads inside the callback see the staged rows, and letting an error escape rolls the whole thing back.

await db.transaction(async (tx) => {
  await tx.query(
    'UPDATE tasks SET done = $1 WHERE id = $2',
    [true, id],
  );
  await tx.query(
    'DELETE FROM tasks WHERE done = $1',
    [true],
  );
});

Re-run without re-parsing

prepare() retains one parsed statement in the Worker. It resolves tables and types against the current catalog on every execution, so a compatible schema change does not make the handle stale.

const openTasks = await db.prepare<{
  id: string;
  title: string;
}>('SELECT id, title FROM tasks WHERE done = $1');

const {rows} = await openTasks.execute([false]);

Or bring a query builder

The Drizzle ORM and the Kysely query builder run on the same Client. push() makes the database hold a Drizzle schema as the app starts, in one atomic change that keeps every row. And npm create tinyjoin@latest can start a new app with either.

import {drizzle, push} from 'tinyjoin/drizzle';
import {eq} from 'drizzle-orm';
import * as schema from './schema';

const orm = drizzle(db, {schema});
await push(orm, schema);

const open = await orm
  .select()
  .from(schema.tasks)
  .where(eq(schema.tasks.done, false));

Let the view follow the data

subscribe() reports which tables changed, so a UI can re-query instead of being told what to redraw by every writer. close() then releases statements, storage, and the Worker.

const unsubscribe = db.subscribe(
  {tables: ['tasks']},
  () => render(),
);

// Later
unsubscribe();
await db.close();

Go deeper when you need to

Local by design

TinyJoin keeps your database in the browser. Your app reads and writes data locally, without waiting for a database server. Keep data in memory or save it across reloads with OPFS; tabs opening the same persistent database share access automatically.

Your app can keep working when the network drops. Apps created with the starter can also reopen offline once their production build has been cached.

An important warning

TinyJoin is experimental and verified on Chromium only, with a bounded SQL dialect and no built-in remote synchronization (yet!). Browser storage can be lost, so use it for data you can reconstruct. Read the caveats and SQL compatibility guide to check whether this project currently fits your app. We're working on it though, so keep checking back.


Meet the family

TinyJoin is one of a group of small libraries that make rich client and local-first apps easier to build. Take a look at the others!

  • TinyBase A reactive data store with persistence and synchronization.
  • Synclets An open, storage-agnostic sync engine development kit.
  • TinyWidgets A collection of tiny, reusable UI components.
  • TinyTick A tiny but very useful task orchestrator.

About

A tiny, worker-first relational database for browser apps.

Resources

Stars

24 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages