Skip to content

Commit aafe399

Browse files
committed
v0.3.0
BETWEEN, AS column aliases, SELECT DISTINCT, INSERT ... ON CONFLICT upserts, and LIKE/ILIKE pattern predicates. Each is new syntax that was previously rejected, so existing statements keep their meaning. The Worker protocol stays at version 8 and the page format at format 2, so upgrading from v0.2.0 needs no action. Building with Rust 1.98.1 more than offsets the new SQL: the compressed engine is about 5 KiB smaller than in v0.2.0, so the release notes now say so rather than predicting growth. The compatibility guide states the DISTINCT ORDER BY rule exactly for single tables and joins, and that a LIKE escape makes the next character literal and a non-text column is rejected. Also rebuilds the committed site documentation, which had not yet picked up the v0.3.0 notes.
1 parent 30e5b49 commit aafe399

23 files changed

Lines changed: 171 additions & 48 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<link rel="preload" as="image" href="https://tinybase.org/favicon.svg?asImg"><link rel="preload" as="image" href="https://synclets.org/favicon.svg?asImg"><link rel="preload" as="image" href="https://tinywidgets.org/favicon.svg?asImg"><link rel="preload" as="image" href="https://tinytick.org/favicon.svg?asImg"><section id="hero"><h2 id="a-tiny-worker-first-relational-database-for-browser-apps">A tiny, worker-first <em>relational database</em> for browser apps.</h2><p>PostgreSQL-shaped SQL, running locally and away from the main thread.</p></section><nav id="actions" aria-label="Get started"><a class="start" href="https://tinyjoin.org/guides/getting-started/">Get started</a> <a href="https://tinyjoin.org/demos/">Try the demos</a> <a href="https://tinyjoin.org/api/">Read the API</a></nav><hr><section><h2 id="small-enough-to-not-worry-about">Small enough to not worry about</h2><p>The whole database - the main-thread client, the Worker host, and the Rust WASM engine - is 299 KiB gzipped, and only 6 KiB of that ever runs on the UI thread.</p></section><div class="table"><table><thead><tr><th>Component</th><th style="text-align:right">gzip</th></tr></thead><tbody><tr><td>Main JS</td><td style="text-align:right">6 KiB</td></tr><tr><td>Worker JS</td><td style="text-align:right">15 KiB</td></tr><tr><td>Engine WASM</td><td style="text-align:right">279 KiB</td></tr><tr><td><strong>Everything</strong></td><td style="text-align:right"><strong>299 KiB</strong></td></tr></tbody></table></div><section><h2 id="your-first-tinyjoin-app">Your first <em>TinyJoin</em> app</h2><p>Scaffold a complete local todo app in JS or TS - and with its relational data saved in TinyJoin across reloads - in less than 60s.</p></section>
1+
<link rel="preload" as="image" href="https://tinybase.org/favicon.svg?asImg"><link rel="preload" as="image" href="https://synclets.org/favicon.svg?asImg"><link rel="preload" as="image" href="https://tinywidgets.org/favicon.svg?asImg"><link rel="preload" as="image" href="https://tinytick.org/favicon.svg?asImg"><section id="hero"><h2 id="a-tiny-worker-first-relational-database-for-browser-apps">A tiny, worker-first <em>relational database</em> for browser apps.</h2><p>PostgreSQL-shaped SQL, running locally and away from the main thread.</p></section><nav id="actions" aria-label="Get started"><a class="start" href="https://tinyjoin.org/guides/getting-started/">Get started</a> <a href="https://tinyjoin.org/demos/">Try the demos</a> <a href="https://tinyjoin.org/api/">Read the API</a></nav><hr><section><h2 id="small-enough-to-not-worry-about">Small enough to not worry about</h2><p>The whole database - the main-thread client, the Worker host, and the Rust WASM engine - is 295 KiB gzipped, and only 6 KiB of that ever runs on the UI thread.</p></section><div class="table"><table><thead><tr><th>Component</th><th style="text-align:right">gzip</th></tr></thead><tbody><tr><td>Main JS</td><td style="text-align:right">6 KiB</td></tr><tr><td>Worker JS</td><td style="text-align:right">15 KiB</td></tr><tr><td>Engine WASM</td><td style="text-align:right">274 KiB</td></tr><tr><td><strong>Everything</strong></td><td style="text-align:right"><strong>295 KiB</strong></td></tr></tbody></table></div><section><h2 id="your-first-tinyjoin-app">Your first <em>TinyJoin</em> app</h2><p>Scaffold a complete local todo app in JS or TS - and with its relational data saved in TinyJoin across reloads - in less than 60s.</p></section>
22

33
```bash
44
> npm create tinyjoin@latest

‎docs/guides/agents-guide/index.html‎

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

‎docs/guides/agents-guide/main.html‎

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

‎docs/guides/caveats/index.html‎

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

‎docs/guides/caveats/main.html‎

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

‎docs/guides/releases/index.html‎

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

‎docs/guides/releases/main.html‎

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

‎docs/guides/sql-compatibility/index.html‎

Lines changed: 4 additions & 2 deletions
Large diffs are not rendered by default.

‎docs/guides/sql-compatibility/main.html‎

Lines changed: 4 additions & 2 deletions
Large diffs are not rendered by default.

‎docs/guides/storage-and-lifecycle/index.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,4 @@
4848
<span class="keyword">await</span> db<span class="punctuation">.</span><span class="function"><a href="/api/tinyjoin/classes/lifecycle/client/methods/lifecycle/close/">close</a></span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">.</span><span class="function">catch</span><span class="punctuation">(</span><span class="punctuation">(</span>cleanupError<span class="punctuation">)</span> <span class="operator">=></span> <span class="builtin">console</span><span class="punctuation">.</span><span class="function">error</span><span class="punctuation">(</span>cleanupError<span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">;</span>
4949
<span class="keyword">const</span> reopened <span class="operator">=</span> <span class="keyword">await</span> <span class="function"><a href="/api/the-essentials/using-a-database/create/">create</a></span><span class="punctuation">(</span><span class="string">'opfs://my-app'</span><span class="punctuation">)</span><span class="punctuation">;</span> <span class="comment">// Use the original name.</span>
5050
<span class="comment">// Reconcile the original operation using reopened before accepting more work.</span>
51-
</code></pre><p>A crash during a write can lose its acknowledgement after committing. Follow <a href="#recovering-after-an-uncertain-write">uncertain-write reconciliation</a>, even when the error is a Worker failure instead of a storage code. Memory data is lost with its Worker.</p><p>Temporary OPFS owner handover is different: <code>LEADER_CHANGED</code> rejects in-flight operations, but the same <a href="/api/tinyjoin/classes/lifecycle/client/"><code>Client</code></a> reconnects and restores prepared statements for subsequent work. Reconcile interrupted writes; an interrupted transaction ends with <code>TRANSACTION_LOST</code>. The next section describes this recoverable lifecycle.</p><h3 id="tab-handover-and-subscriptions">Tab handover and subscriptions</h3><p>The browser&#x27;s locks elect the next owner when the previous Worker closes or dies. New operations wait while that owner opens the database. Prepared statements are restored automatically when needed. Notifications fan out to every <a href="/api/tinyjoin/classes/lifecycle/client/"><code>Client</code></a>, including the one that wrote the data.</p><p>Operations already sent to a departing owner reject with <code>LEADER_CHANGED</code>. Their effects may already have committed; TinyJoin never silently repeats them. The <a href="/api/tinyjoin/classes/lifecycle/client/"><code>Client</code></a> reconnects for subsequent operations, so inspect the stored outcome before retrying a write. A callback transaction interrupted by owner loss cannot continue: subsequent transaction operations reject with <code>TRANSACTION_LOST</code>. Start a new transaction after reconciliation.</p><p>After handover, or when a page becomes visible or resumes, subscriptions may receive <code>{revision, tables: [], reset: true}</code>. Re-query on this notification even though the precise changed tables are unknown. Filtered subscriptions also receive it. A subscription is an invalidation signal, not a durable log of every commit.</p><p>Transactions exclude other Clients for the entire callback. Keep callbacks short and do not wait on work that needs another <a href="/api/tinyjoin/classes/lifecycle/client/"><code>Client</code></a> for the same name. If a client disappears with a transaction open, the owner rolls it back. A frozen but still live owner or transaction holder can delay other tabs until it resumes or closes. TinyJoin does not steal a live storage lock based on a timer, since doing so could let two engines write concurrently.</p><p>For offline reopening of the application itself, use the <a href="/guides/offline/">offline build integration</a>. OPFS stores the data; a service worker caches the application and database runtime files.</p><h3 id="recovering-after-an-uncertain-write">Recovering after an uncertain write</h3><p>A rejected write does not always prove that nothing committed. The following <a href="/api/tinyjoin/classes/errors/clienterror/"><code>ClientError</code></a> codes mean the current engine must no longer be used:</p><div class="table"><table><thead><tr><th>Code</th><th>Meaning</th></tr></thead><tbody><tr><td><code>RECOVERY_REQUIRED</code></td><td>The engine cannot safely continue after a storage publication failure; reopening must establish the stored state.</td></tr><tr><td><code>STORAGE_COMMIT_OUTCOME_UNKNOWN</code></td><td>A storage write or a result after a possible commit could not be confirmed. The requested change may have committed.</td></tr><tr><td><code>STORAGE_ENGINE_POISONED</code></td><td>A previous uncertain or fatal result already made this engine unusable.</td></tr></tbody></table></div><p>Stop accepting database work, keep the original error, and close the <a href="/api/tinyjoin/classes/lifecycle/client/"><code>Client</code></a>. For OPFS, open the <strong>same database name</strong> with a new <a href="/api/the-essentials/using-a-database/create/"><code>create</code></a>() call and inspect the recovered rows before deciding whether to repeat the operation. If open or inspection fails, keep the application in a recovery state and preserve the stored data; changing the name or deleting the database would hide the state you need to reconcile. A new memory database starts empty and cannot recover the old <a href="/api/tinyjoin/classes/lifecycle/client/"><code>Client</code></a>&#x27;s data.</p><p>Give an application operation a stable identifier before its first attempt and record that identifier in the same transaction as its effects. After reopening, query that record and compare the intended values. A matching record means the operation already happened; a missing record may allow a deliberate retry with the <strong>same identifier</strong>. An unexpected record needs application reconciliation. Generating a fresh identifier on every retry can apply an operation twice. TinyJoin has no automatic replay or <code>ON CONFLICT</code> clause, so this policy belongs to the application.</p><p>The <code>retryable</code> property only says that a later attempt or reopen may succeed. It is not a guarantee that replaying a write is safe, nor that the current <a href="/api/tinyjoin/classes/lifecycle/client/"><code>Client</code></a> remains usable. A rollback attempted after an uncertain commit cannot establish that the commit was absent. See also <a href="/guides/transactions-and-changes/#errors-and-cancellation">transaction error handling</a>.</p></section></article><aside aria-hidden="true"></aside></main><footer><nav><a id="gh" href="https://github.com/tinyplex/tinyjoin" target="_blank" rel="noreferrer">GitHub</a></nav><nav><a href="/">TinyJoin</a> © 2026 James Pearce. MIT License.</nav></footer><script>window.dataLayer=window.dataLayer||[];function g(){dataLayer.push(arguments);}g('js',new Date());g('config','G-40B96SPQX2');</script></body></html>
51+
</code></pre><p>A crash during a write can lose its acknowledgement after committing. Follow <a href="#recovering-after-an-uncertain-write">uncertain-write reconciliation</a>, even when the error is a Worker failure instead of a storage code. Memory data is lost with its Worker.</p><p>Temporary OPFS owner handover is different: <code>LEADER_CHANGED</code> rejects in-flight operations, but the same <a href="/api/tinyjoin/classes/lifecycle/client/"><code>Client</code></a> reconnects and restores prepared statements for subsequent work. Reconcile interrupted writes; an interrupted transaction ends with <code>TRANSACTION_LOST</code>. The next section describes this recoverable lifecycle.</p><h3 id="tab-handover-and-subscriptions">Tab handover and subscriptions</h3><p>The browser&#x27;s locks elect the next owner when the previous Worker closes or dies. New operations wait while that owner opens the database. Prepared statements are restored automatically when needed. Notifications fan out to every <a href="/api/tinyjoin/classes/lifecycle/client/"><code>Client</code></a>, including the one that wrote the data.</p><p>Operations already sent to a departing owner reject with <code>LEADER_CHANGED</code>. Their effects may already have committed; TinyJoin never silently repeats them. The <a href="/api/tinyjoin/classes/lifecycle/client/"><code>Client</code></a> reconnects for subsequent operations, so inspect the stored outcome before retrying a write. A callback transaction interrupted by owner loss cannot continue: subsequent transaction operations reject with <code>TRANSACTION_LOST</code>. Start a new transaction after reconciliation.</p><p>After handover, or when a page becomes visible or resumes, subscriptions may receive <code>{revision, tables: [], reset: true}</code>. Re-query on this notification even though the precise changed tables are unknown. Filtered subscriptions also receive it. A subscription is an invalidation signal, not a durable log of every commit.</p><p>Transactions exclude other Clients for the entire callback. Keep callbacks short and do not wait on work that needs another <a href="/api/tinyjoin/classes/lifecycle/client/"><code>Client</code></a> for the same name. If a client disappears with a transaction open, the owner rolls it back. A frozen but still live owner or transaction holder can delay other tabs until it resumes or closes. TinyJoin does not steal a live storage lock based on a timer, since doing so could let two engines write concurrently.</p><p>For offline reopening of the application itself, use the <a href="/guides/offline/">offline build integration</a>. OPFS stores the data; a service worker caches the application and database runtime files.</p><h3 id="recovering-after-an-uncertain-write">Recovering after an uncertain write</h3><p>A rejected write does not always prove that nothing committed. The following <a href="/api/tinyjoin/classes/errors/clienterror/"><code>ClientError</code></a> codes mean the current engine must no longer be used:</p><div class="table"><table><thead><tr><th>Code</th><th>Meaning</th></tr></thead><tbody><tr><td><code>RECOVERY_REQUIRED</code></td><td>The engine cannot safely continue after a storage publication failure; reopening must establish the stored state.</td></tr><tr><td><code>STORAGE_COMMIT_OUTCOME_UNKNOWN</code></td><td>A storage write or a result after a possible commit could not be confirmed. The requested change may have committed.</td></tr><tr><td><code>STORAGE_ENGINE_POISONED</code></td><td>A previous uncertain or fatal result already made this engine unusable.</td></tr></tbody></table></div><p>Stop accepting database work, keep the original error, and close the <a href="/api/tinyjoin/classes/lifecycle/client/"><code>Client</code></a>. For OPFS, open the <strong>same database name</strong> with a new <a href="/api/the-essentials/using-a-database/create/"><code>create</code></a>() call and inspect the recovered rows before deciding whether to repeat the operation. If open or inspection fails, keep the application in a recovery state and preserve the stored data; changing the name or deleting the database would hide the state you need to reconcile. A new memory database starts empty and cannot recover the old <a href="/api/tinyjoin/classes/lifecycle/client/"><code>Client</code></a>&#x27;s data.</p><p>Give an application operation a stable identifier before its first attempt and record that identifier in the same transaction as its effects. After reopening, query that record and compare the intended values. A matching record means the operation already happened; a missing record may allow a deliberate retry with the <strong>same identifier</strong>. An unexpected record needs application reconciliation. Generating a fresh identifier on every retry can apply an operation twice. TinyJoin has no automatic replay, so this policy belongs to the application. Recording the identifier with <code>ON CONFLICT DO NOTHING</code> skips an operation that is already recorded, but it does not compare the recorded values with the intended ones.</p><p>The <code>retryable</code> property only says that a later attempt or reopen may succeed. It is not a guarantee that replaying a write is safe, nor that the current <a href="/api/tinyjoin/classes/lifecycle/client/"><code>Client</code></a> remains usable. A rollback attempted after an uncertain commit cannot establish that the commit was absent. See also <a href="/guides/transactions-and-changes/#errors-and-cancellation">transaction error handling</a>.</p></section></article><aside aria-hidden="true"></aside></main><footer><nav><a id="gh" href="https://github.com/tinyplex/tinyjoin" target="_blank" rel="noreferrer">GitHub</a></nav><nav><a href="/">TinyJoin</a> © 2026 James Pearce. MIT License.</nav></footer><script>window.dataLayer=window.dataLayer||[];function g(){dataLayer.push(arguments);}g('js',new Date());g('config','G-40B96SPQX2');</script></body></html>

0 commit comments

Comments
 (0)