API
The Essentials
create
The create function opens a TinyJoin database in a dedicated Worker.
create(): Promise<Client>| returns | Promise<Client> | A Promise resolving to a |
|---|
It constructs a Client and resolves once that Client is ready, so a database that cannot be opened rejects here. The Client constructor returns before then, for code that needs the waitReady lifecycle.
The overload of this function with no arguments creates an ephemeral in-memory database using the packaged Worker. Its data is lost when the Client closes or the page reloads.
Example
import {create} from 'tinyjoin';
const db = await create();
await db.exec(`
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL
)
`);
const {rows} = await db.query('SELECT * FROM tasks ORDER BY id');
await db.close();
The overload of this function with one argument, an options object, opens the database that its ClientOptions describe.
create(options: ClientOptions): Promise<Client>| Type | Description | |
|---|---|---|
options | ClientOptions | Storage and Worker configuration. |
| returns | Promise<Client> | A Promise resolving to a |
Use the options object to select storage or provide a custom Worker. Omitting its dataDir property selects an ephemeral in-memory database.
Example
import {create} from 'tinyjoin';
const db = await create({dataDir: 'memory://'});
await db.close();
The overload of this function with a storage URL as its first argument, and optionally options as its second, opens a database at that URL.
create(
dataDir: string | undefined,
options?: ClientOptions,
): Promise<Client>| Type | Description | |
|---|---|---|
dataDir | string | undefined | The |
options? | ClientOptions | Optional Worker configuration. |
| returns | Promise<Client> | A Promise resolving to a |
Use a stable opfs://name to persist data in the same browser, or memory:// for an ephemeral database. When providing the second argument, supply a storage URL and leave options.dataDir unset.
Example
import {create} from 'tinyjoin';
const db = await create('opfs://my-app');
await db.close();
exec
The exec method runs one or more parameter-free statements as one implicit transaction.
exec(
sql: string,
options?: QueryOptions,
): Promise<Results<Row>[]>| Type | Description | |
|---|---|---|
sql | string | |
options? | QueryOptions | |
| returns | Promise<Results<Row>[]> | A Promise resolving to one result per statement, in script order. |
Example
import {create} from 'tinyjoin';
const db = await create();
const results = await db.exec(`
CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL);
INSERT INTO tasks (id, title) VALUES ('a', 'Write docs');
SELECT * FROM tasks;
`);
console.log(results.length);
// -> 3
console.log(results[2].rows);
// -> [{id: 'a', title: 'Write docs'}]
await db.close();
query
The query method executes exactly one parameterized SQL statement.
query<RowType>(
sql: string,
params?: JsonValue[],
options?: QueryOptions,
): Promise<Results<RowType>>| Type | Description | |
|---|---|---|
sql | string | A statement in the documented TinyJoin SQL subset. |
params? | JsonValue[] | JSON-compatible values for |
options? | QueryOptions | Result-shape options. |
| returns | Promise<Results<RowType>> | A Promise resolving to the statement results. |
Examples
import {create} from 'tinyjoin';
const db = await create();
await db.query(
'CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL)',
);
const inserted = await db.query(
'INSERT INTO tasks (id, title) VALUES ($1, $2)',
['a', 'Write docs'],
);
console.log(inserted.affectedRows);
// -> 1
const {rows} = await db.query<{id: string; title: string}>(
'SELECT * FROM tasks WHERE id = $1',
['a'],
);
console.log(rows);
// -> [{id: 'a', title: 'Write docs'}]
await db.close();
import {create} from 'tinyjoin';
const db = await create();
await db.exec(`
CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL);
INSERT INTO tasks (id, title) VALUES ('a', 'Write docs');
`);
// Rows come back as arrays, in the order of the fields, on request.
const {fields, rows} = await db.query('SELECT id, title FROM tasks', [], {
rowMode: 'array',
});
console.log(fields.map(({name}) => name));
// -> ['id', 'title']
console.log(rows);
// -> [['a', 'Write docs']]
await db.close();
sql
The sql method is a parameterizing tagged-template form of query.
sql<RowType>(
strings: TemplateStringsArray,
...params: JsonValue[],
): Promise<Results<RowType>>| Type | Description | |
|---|---|---|
strings | TemplateStringsArray | |
...params | JsonValue[] | |
| returns | Promise<Results<RowType>> | A Promise resolving to the statement results. |
Interpolated values become $n parameters. It does not interpolate raw identifiers or SQL fragments.
Example
import {create} from 'tinyjoin';
const db = await create();
await db.sql`
CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL)
`;
const id = 'a';
const title = 'Write docs';
await db.sql`INSERT INTO tasks (id, title) VALUES (${id}, ${title})`;
const {rows} = await db.sql<{title: string}>`
SELECT title FROM tasks WHERE id = ${id}
`;
console.log(rows);
// -> [{title: 'Write docs'}]
await db.close();
transaction
The transaction method stages row mutations and publishes them together when the callback succeeds, unless it explicitly rolls back.
transaction<Result>(callback: (transaction: Transaction) => Result | Promise<Result>): Promise<Result>| Type | Description | |
|---|---|---|
callback | (transaction: Transaction) => Result | Promise<Result> | |
| returns | Promise<Result> | A Promise resolving to the callback's result after the transaction commits or explicitly rolls back. |
Transaction calls on this Client queue in order. Do not await another transaction on this Client inside the callback; pass its Transaction to helpers instead. Use that object for all SQL in the callback and prepare handles beforehand. Other Clients for the same OPFS name wait for the whole callback to finish. Do not await work on those Clients from inside this callback. An uncaught callback error before commit discards staged work. A caught statement error leaves earlier writes staged unless rollback is called.
There is no cancellation or timeout option. Promise.race only stops waiting, and queued work can still commit. Rejection during commit can leave its outcome uncertain; see the recovery guide before replaying a failed write.
Example
import {create} from 'tinyjoin';
const db = await create();
await db.exec(`
CREATE TABLE accounts (id TEXT PRIMARY KEY, balance INTEGER NOT NULL);
INSERT INTO accounts (id, balance) VALUES ('a', 100);
INSERT INTO accounts (id, balance) VALUES ('b', 0);
`);
// Both updates commit together, or neither does.
const moved = await db.transaction(async (tx) => {
await tx.query(
'UPDATE accounts SET balance = balance - $1 WHERE id = $2',
[30, 'a'],
);
await tx.query(
'UPDATE accounts SET balance = balance + $1 WHERE id = $2',
[30, 'b'],
);
return 30;
});
console.log(moved);
// -> 30
const {rows} = await db.query('SELECT * FROM accounts ORDER BY id');
console.log(rows);
// -> [{id: 'a', balance: 70}, {id: 'b', balance: 30}]
await db.close();
tinyjoin
The tinyjoin module provides a small PostgreSQL-shaped relational database that runs in a dedicated browser Worker and stores data in memory or OPFS.
Start with the create function. It constructs the Worker and loads the WebAssembly engine, so applications do not need to manage either directly.
Classes
Lifecycle classes
Client
The Client class represents one open TinyJoin database and its dedicated Worker.
Prefer the async create function so initialization failures are reported before the Client is returned.
Constructors
constructor
The constructor begins opening a Client immediately, and returns before the Client is ready.
new Client(options?: ClientOptions): Client| Type | Description | |
|---|---|---|
options? | ClientOptions | |
| returns | Client |
Prefer the create function, which constructs a Client and resolves once it is ready, unless code specifically needs the waitReady lifecycle.
Example
import {Client} from 'tinyjoin';
const db = new Client({dataDir: 'opfs://my-app-v1'});
console.log(db.ready);
// -> false
await db.waitReady;
console.log(db.ready);
// -> true
await db.close();
Methods
Lifecycle methods
check
The check method reads every row and index entry of the committed database, and every page that holds them, and resolves when all of them are sound.
check(): Promise<void>Opening a database checks only its catalog, and each page and row is checked as a statement reads it, so rows that are never read, and whether each index still matches its table, are checked only here. The check takes time in proportion to the database, and statements from every Client of the same OPFS name wait while it runs. Call it outside a transaction.
TinyJoin cannot yet repair a database that fails its check.
Example
import {ClientError, create} from 'tinyjoin';
const db = await create('opfs://my-app-v1');
try {
await db.check();
console.log('Every row and index entry is sound.');
} catch (error) {
if (!(error instanceof ClientError)) {
throw error;
}
// Nothing repairs a database that fails its check, so the application
// restores its own backup into a new OPFS name.
console.error(error.code);
} finally {
await db.close();
}
close
The close method detaches this Client and releases its prepared statements and Worker. Other Clients for the same OPFS name stay connected; ownership transfers automatically when necessary. Repeated calls share the same asynchronous cleanup. A closed Client cannot resume; create a new one and recreate its prepared statements and subscriptions. Closing is not a cancellation or rollback guarantee for a write already in flight.
close(): Promise<void>Example
import {create} from 'tinyjoin';
const db = await create('opfs://my-app-v1');
try {
await db.exec(`
CREATE TABLE IF NOT EXISTS tasks (id TEXT PRIMARY KEY)
`);
} finally {
// Releases the Worker and its hold on the storage, whatever happened.
await db.close();
}
console.log(db.closed);
// -> true
getSchema
The getSchema method reads the database's tables, with their columns, primary keys, and indexes, as the engine's catalog holds them.
getSchema(): Promise<Schema>Tables and each table's indexes are in name order, and columns and key columns are in the order the table declares them. Call it outside a transaction.
Example
import {create} from 'tinyjoin';
const db = await create();
await db.exec(`
CREATE TABLE tasks (id TEXT PRIMARY KEY, done BOOLEAN NOT NULL DEFAULT false);
CREATE INDEX tasks_done ON tasks (done);
`);
const {tables} = await db.getSchema();
console.log(tables[0].columns[1]);
// -> {name: 'done', type: 'boolean', nullable: false, default: false}
await db.close();
setSchema
The setSchema method makes the database's tables, columns, primary keys, and indexes match a Schema, as getSchema returns one, in one change that commits whole or not at all.
setSchema(
schema: Schema,
options?: SetSchemaOptions,
): Promise<boolean>| Type | Description | |
|---|---|---|
schema | Schema | The schema to make the database's. |
options? | SetSchemaOptions | Whether to drop what the schema leaves out. |
| returns | Promise<boolean> | A Promise resolving to true if the database changed, or false if it already had the schema. |
It creates the tables and columns the schema has and the database lacks, renames a table or column from the name its renamedFrom gives while the database still has that name, and changes a column's default, nullability, or VARCHAR length to the schema's. Each table's indexes and foreign keys become exactly the schema's, with keys added once every table they reference exists. Tables and columns the schema leaves out stay, unless the options' drop property is true. Rows keep their values, as ALTER TABLE keeps them: a change no statement could make, to a primary key or a column's runtime type, is refused, and so is one that a row prevents, such as NOT NULL where a row holds NULL.
A schema the database already has changes nothing, so every tab of an application can call setSchema as it starts. A schema whose version is lower than the database's is refused with SCHEMA_OUTDATED, so that a tab still running an older copy of the application cannot undo a newer one's changes; a higher version is recorded. Call it outside a transaction.
Example
import {create} from 'tinyjoin';
const db = await create();
await db.setSchema({
version: 1,
tables: [
{
name: 'tasks',
columns: [
{name: 'id', type: 'text', nullable: false},
{name: 'title', type: 'text', nullable: false},
{name: 'done', type: 'boolean', nullable: false, default: false},
],
primaryKey: ['id'],
indexes: [{name: 'tasks_done', columns: ['done'], unique: false}],
foreignKeys: [],
},
],
});
await db.close();
SQL methods
exec
The exec method runs one or more parameter-free statements as one implicit transaction.
exec(
sql: string,
options?: QueryOptions,
): Promise<Results<Row>[]>| Type | Description | |
|---|---|---|
sql | string | |
options? | QueryOptions | |
| returns | Promise<Results<Row>[]> | A Promise resolving to one result per statement, in script order. |
Example
import {create} from 'tinyjoin';
const db = await create();
const results = await db.exec(`
CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL);
INSERT INTO tasks (id, title) VALUES ('a', 'Write docs');
SELECT * FROM tasks;
`);
console.log(results.length);
// -> 3
console.log(results[2].rows);
// -> [{id: 'a', title: 'Write docs'}]
await db.close();
prepare
The prepare method parses and retains one reusable read or row-mutation statement in the Worker.
prepare<RowType>(sql: string): Promise<PreparedStatement<RowType>>| Type | Description | |
|---|---|---|
sql | string | |
| returns | Promise<PreparedStatement<RowType>> | A Promise resolving to a reusable |
Example
import {create} from 'tinyjoin';
const db = await create();
await db.exec(`
CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL);
INSERT INTO tasks (id, title) VALUES ('a', 'Write docs');
INSERT INTO tasks (id, title) VALUES ('b', 'Ship release');
`);
const titleOf = await db.prepare<{title: string}>(
'SELECT title FROM tasks WHERE id = $1',
);
for (const id of ['a', 'b']) {
const {rows} = await titleOf.execute([id]);
console.log(rows[0].title);
}
// -> 'Write docs'
// -> 'Ship release'
await titleOf.close();
await db.close();
query
The query method executes exactly one parameterized SQL statement.
query<RowType>(
sql: string,
params?: JsonValue[],
options?: QueryOptions,
): Promise<Results<RowType>>| Type | Description | |
|---|---|---|
sql | string | A statement in the documented TinyJoin SQL subset. |
params? | JsonValue[] | JSON-compatible values for |
options? | QueryOptions | Result-shape options. |
| returns | Promise<Results<RowType>> | A Promise resolving to the statement results. |
Examples
import {create} from 'tinyjoin';
const db = await create();
await db.query(
'CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL)',
);
const inserted = await db.query(
'INSERT INTO tasks (id, title) VALUES ($1, $2)',
['a', 'Write docs'],
);
console.log(inserted.affectedRows);
// -> 1
const {rows} = await db.query<{id: string; title: string}>(
'SELECT * FROM tasks WHERE id = $1',
['a'],
);
console.log(rows);
// -> [{id: 'a', title: 'Write docs'}]
await db.close();
import {create} from 'tinyjoin';
const db = await create();
await db.exec(`
CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL);
INSERT INTO tasks (id, title) VALUES ('a', 'Write docs');
`);
// Rows come back as arrays, in the order of the fields, on request.
const {fields, rows} = await db.query('SELECT id, title FROM tasks', [], {
rowMode: 'array',
});
console.log(fields.map(({name}) => name));
// -> ['id', 'title']
console.log(rows);
// -> [['a', 'Write docs']]
await db.close();
sql
The sql method is a parameterizing tagged-template form of query.
sql<RowType>(
strings: TemplateStringsArray,
...params: JsonValue[],
): Promise<Results<RowType>>| Type | Description | |
|---|---|---|
strings | TemplateStringsArray | |
...params | JsonValue[] | |
| returns | Promise<Results<RowType>> | A Promise resolving to the statement results. |
Interpolated values become $n parameters. It does not interpolate raw identifiers or SQL fragments.
Example
import {create} from 'tinyjoin';
const db = await create();
await db.sql`
CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL)
`;
const id = 'a';
const title = 'Write docs';
await db.sql`INSERT INTO tasks (id, title) VALUES (${id}, ${title})`;
const {rows} = await db.sql<{title: string}>`
SELECT title FROM tasks WHERE id = ${id}
`;
console.log(rows);
// -> [{title: 'Write docs'}]
await db.close();
Transactions methods
transaction
The transaction method stages row mutations and publishes them together when the callback succeeds, unless it explicitly rolls back.
transaction<Result>(callback: (transaction: Transaction) => Result | Promise<Result>): Promise<Result>| Type | Description | |
|---|---|---|
callback | (transaction: Transaction) => Result | Promise<Result> | |
| returns | Promise<Result> | A Promise resolving to the callback's result after the transaction commits or explicitly rolls back. |
Transaction calls on this Client queue in order. Do not await another transaction on this Client inside the callback; pass its Transaction to helpers instead. Use that object for all SQL in the callback and prepare handles beforehand. Other Clients for the same OPFS name wait for the whole callback to finish. Do not await work on those Clients from inside this callback. An uncaught callback error before commit discards staged work. A caught statement error leaves earlier writes staged unless rollback is called.
There is no cancellation or timeout option. Promise.race only stops waiting, and queued work can still commit. Rejection during commit can leave its outcome uncertain; see the recovery guide before replaying a failed write.
Example
import {create} from 'tinyjoin';
const db = await create();
await db.exec(`
CREATE TABLE accounts (id TEXT PRIMARY KEY, balance INTEGER NOT NULL);
INSERT INTO accounts (id, balance) VALUES ('a', 100);
INSERT INTO accounts (id, balance) VALUES ('b', 0);
`);
// Both updates commit together, or neither does.
const moved = await db.transaction(async (tx) => {
await tx.query(
'UPDATE accounts SET balance = balance - $1 WHERE id = $2',
[30, 'a'],
);
await tx.query(
'UPDATE accounts SET balance = balance + $1 WHERE id = $2',
[30, 'b'],
);
return 30;
});
console.log(moved);
// -> 30
const {rows} = await db.query('SELECT * FROM accounts ORDER BY id');
console.log(rows);
// -> [{id: 'a', balance: 70}, {id: 'b', balance: 30}]
await db.close();
Subscriptions methods
getRevision
The getRevision method returns the newest database revision observed by this Client.
getRevision(): number| returns | number | The newest database revision observed by this |
|---|
Example
import {create} from 'tinyjoin';
const db = await create();
await db.exec(`
CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL)
`);
const before = db.getRevision();
const {revision} = await db.query(
'INSERT INTO tasks (id, title) VALUES ($1, $2)',
['a', 'Write docs'],
);
console.log(revision > before);
// -> true
console.log(db.getRevision() == revision);
// -> true
await db.close();
subscribe
The subscribe method listens for committed table changes and returns an unsubscribe function. OPFS Clients receive changes from every connected tab. After handover or page restoration, reset events notify every subscriber to re-query even though tables is empty.
subscribe(
options: SubscriptionOptions,
listener: (event: TablesChangedEvent) => void,
): () => void| Type | Description | |
|---|---|---|
options | SubscriptionOptions | |
listener | (event: TablesChangedEvent) => void | |
| returns | () => void | A function that removes this subscription when called. |
Example
import {create} from 'tinyjoin';
const db = await create();
await db.exec(`
CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL)
`);
const unsubscribe = db.subscribe({tables: ['tasks']}, async ({tables}) => {
// The event names the tables that changed. Query again for their rows.
const {rows} = await db.query('SELECT * FROM tasks ORDER BY id');
console.log(tables, rows);
// -> ['tasks'] [{id: 'a', title: 'Write docs'}]
unsubscribe();
await db.close();
});
await db.query('INSERT INTO tasks (id, title) VALUES ($1, $2)', [
'a',
'Write docs',
]);
Properties
closed
The closed property indicates that Client cleanup has completed. A terminal Worker failure does not itself complete Client cleanup. Call close even when subsequent operations reject with WORKER_TERMINATED.
booleanready
The ready property indicates that initialization finished and closing has not started. It is not a health check: it can remain true after a terminal Worker error while operations reject. Close and reopen the Client and reconcile writes; see the lifecycle guide. Temporary OPFS owner handover reconnects the same Client automatically.
booleanwaitReady
The waitReady property resolves when the Worker and database are ready. It settles once for initialization; it does not track later Worker health or temporary OPFS owner handover.
Promise<void>Errors classes
ClientError
The ClientError class extends JavaScript Error with a validated error returned by the TinyJoin Worker.
RECOVERY_REQUIRED, STORAGE_COMMIT_OUTCOME_UNKNOWN, and STORAGE_ENGINE_POISONED mean the engine must no longer be used. Stop work, close the Client, reopen the same OPFS name, and reconcile stored state using stable operation identifiers before replaying a write. A rejected operation may already have committed. See the recovery guide.
Constructors
constructor
The constructor creates an Error from its serialized Worker envelope.
new ClientError(error: SerializedError): ClientError| Type | Description | |
|---|---|---|
error | SerializedError | |
| returns | ClientError |
Example
import {ClientError} from 'tinyjoin';
const error = new ClientError({
code: 'INVALID_ARGUMENT',
message: 'A task needs a title.',
details: {column: 'title'},
});
console.log(error instanceof Error);
// -> true
console.log(error.code, error.details, error.retryable);
// -> 'INVALID_ARGUMENT' {column: 'title'} false
Properties
code
The code property identifies the TinyJoin error family.
stringdetails
The details property contains optional JSON-compatible context.
JsonValue | undefinedretryable
The retryable property indicates whether reopening or retrying may succeed. It is not a safe-replay guarantee. An uncertain write requires reopening and reconciliation even when application code wants to retry it.
booleanInterfaces
SQL interfaces
PreparedStatement
The PreparedStatement interface represents one parsed SELECT, INSERT, UPDATE, or DELETE statement retained by an open Client.
Methods
Lifecycle methods
close
The close method seals the handle and releases its Worker resources after already-started executions settle.
close(): Promise<void>Example
import {create} from 'tinyjoin';
const db = await create();
await db.exec(`
CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL)
`);
const select = await db.prepare('SELECT * FROM tasks WHERE id = $1');
await select.execute(['a']);
await select.close();
console.log(select.closed);
// -> true
await db.close();
SQL methods
execute
The execute method binds a complete parameter list and runs the statement.
execute(
params?: JsonValue[],
options?: QueryOptions,
): Promise<Results<RowType>>| Type | Description | |
|---|---|---|
params? | JsonValue[] | JSON-compatible values for |
options? | QueryOptions | Result-shape options. |
| returns | Promise<Results<RowType>> | A Promise resolving to the statement results. |
Example
import {create} from 'tinyjoin';
const db = await create();
await db.exec(`
CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL)
`);
const insert = await db.prepare(
'INSERT INTO tasks (id, title) VALUES ($1, $2)',
);
await insert.execute(['a', 'Write docs']);
await insert.execute(['b', 'Ship release']);
const {rows} = await db.query('SELECT title FROM tasks ORDER BY id');
console.log(rows);
// -> [{title: 'Write docs'}, {title: 'Ship release'}]
await insert.close();
await db.close();
Properties
closed
The closed property indicates whether the handle has been sealed. It is not a Worker health check. A terminal Worker failure can leave it false while execute rejects. Closing the Client seals its handles.
booleanTransactions interfaces
Transaction
The Transaction interface runs reads and row mutations against one isolated staged database inside a Client.transaction callback.
Do not retain this object after its callback completes. Run schema DDL in a standalone Client.query call or Client.exec script. Pass this object to helpers instead of awaiting another transaction on the same Client, which would queue behind the active callback and deadlock.
Methods
exec
The exec method runs a parameter-free DML and read script as one savepoint.
exec(
sql: string,
options?: QueryOptions,
): Promise<Results<Row>[]>| Type | Description | |
|---|---|---|
sql | string | |
options? | QueryOptions | |
| returns | Promise<Results<Row>[]> | A Promise resolving to one result per statement, in script order. |
Example
import {create} from 'tinyjoin';
const db = await create();
await db.exec(`
CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL)
`);
await db.transaction(async (tx) => {
const results = await tx.exec(`
INSERT INTO tasks (id, title) VALUES ('a', 'Write docs');
INSERT INTO tasks (id, title) VALUES ('b', 'Ship release');
SELECT id FROM tasks ORDER BY id;
`);
console.log(results[2].rows);
// -> [{id: 'a'}, {id: 'b'}]
});
await db.close();
execute
The execute method runs a prepared statement owned by the same Client.
execute<RowType>(
statement: PreparedStatement<RowType>,
params?: JsonValue[],
options?: QueryOptions,
): Promise<Results<RowType>>| Type | Description | |
|---|---|---|
statement | PreparedStatement<RowType> | |
params? | JsonValue[] | |
options? | QueryOptions | |
| returns | Promise<Results<RowType>> | A Promise resolving to the statement results. |
Example
import {create} from 'tinyjoin';
const db = await create();
await db.exec(`
CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL)
`);
// Prepare the statement before the transaction, and run it inside.
const insert = await db.prepare(
'INSERT INTO tasks (id, title) VALUES ($1, $2)',
);
await db.transaction(async (tx) => {
await tx.execute(insert, ['a', 'Write docs']);
await tx.execute(insert, ['b', 'Ship release']);
});
const {rows} = await db.query('SELECT id FROM tasks ORDER BY id');
console.log(rows);
// -> [{id: 'a'}, {id: 'b'}]
await insert.close();
await db.close();
query
The query method runs one parameterized statement against staged data.
query<RowType>(
sql: string,
params?: JsonValue[],
options?: QueryOptions,
): Promise<Results<RowType>>| Type | Description | |
|---|---|---|
sql | string | |
params? | JsonValue[] | |
options? | QueryOptions | |
| returns | Promise<Results<RowType>> | A Promise resolving to the statement results. |
Example
import {create} from 'tinyjoin';
const db = await create();
await db.exec(`
CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL)
`);
await db.transaction(async (tx) => {
await tx.query('INSERT INTO tasks (id, title) VALUES ($1, $2)', [
'a',
'Write docs',
]);
// A read inside the transaction sees the rows it has staged.
const {rows} = await tx.query('SELECT title FROM tasks WHERE id = $1', [
'a',
]);
console.log(rows);
// -> [{title: 'Write docs'}]
});
await db.close();
rollback
The rollback method explicitly discards the staged transaction.
rollback(): Promise<void>Example
import {create} from 'tinyjoin';
const db = await create();
await db.exec(`
CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL)
`);
await db.transaction(async (tx) => {
await tx.query('INSERT INTO tasks (id, title) VALUES ($1, $2)', [
'a',
'Write docs',
]);
await tx.rollback();
console.log(tx.closed);
// -> true
});
const {rows} = await db.query('SELECT * FROM tasks');
console.log(rows);
// -> []
await db.close();
sql
The sql method is a parameterizing tagged-template form of query.
sql<RowType>(
strings: TemplateStringsArray,
...params: JsonValue[],
): Promise<Results<RowType>>| Type | Description | |
|---|---|---|
strings | TemplateStringsArray | |
...params | JsonValue[] | |
| returns | Promise<Results<RowType>> | A Promise resolving to the statement results. |
Example
import {create} from 'tinyjoin';
const db = await create();
await db.exec(`
CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL)
`);
const tasks = [
{id: 'a', title: 'Write docs'},
{id: 'b', title: 'Ship release'},
];
await db.transaction(async (tx) => {
for (const {id, title} of tasks) {
await tx.sql`INSERT INTO tasks (id, title) VALUES (${id}, ${title})`;
}
});
const {rows} = await db.query('SELECT id FROM tasks ORDER BY id');
console.log(rows);
// -> [{id: 'a'}, {id: 'b'}]
await db.close();
Properties
closed
The closed property indicates that the transaction is sealed, including after an explicit rollback or after its callback finishes.
booleanQuery results interfaces
QueryOptions
The QueryOptions interface configures the shape of query results.
Only rowMode is supported. There is no AbortSignal or timeout option; Promise.race with a timer does not cancel database work.
rowMode
The rowMode property selects object rows by default, or arrays whose values follow the order of the fields property. Only array rows can return a SELECT whose output names repeat.
RowModeResultField
The ResultField interface describes one projected result column.
dataTypeID
The dataTypeID property contains the closest stable PostgreSQL OID for the TinyJoin runtime type.
numbername
The name property contains the projected column name or alias, or ?column? for an expression without an alias.
stringResults
The Results interface is returned by queries, prepared statements, and scripts.
It follows the familiar PGlite result shape and adds the observed database revision and the names of changed tables.
affectedRows
The affectedRows property reports the number of rows changed by a write.
numbercommand
The command property contains the executed statement family.
stringfields
The fields property describes the projected columns in result order.
ResultField[]keys
The keys property contains the primary keys the statement changed, keyed by table name.
ChangedKeysA table appears only when every key it changed fits the reporting bound of 1,000 keys per table. A table that changed more rows than that is absent, so an absent table means the rows that changed cannot be named, not that nothing changed. The tables property stays authoritative either way.
revision
The revision property contains the database revision observed by the statement.
numberrowCount
The rowCount property reports the number of rows returned or changed.
numberrows
The rows property contains the rows returned by the statement.
RowType[]tables
The tables property contains the tables changed by the statement.
string[]Subscriptions interfaces
SubscriptionOptions
The SubscriptionOptions interface filters invalidations by table name.
tables
The tables property limits delivery to invalidations that include at least one of these tables. Omit it to observe every table-change invalidation; adjacent changes may be coalesced.
string[]TablesChangedEvent
The TablesChangedEvent interface describes one committed table-level invalidation or a request to refresh after database-owner handover.
keys
The keys property contains the primary keys that changed, keyed by table name, for the tables whose complete set is known.
ChangedKeysUse it to refresh individual rows instead of re-reading a whole table. A table listed in tables but absent here changed more rows than the bound of 1,000 keys per table allows an event to name, or changed across a handover; re-query that table instead. It is always empty when reset is true.
reset
The reset property is true when database-owner handover or page restoration requires a re-query even though the changed tables are unknown. In that case tables is empty and every subscription is notified, including filtered ones. Normal committed-change events omit this property.
booleanrevision
The revision property contains the committed database revision.
numbertables
The tables property contains the names of changed tables. It is empty when reset is true, which means the subscriber should refresh its query anyway.
string[]Configuration interfaces
ClientOptions
The ClientOptions interface configures storage or an application-owned Worker.
The zero-boilerplate default constructs TinyJoin's packaged module Worker. Provide at most one of worker, workerFactory, or workerUrl.
worker
The worker property provides an already-created Worker-compatible object.
WorkerLikedataDir
The dataDir property selects memory or a named OPFS database.
stringworkerFactory
The workerFactory property creates an application-owned Worker lazily.
() => WorkerLikeworkerUrl
The workerUrl property identifies an application-owned module Worker.
string | URLErrors interfaces
SerializedError
The SerializedError interface is the stable error envelope sent by the Worker.
code
The code property identifies the TinyJoin error family.
stringdetails
The details property contains optional JSON-compatible context.
JsonValuemessage
The message property explains the failure.
stringretryable
The retryable property indicates whether reopening or retrying may succeed. It does not guarantee that replaying a write is safe or that the current Client remains usable. Reconcile uncertain writes after reopening.
booleanWorkers interfaces
WorkerLike
The WorkerLike interface is the minimal Worker surface accepted by a custom Client configuration.
Most applications should let create construct the packaged Worker instead.
Methods
addEventListener
The overload of this method for the message event type listens for Worker messages.
addEventListener(
type: "message",
listener: (event: (
Omit<MessageEvent<any>, "data"> &
{data: unknown}
)) => void,
): void| Type | Description | |
|---|---|---|
type | "message" | The |
listener | (event: ( Omit<MessageEvent<any>, "data"> & {data: unknown} )) => void | The callback receiving messages from the Worker. |
| returns | void | This has no return value. |
Example
import {create} from 'tinyjoin';
import type {WorkerLike} from 'tinyjoin';
const worker: WorkerLike = new Worker(
new URL('./worker.ts', import.meta.url),
{type: 'module'},
);
// Count the messages the Worker sends, beside the Client's own listener.
let messages = 0;
worker.addEventListener('message', () => {
messages++;
});
const db = await create({worker});
console.log(messages > 0);
// -> true
await db.close();
The overload of this method for the messageerror event type listens for message decoding failures.
addEventListener(
type: "messageerror",
listener: (event: (
Omit<MessageEvent<any>, "data"> &
{data: unknown}
)) => void,
): void| Type | Description | |
|---|---|---|
type | "messageerror" | The |
listener | (event: ( Omit<MessageEvent<any>, "data"> & {data: unknown} )) => void | The callback receiving message decoding failures. |
| returns | void | This has no return value. |
Example
import {create} from 'tinyjoin';
import type {WorkerLike} from 'tinyjoin';
const worker: WorkerLike = new Worker(
new URL('./worker.ts', import.meta.url),
{type: 'module'},
);
// Report a message from the Worker that the page could not decode.
worker.addEventListener('messageerror', () => {
console.error('A message from the Worker could not be decoded.');
});
const db = await create({worker});
await db.close();
The overload of this method for the error event type listens for Worker runtime failures.
addEventListener(
type: "error",
listener: (event: ErrorEvent) => void,
): void| Type | Description | |
|---|---|---|
type | "error" | The |
listener | (event: ErrorEvent) => void | The callback receiving Worker runtime failures. |
| returns | void | This has no return value. |
Example
import {create} from 'tinyjoin';
import type {WorkerLike} from 'tinyjoin';
const worker: WorkerLike = new Worker(
new URL('./worker.ts', import.meta.url),
{type: 'module'},
);
// Report an error the Worker did not catch, such as a script that failed
// to load.
worker.addEventListener('error', (event) => {
console.error('The TinyJoin Worker failed:', event.message);
});
const db = await create({worker});
await db.close();
postMessage
The postMessage method sends a request to the Worker.
postMessage(message: unknown): void| Type | Description | |
|---|---|---|
message | unknown | |
| returns | void | This has no return value. |
Example
import {create} from 'tinyjoin';
import type {WorkerLike} from 'tinyjoin';
// The application's worker.ts calls startWorker() from tinyjoin/worker.
const worker: WorkerLike = new Worker(
new URL('./worker.ts', import.meta.url),
{type: 'module'},
);
// Count the requests the Client posts to its Worker.
let requests = 0;
const post = worker.postMessage.bind(worker);
worker.postMessage = (message) => {
requests++;
post(message);
};
const db = await create({worker});
console.log(requests > 0);
// -> true
await db.close();
removeEventListener
The overload of this method for the message event type removes a message listener.
removeEventListener(
type: "message",
listener: (event: (
Omit<MessageEvent<any>, "data"> &
{data: unknown}
)) => void,
): void| Type | Description | |
|---|---|---|
type | "message" | The |
listener | (event: ( Omit<MessageEvent<any>, "data"> & {data: unknown} )) => void | The same callback passed to addEventListener. |
| returns | void | This has no return value. |
Example
import {create} from 'tinyjoin';
import type {WorkerLike} from 'tinyjoin';
const worker: WorkerLike = new Worker(
new URL('./worker.ts', import.meta.url),
{type: 'module'},
);
let messages = 0;
const countMessage = (): void => {
messages++;
};
worker.addEventListener('message', countMessage);
const db = await create({worker});
// Count only the messages it took to open the database.
worker.removeEventListener('message', countMessage);
console.log(messages > 0);
// -> true
await db.close();
The overload of this method for the messageerror event type removes a message-error listener.
removeEventListener(
type: "messageerror",
listener: (event: (
Omit<MessageEvent<any>, "data"> &
{data: unknown}
)) => void,
): void| Type | Description | |
|---|---|---|
type | "messageerror" | The |
listener | (event: ( Omit<MessageEvent<any>, "data"> & {data: unknown} )) => void | The same callback passed to addEventListener. |
| returns | void | This has no return value. |
Example
import {create} from 'tinyjoin';
import type {WorkerLike} from 'tinyjoin';
const worker: WorkerLike = new Worker(
new URL('./worker.ts', import.meta.url),
{type: 'module'},
);
const reportUndecoded = (): void => {
console.error('A message from the Worker could not be decoded.');
};
worker.addEventListener('messageerror', reportUndecoded);
const db = await create({worker});
await db.close();
// The Worker has stopped, so there is nothing left to report.
worker.removeEventListener('messageerror', reportUndecoded);
The overload of this method for the error event type removes a Worker error listener.
removeEventListener(
type: "error",
listener: (event: ErrorEvent) => void,
): void| Type | Description | |
|---|---|---|
type | "error" | The |
listener | (event: ErrorEvent) => void | The same callback passed to addEventListener. |
| returns | void | This has no return value. |
Example
import {create} from 'tinyjoin';
import type {WorkerLike} from 'tinyjoin';
const worker: WorkerLike = new Worker(
new URL('./worker.ts', import.meta.url),
{type: 'module'},
);
const reportFailure = (): void => {
console.error('The TinyJoin Worker failed.');
};
worker.addEventListener('error', reportFailure);
const db = await create({worker});
await db.close();
// The Worker has stopped, so there is nothing left to report.
worker.removeEventListener('error', reportFailure);
Properties
terminate
The terminate method stops an application-owned Worker when available.
() => voidSchema interfaces
ColumnSchema
The ColumnSchema interface describes one column of a table.
default
The default property contains the column's literal DEFAULT value, and is absent when the column declares none. A column declared DEFAULT NULL has a default of null.
JsonValuemaxLength
The maxLength property contains the most characters a VARCHAR(n) column holds, its n, and is absent for any other column.
numbername
The name property contains the column's name, as the table declares it.
stringnullable
The nullable property is false when the column is declared NOT NULL or is part of the primary key.
booleanrenamedFrom
The renamedFrom property names a column's former name, which setSchema renames the column from while the table still has a column of that name. getSchema never includes it.
stringtype
The type property contains the column's runtime ColumnType.
ColumnTypeForeignKeySchema
The ForeignKeySchema interface describes one foreign key of a table: columns whose values, unless one is NULL, must be those of a row of the table it references.
columns
The columns property contains the referencing columns, in key order.
string[]name
The name property contains the foreign key's name.
stringonDelete
The onDelete property says what deleting a referenced row does to the rows that reference it.
ForeignKeyActiononUpdate
The onUpdate property says what changing a referenced row's referenced values does to the rows that reference it.
ForeignKeyActionreferencedColumns
The referencedColumns property contains the referenced table's columns, by position in columns: its primary key, or a unique index's columns.
string[]references
The references property names the table the key references, which may be the table's own.
stringIndexSchema
The IndexSchema interface describes one secondary index on a table.
A primary key is not listed as an index; TableSchema's primaryKey holds it.
columns
The columns property contains the indexed column names, in index order.
string[]name
The name property contains the index's name.
stringunique
The unique property is true for an index created with CREATE UNIQUE INDEX.
booleanSchema
The Schema interface is returned by the getSchema method, and describes every table in the database. The setSchema method takes one to make the database's.
tables
The tables property describes the database's tables, in name order.
TableSchema[]version
The version property contains the version the application last gave the schema with setSchema, or zero. setSchema refuses a schema whose version is lower, as an older copy of the application would give it.
numberSetSchemaOptions
The SetSchemaOptions interface configures the setSchema method.
drop
The drop property, when true, makes setSchema drop each table, and each column of a table the schema keeps, that the schema leaves out, and their data with them. Otherwise setSchema leaves them as they are.
booleanTableSchema
The TableSchema interface describes one table, its columns, its primary key, its indexes, and its foreign keys.
columns
The columns property describes the table's columns, in declaration order, including any added later by ALTER TABLE.
ColumnSchema[]foreignKeys
The foreignKeys property describes the table's foreign keys, in the order they were declared.
ForeignKeySchema[]indexes
The indexes property describes the table's secondary indexes, in name order.
IndexSchema[]name
The name property contains the table's name.
stringprimaryKey
The primaryKey property contains the primary-key column names, in key order. A composite key has more than one.
string[]renamedFrom
The renamedFrom property names a table's former name, which setSchema renames the table from while the database still has a table of that name. getSchema never includes it.
stringFunctions
create
The create function opens a TinyJoin database in a dedicated Worker.
create(): Promise<Client>| returns | Promise<Client> | A Promise resolving to a |
|---|
It constructs a Client and resolves once that Client is ready, so a database that cannot be opened rejects here. The Client constructor returns before then, for code that needs the waitReady lifecycle.
The overload of this function with no arguments creates an ephemeral in-memory database using the packaged Worker. Its data is lost when the Client closes or the page reloads.
Example
import {create} from 'tinyjoin';
const db = await create();
await db.exec(`
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL
)
`);
const {rows} = await db.query('SELECT * FROM tasks ORDER BY id');
await db.close();
The overload of this function with one argument, an options object, opens the database that its ClientOptions describe.
create(options: ClientOptions): Promise<Client>| Type | Description | |
|---|---|---|
options | ClientOptions | Storage and Worker configuration. |
| returns | Promise<Client> | A Promise resolving to a |
Use the options object to select storage or provide a custom Worker. Omitting its dataDir property selects an ephemeral in-memory database.
Example
import {create} from 'tinyjoin';
const db = await create({dataDir: 'memory://'});
await db.close();
The overload of this function with a storage URL as its first argument, and optionally options as its second, opens a database at that URL.
create(
dataDir: string | undefined,
options?: ClientOptions,
): Promise<Client>| Type | Description | |
|---|---|---|
dataDir | string | undefined | The |
options? | ClientOptions | Optional Worker configuration. |
| returns | Promise<Client> | A Promise resolving to a |
Use a stable opfs://name to persist data in the same browser, or memory:// for an ephemeral database. When providing the second argument, supply a storage URL and leave options.dataDir unset.
Example
import {create} from 'tinyjoin';
const db = await create('opfs://my-app');
await db.close();
Type Aliases
Query results type aliases
ChangedKeys
The ChangedKeys type maps a table name to the primary keys that changed in it.
{[table: string]: Row[]}Each key is an object holding only that table's primary-key columns, so a composite key arrives as one object with one property per column.
RowMode
The RowMode type selects object rows or positional array rows.
"array" | "object"An object row holds one value for each name, so a SELECT whose output names repeat is rejected unless its rows are arrays.
Data types type aliases
JsonPrimitive
The JsonPrimitive type represents a scalar value accepted by TinyJoin.
null | boolean | number | stringJsonValue
The JsonValue type represents a parameter or result value accepted by the TinyJoin JavaScript API.
JsonPrimitive | JsonValue[] | {[key: string]: JsonValue}Row
The Row type represents the default object form of a result row.
Record<string, JsonValue>Configuration type aliases
DataDir
The DataDir type identifies the database storage mode.
stringUse memory:// for an ephemeral database, or opfs://name for a persistent database. Calling create without a data directory also uses memory. Clients using the same OPFS name automatically share an elected database owner, including across tabs. Pending operations interrupted by owner loss reject without replay; subsequent operations reconnect automatically. Different names have independent data and do not synchronize.
Schema type aliases
ColumnType
The ColumnType type names one of the five runtime types a column holds.
"boolean" | "integer" | "float" | "text" | "json"Each SQL type name maps to one of them: INTEGER and BIGINT to integer, REAL and DOUBLE PRECISION to float, TEXT and VARCHAR to text, and JSON and JSONB to json. The schema keeps the runtime type, not the name the table was declared with, and the length of a VARCHAR(n) as its column's maxLength.
ForeignKeyAction
The ForeignKeyAction type names what deleting or updating a referenced row does to the rows that reference it: no action and restrict refuse it, cascade deletes or updates them too, and set null and set default change their referencing columns to NULL or to the columns' defaults.
"no action" | "restrict" | "cascade" | "set null" | "set default"node
The node module opens ephemeral TinyJoin databases in Node.js 22 or later. It constructs a Node Worker thread and loads the packaged WebAssembly engine automatically, with no extra dependencies, bundler, or polyfills.
The returned Client has the same SQL, transaction, prepared-statement, and subscription APIs as the browser Client. The module also exports ClientError and the shared Client API types, so Node applications can import their query, result, transaction, and subscription contracts from this entry point. ClientError is the same class exported by the browser entry point. See the Node guide.
Functions
create
The create function opens a fresh in-memory database in a dedicated Node Worker thread. It resolves when the database is ready.
create(dataDir?: "memory://"): Promise<Client>| Type | Description | |
|---|---|---|
dataDir? | "memory://" | The optional |
| returns | Promise<Client> | A ready |
Omit the argument or pass memory://. Each call owns an independent database and Worker; close the Client to release the Worker. All data is lost when the Client closes or the process exits. OPFS, filesystem persistence, and remote synchronization are not supported by this entry point.
Example
import {type Client, create} from 'tinyjoin/node';
const db: Client = await create();
try {
await db.exec('CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT)');
await db.query('INSERT INTO notes VALUES ($1, $2)', [1, 'Hello from Node']);
console.log((await db.query('SELECT * FROM notes ORDER BY id')).rows);
} finally {
await db.close();
}
References
worker
The worker module lets advanced applications start TinyJoin inside a Worker that they construct and bundle themselves.
Most applications should use create from the main tinyjoin module, which constructs the packaged Worker automatically.
startWorker
The startWorker function starts the TinyJoin request host in the current dedicated Worker. Persistent databases participate in automatic same-name coordination across tabs, just like the packaged Worker. Memory databases remain independent.
startWorker(): voidExample
import {startWorker} from 'tinyjoin/worker';
startWorker();
drizzle
The drizzle module connects the Drizzle ORM query builder to a TinyJoin Client. It needs drizzle-orm 0.45 or later, which the application installs itself: TinyJoin declares it as an optional peer dependency and never bundles it.
Drizzle writes PostgreSQL, so its queries run in TinyJoin's SQL dialect, and its schemas use the column types that dialect has. See the Drizzle guide for what works, and what does not.
Interfaces
Migrations interfaces
MigrationConfig
The MigrationConfig interface describes the migrations that the migrate function applies.
journal
The journal property is the Drizzle Kit journal, which an application can import from meta/_journal.json.
MigrationJournalmigrations
The migrations property maps each migration's file to its SQL text. A key may be the migration's tag, its file name, or any path ending in its file name, so the object that a bundler's glob import returns can be passed as it is.
Record<string, string>migrationsTable
The migrationsTable property names the table that records the migrations applied, __drizzle_migrations unless it is given.
stringMigrationJournal
The MigrationJournal interface is the journal that drizzle-kit generate writes beside its migrations, as meta/_journal.json, of which the migrate function reads each entry's tag and time.
entries
The entries property lists the migrations in the order to apply them, each with its tag, which names its SQL file, and the time it was generated.
{
tag: string;
when: number;
}[]Push interfaces
PushOptions
The PushOptions interface configures the push function.
drop
The drop property, when true, drops each table, and each column of a table the schema keeps, that the schema leaves out, with their data. Otherwise they stay as they are.
booleanrenames
The renames property maps the new name of each renamed table, as table, or column, as table.column, to the name it had, so that push renames it, keeping its data, rather than creating it empty. A rename that has already happened changes nothing.
Record<string, string>version
The version property gives the schema a version, zero unless it is given, which the database records. A schema whose version is lower than the database's is refused with SCHEMA_OUTDATED, so that a tab running an older copy of the application cannot undo a newer one's changes.
numberFunctions
Drizzle functions
drizzle
The drizzle function creates a Drizzle database over a TinyJoin Client.
drizzle<TSchema>(
client: Client,
config?: DrizzleConfig<TSchema>,
): TinyJoinDatabase<TSchema>| Type | Description | |
|---|---|---|
client | Client | |
config? | DrizzleConfig<TSchema> | Drizzle's configuration: its schema, for relational queries, and its logger, casing, and cache. |
| returns | TinyJoinDatabase<TSchema> | A Drizzle database. |
Queries run on the Client as array rows, which Drizzle maps to its own result shapes. Drizzle's db.transaction runs on the Client's callback transaction, so it holds the database as any TinyJoin transaction does, tx.rollback discards its work, and a transaction cannot nest, since there are no savepoints, or take an isolation level.
A value Drizzle encodes for a json or jsonb column is bound as the JSON value it is, rather than as the JSON text Drizzle makes of it, which TinyJoin would store as a string.
Close the Client yourself when the database is no longer needed.
Example
import {create} from 'tinyjoin';
import {drizzle} from 'tinyjoin/drizzle';
import {eq} from 'drizzle-orm';
import {boolean, pgTable, text} from 'drizzle-orm/pg-core';
const tasks = pgTable('tasks', {
id: text('id').primaryKey(),
title: text('title').notNull(),
done: boolean('done').notNull().default(false),
});
const client = await create('opfs://my-app-v1');
const db = drizzle(client, {schema: {tasks}});
await db.insert(tasks).values({id: crypto.randomUUID(), title: 'Try Drizzle'});
const open = await db.select().from(tasks).where(eq(tasks.done, false));
Migrations functions
migrate
The migrate function applies the migrations that drizzle-kit generate wrote, which the database has not yet recorded, in the journal's order.
migrate<TSchema>(
db: TinyJoinDatabase<TSchema>,
config: MigrationConfig,
): Promise<void>| Type | Description | |
|---|---|---|
db | TinyJoinDatabase<TSchema> | The Drizzle database, from drizzle. |
config | MigrationConfig | The journal and the SQL text of each migration. |
| returns | Promise<void> | This has no return value. |
Each migration runs as one script together with the row that records it, so it commits whole or not at all, and a migration that fails leaves the database as the previous one left it, and rejects with its error. A migration another Client of the same database applied first is skipped, so every tab of an application can call migrate as it starts.
A migration is recorded by its tag and applied once: a file changed after it was applied is not applied again, and a migration that joins the journal before others already applied is still applied. The migrations need no file system, so a browser application bundles them.
Example
import {create} from 'tinyjoin';
import {drizzle, migrate} from 'tinyjoin/drizzle';
import journal from './drizzle/meta/_journal.json';
const db = drizzle(await create('opfs://my-app-v1'));
await migrate(db, {
journal,
migrations: import.meta.glob<string>('./drizzle/*.sql', {
query: '?raw',
import: 'default',
eager: true,
}),
});
Push functions
push
The push function makes the database hold the tables of a Drizzle schema, as Drizzle Kit's push would for PostgreSQL, but from the application and as one change that commits whole or not at all.
push<TSchema>(
db: TinyJoinDatabase<TSchema>,
schema: Record<string, unknown>,
options?: PushOptions,
): Promise<boolean>| Type | Description | |
|---|---|---|
db | TinyJoinDatabase<TSchema> | The Drizzle database, from drizzle. |
schema | Record<string, unknown> | The Drizzle schema, whose tables push reads, leaving out its relations. |
options? | PushOptions | The schema's version, whether to drop what it leaves out, and what it renames. |
| returns | Promise<boolean> | A Promise resolving to true if the database changed, or false if it already held the schema. |
It reads each table's columns, primary key, unique constraints, and indexes from Drizzle, and passes them to the Client's setSchema, which creates what the database lacks and changes what differs. A schema the database already holds changes nothing, so every tab of an application can call push as it starts, in place of migrations.
A table that declares what TinyJoin does not have, such as a foreign key, a timestamp column, an SQL default, or an index on an expression, is refused before anything changes. A default Drizzle works out in JavaScript, with $defaultFn, is Drizzle's, not the database's.
Example
import {create} from 'tinyjoin';
import {drizzle, push} from 'tinyjoin/drizzle';
import * as schema from './schema';
const db = drizzle(await create('opfs://my-app-v1'), {schema});
await push(db, schema, {version: 2, renames: {'tasks.name': 'title'}});
Type Aliases
TinyJoinDatabase
The TinyJoinDatabase type is the Drizzle database the drizzle function returns: Drizzle's PostgreSQL database with the TinyJoin Client it queries as its $client.
(
PgRemoteDatabase<TSchema> &
{$client: Client}
)| Type | Description | |
|---|---|---|
$client | Client |
Drizzle's raw db.execute resolves to the rows its statement returns, as objects.
kysely
The kysely module connects the Kysely query builder to a TinyJoin Client. It needs kysely 0.28 or later, which the application installs itself: TinyJoin declares it as an optional peer dependency and never bundles it.
Kysely writes PostgreSQL, so its queries run in TinyJoin's SQL dialect. See the Kysely guide for what works, and what does not.
Classes
TinyJoinDialect
The TinyJoinDialect class is a Kysely dialect that runs Kysely's PostgreSQL queries on a TinyJoin Client.
Kysely runs one query at a time on the Client, so a query outside a transaction waits until the transaction ends rather than failing. Kysely's db.transaction runs on the Client's callback transaction, which holds the database, and every tab sharing it, until it commits or rolls back. It takes no isolation level or access mode, and there are no savepoints.
Its introspector reads the Client's getSchema, and Kysely's Migrator runs each migration's statements in turn, since TinyJoin runs DDL outside transactions, with a Web Lock keeping one tab migrating at a time.
Example
import {create} from 'tinyjoin';
import {TinyJoinDialect} from 'tinyjoin/kysely';
import {Kysely} from 'kysely';
interface Database {
tasks: {id: string; title: string; done: boolean};
}
const client = await create('opfs://my-app-v1');
const db = new Kysely<Database>({dialect: new TinyJoinDialect({client})});
const open = await db
.selectFrom('tasks')
.select(['id', 'title'])
.where('done', '=', false)
.execute();
Constructors
constructor
The constructor creates a dialect over a TinyJoin Client.
new TinyJoinDialect(config: TinyJoinDialectConfig): TinyJoinDialect| Type | Description | |
|---|---|---|
config | TinyJoinDialectConfig | The dialect's configuration, with its |
| returns | TinyJoinDialect |
Example
import {create} from 'tinyjoin';
import {TinyJoinDialect} from 'tinyjoin/kysely';
import {Kysely} from 'kysely';
interface Database {
tasks: {id: string; title: string};
}
const client = await create();
const dialect = new TinyJoinDialect({client});
const db = new Kysely<Database>({dialect});
await db.schema
.createTable('tasks')
.addColumn('id', 'text', (column) => column.primaryKey())
.addColumn('title', 'text', (column) => column.notNull())
.execute();
// Kysely's destroy leaves the Client open, so the application closes it.
await db.destroy();
await client.close();
Methods
createAdapter
The createAdapter method creates the adapter that tells Kysely how TinyJoin differs from PostgreSQL. Kysely calls it.
createAdapter(): DialectAdapter| returns | DialectAdapter | A Kysely dialect adapter. |
|---|
Example
import {create} from 'tinyjoin';
import {TinyJoinDialect} from 'tinyjoin/kysely';
const client = await create();
const adapter = new TinyJoinDialect({client}).createAdapter();
// TinyJoin runs DDL outside transactions, so Kysely's Migrator must too.
console.log(adapter.supportsTransactionalDdl);
// -> false
console.log(adapter.supportsReturning);
// -> true
await client.close();
createDriver
The createDriver method creates the driver that runs Kysely's queries on the Client. Kysely calls it.
createDriver(): Driver| returns | Driver | A Kysely driver. |
|---|
Example
import {create} from 'tinyjoin';
import {TinyJoinDialect} from 'tinyjoin/kysely';
import {Kysely} from 'kysely';
interface Database {
tasks: {id: string; title: string};
}
const client = await create();
await client.exec(`
CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL)
`);
const db = new Kysely<Database>({dialect: new TinyJoinDialect({client})});
// Kysely creates the driver for its first query, and runs each query on
// the Client.
await db
.insertInto('tasks')
.values({id: 'a', title: 'Write docs'})
.execute();
const {rows} = await client.query('SELECT * FROM tasks');
console.log(rows);
// -> [{id: 'a', title: 'Write docs'}]
await client.close();
createIntrospector
The createIntrospector method creates the introspector that lists the database's tables and columns from the Client's getSchema. Kysely calls it.
createIntrospector(): DatabaseIntrospector| returns | DatabaseIntrospector | A Kysely database introspector. |
|---|
Example
import {create} from 'tinyjoin';
import {TinyJoinDialect} from 'tinyjoin/kysely';
import {Kysely} from 'kysely';
const client = await create();
await client.exec(`
CREATE TABLE tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL)
`);
const db = new Kysely<unknown>({dialect: new TinyJoinDialect({client})});
// Kysely reads the tables through the introspector.
const [tasks] = await db.introspection.getTables();
console.log(tasks.name);
// -> 'tasks'
console.log(tasks.columns.map(({name, dataType}) => [name, dataType]));
// -> [['id', 'text'], ['title', 'text']]
await client.close();
createQueryCompiler
The createQueryCompiler method creates Kysely's PostgreSQL query compiler. Kysely calls it.
createQueryCompiler(): QueryCompiler| returns | QueryCompiler | A Kysely query compiler. |
|---|
Example
import {create} from 'tinyjoin';
import {TinyJoinDialect} from 'tinyjoin/kysely';
import {Kysely} from 'kysely';
interface Database {
tasks: {id: string; title: string};
}
const client = await create();
const db = new Kysely<Database>({dialect: new TinyJoinDialect({client})});
// Kysely compiles each query with the compiler, into PostgreSQL's SQL.
const {sql, parameters} = db
.selectFrom('tasks')
.select('title')
.where('id', '=', 'a')
.compile();
console.log(sql);
// -> 'select "title" from "tasks" where "id" = $1'
console.log(parameters);
// -> ['a']
await client.close();
Interfaces
TinyJoinDialectConfig
The TinyJoinDialectConfig interface configures a TinyJoinDialect.
client
The client property is the TinyJoin Client, from create(), on which Kysely runs its queries. The application closes it: Kysely's destroy leaves it open.
Clientvite
The vite module provides optional production offline caching for Vite applications, including TinyJoin's lazily loaded Worker, OPFS, and WASM files. This Node-only build integration is separate from the browser runtime.
Interfaces
TinyjoinOfflineOptions
The TinyjoinOfflineOptions interface configures production offline caching.
mode
The mode property selects automatic service-worker registration or integration with an existing service worker. The default is service-worker.
"service-worker" | "manifest"The manifest mode emits tinyjoin-precache.json and tinyjoin-precache.js without registering or replacing a service worker.
Functions
tinyjoinOffline
The tinyjoinOffline function returns a Vite plugin that precaches the complete emitted production build, including lazy runtime assets, with verified content hashes. It does not register a service worker during development.
tinyjoinOffline(options?: TinyjoinOfflineOptions): Plugin| Type | Description | |
|---|---|---|
options? | TinyjoinOfflineOptions | Optional offline-caching settings. |
| returns | Plugin | A Vite plugin that configures production offline caching. |
The first visit requires a network connection. Wait for navigator.serviceWorker.ready before expecting a subsequent navigation to work offline. Storage persistence and application-file caching remain separate: OPFS stores the database, while the service worker stores the application.
Updates wait until all tabs controlled by the old service worker close. The plugin does not force a new worker onto running tabs. Serve the whole build together at one same-origin root-relative or relative Vite base. External requests and files created after the build are not cached.
Example
import {defineConfig} from 'vite';
import {tinyjoinOffline} from 'tinyjoin/vite';
export default defineConfig({plugins: [tinyjoinOffline()]});