Skip to content

v9.0

Latest

Choose a tag to compare

@jamesgpearce jamesgpearce released this 03 Jul 03:27

This release has no new features; just fixes and reliability improvements.

TinyBase v9.0 is all about addressing issues from the community - and making local-first apps behave better in production. The areas addressed include persistence, synchronization, schema defaults, infrastructure limits, and edge-case query semantics.

There is one new configuration option (for more selective Value persistence), but otherwise the wider theme is reliability. This release hardens WebSocket synchronization, Durable Object storage, PowerSync startup, custom Persister loading, and a few type and documentation edges so that apps recover and sync more cleanly under real-world conditions.

We hope you enjoy using TinyBase and if you find further issues, keep them coming!

Persistence Subsets

This release adds finer-grained configuration for tabular database Persisters, allowing Values persistence to be limited to selected Value Ids (#279).

For apps that keep durable state and UI-only state in the same Store, the DpcTabularValues load and save properties can now use an array of Value Ids instead of a simple boolean:

const valuesSubsetDatabasePersisterConfig = {
  mode: 'tabular',
  values: {
    load: ['selectedPet', 'open'],
    save: ['selectedPet'],
  },
};

console.log(valuesSubsetDatabasePersisterConfig.values.load);
// -> ['selectedPet', 'open']

When a subset is configured, unlisted Values in the Store are not saved, and unlisted columns in the Values database table are left untouched.

WebSocket Synchronization Fixes

WebSocket Synchronizers can now fragment large synchronization payloads and reassemble them on receipt. This helps deployments behind infrastructure with WebSocket message size limits, such as Cloudflare Workers and Durable Objects (#261).

The createWsSynchronizer and createWsServer functions now accept an optional fragment size argument. Incomplete fragment buffers expire using the existing request timeout, which can also now be set on createWsServer. Durable Object servers can override the getFragmentSize and getRequestTimeoutSeconds methods to set the same behavior for messages they send.

The WebSocket Synchronizer documentation now also clarifies that WsServer paths come from WebSocket URL paths, not MergeableStore Ids, so clients that need separate synchronization groups should connect to different URL paths (#206).

When a persisted WsServer path starts after having no connected clients, it now loads its persisted Store before starting synchronization. This means the first client to reconnect is sent only the data it is missing, instead of receiving the whole persisted Store as a fresh change (#205).

Schema Default Synchronization Fixes

Schema defaults inserted automatically into MergeableStores now use neutral timestamps, so defaulted Values and Cells no longer overwrite newer synced data from another peer. Explicit writes of default values still receive normal timestamps (#167).

PowerSync Persistence Fixes

The PowerSync Persister now updates existing tabular rows before inserting missing ones, instead of replacing whole rows during upserts. This avoids flooding PowerSync upload queues with replacement writes when schema validation causes loaded data to be written back unchanged on startup (#262).

Custom Persister Loading Fixes

Custom Persisters can now return undefined from getPersisted to indicate that there is no persisted content. Loading then uses initialContent if it was provided, or otherwise leaves the Store unchanged without invoking the ignored error handler (#161).

Durable Object Persistence Fixes

The Durable Object SQL Storage Persister's fragmented mode now stores table row data as one SQL row per TinyBase Row, instead of one SQL row per Cell. This reduces the number of SQLite writes for wide Rows while preserving the fragmented mode's protection from Cloudflare's 2MB row limit. Existing cell-level fragmented data is still loaded and is cleaned up when the Row is next saved (#268).

Query Transaction Fixes

Grouped queries, including those with having clauses, now correctly return their current result when a query definition is added during an active Store transaction (#259).

Query Documentation Clarifications

The TinyQL documentation now explicitly describes that a Row only appears in a query result when at least one selected Cell or calculated value is defined. If all selected values for a Row resolve to undefined, no ResultRow is created for that Row (#183).

Type Fixes

The schema-aware MergeableContent, MergeableChanges, persisted content, and Persister listener types now validate content being set or loaded in the same way as Store setters. This catches invalid Cell or Value Ids and values in custom Persisters and MergeableStore setters (#178).

Breaking Change

This release is a major version because the Durable Object SQL Storage Persister's fragmented mode uses a new storage layout. TinyBase v9.0 can read the old cell-level fragmented data written by earlier releases, but once it saves the new row-level fragmented data, older TinyBase versions are not designed to read that data back. Apps using fragmented Durable Object SQL storage should not roll those Durable Objects back to an earlier TinyBase version after v9.0 has written to them.

Thank You

Thanks to everyone whose reports and fixes shaped this release:

Dheeraj, Jakub Riedl, Patryk Wegrzyn, Damilola Romniyi, Andrew Glago, wattroll, Will Honey, and Daniel Berndt.

Couldn't do it without you!