Skip to content

Release 0.22.0: localStorage engine, Tauri bridge, npm package rewrite - #6

Merged
Tam1SH merged 9 commits into
masterfrom
release/0.22.0
Sep 25, 2026
Merged

Tam1SH merged 9 commits into
masterfrom
release/0.22.0

Conversation

@Tam1SH

@Tam1SH Tam1SH commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

0.22.0

Breaking

  • Store::subscribe returns a StoreSubscription that unsubscribes when dropped, in place of a bare id.
  • SubscriptionId is a type of its own, drawn from one count per process: an id from one store names nothing in another. StoreBackend::unsubscribe returns whether this store held it.
  • MemoryStore::open(&config, migrations) takes the steps and returns (store, report): a store opened on the memory engine by name runs its steps like any other.
  • Source gains AnotherPage, and StoreEvent::is_external_edit is true for it.
  • gpui adapter 0.3.0: AmeView<T> and AmeEntity<T> lose the store parameter; new_amethystate panics when the slice will not open, try_new_amethystate returns the error. Built on gpui-pre 0.3.6, Rust 1.95.
  • npm amethystate 0.4.0 is rewritten: Field, ReactiveMap, load() into a slice, a Transport with tauri() as the default. get() is synchronous and subscribe returns its unsubscribe, the useSyncExternalStore and Svelte store contract.
  • amethystate-codegen writes strict TypeScript classes over that package; value types come from ts-rs, derived only where the frontend sees them.
  • amethystate-reactor is removed.

New

  • Backend::LocalStorage (feature localstorage), for a page on wasm32-unknown-unknown: one JSON value per key, a write in the call, a full quota answered by set, another tab's write arriving as Source::AnotherPage. StoreLayout::PageStorage { under } says where it lives.
  • Async twins for what waits on the disk: StoreBackend::save_async and close_async, shutdown_async, GlobalStoreGuard::close_async.
  • The memory engine reads in parallel, as redb does.
  • The Tauri plugin has delete_prefix and scan_keys, both in amethystate:default.
  • The yew adapter works without Tauri: AmeStateProvider, use_field, use_read_only_field, use_map, use_map_entry, use_amethystate over a local store.
  • cargo xtask browser runs the whole test suite in headless Chrome.

Fixes

  • Tauri frontends hear events again: the listener goes through the Tauri 2 event plugin.
  • A path stays watched while anyone still watches it; the first unsubscribe ended it for everybody.
  • Deleting a prefix that covers a watched map clears the map on the frontend.
  • A map write from a Rust frontend applies at once and rolls back when refused: an entry no longer flashes as removed while its write is in flight, and a new one no longer vanishes right after it is added.
  • The leptos and dioxus Tauri paths build again.
  • A text store closing on Windows while its directory changed could hang the file watcher and whichever thread next opened a file there; notify moves to 9.0.0-rc.5, which stops a watch without that race.
  • cargo xtask browser removes the Chrome profile each run leaves, about 30 MB apiece.

Also here

  • Every example is the same multi-page todo app; new web-dioxus and web-yew, and tauri-typescript in place of tauri-settings.
  • The book: a TypeScript page for the new package, a browser section on the installation page, corrected Tauri and gpui pages.
  • CI type-checks and tests the npm package.

Warning

The framework adapters and about half of the engines are not used in any real application. They run in CI and were smoke-tested, and they were built as a proof of concept for the architecture of the store.

🤖 Generated with Claude Code

Tam1SH and others added 9 commits September 24, 2026 22:40
…that end themselves

The localstorage engine opens a store in a page on wasm32-unknown-unknown: one JSON value per key under amethystate:<store>:, a write in the call, a full quota answered by set, and another tab's write arriving as Source::AnotherPage. StoreLayout::PageStorage says where it lives.

save_async, close_async, shutdown_async and GlobalStoreGuard::close_async resolve with how the write ended without blocking the thread that awaits them, so a page, which cannot block, can still wait for its data.

Store::subscribe returns a StoreSubscription that unsubscribes on drop. SubscriptionId is drawn from one count per process, so an id from one store names nothing in another, and StoreBackend::unsubscribe says whether it held it.

The memory engine runs migration steps when it is asked for by name, and reads in parallel as redb does.

The whole tests/ tree builds for wasm and runs in headless Chrome through cargo xtask browser, which hands each run a Chrome profile of its own and removes it afterwards: wasm-bindgen-test-runner kills chromedriver before the session ends, and every run used to leave about 30 MB under the temporary directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Frontend listeners go through the Tauri 2 event plugin; before, they never heard anything. Event channel names come from one function, amethystate_core::event_channel, checked against a fixture the TypeScript side reads too.

The plugin keeps a path watched while anyone still watches it rather than ending it at the first unsubscribe, clears a watched map when a prefix deletion covers it, and names a map entry by its own key with the handle that wrote it, so a frontend skips its own echo. delete_prefix and scan_keys join the commands and amethystate:default.

An async map write applies and notifies before it reaches the backend and is undone when the backend refuses it, with the old value taken from the cache: an entry no longer shows as removed while its write is in flight. The arena's Tauri path is rewritten and inserts a new entry rather than updating an absent one, so a new entry no longer vanishes right after it is added.

amethystate-codegen writes strict TypeScript classes over the npm package, with value types imported from the files ts-rs writes beside them; volatile fields are skipped. The Rust wasm bindings are generated from StorePath.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
amethystate 0.4.0: Field, ReactiveMap, load() into a slice, and a Transport with tauri() as the default. get() is synchronous and subscribe returns the function that stops it, which is the contract of React's useSyncExternalStore and of Svelte stores. Writes apply at once and roll back when the store refuses them; a map skips its own echo by the handle that wrote it.

Tested with vitest over @tauri-apps/api/mocks; path spellings and channel names are checked against the fixture the Rust side reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
yew: a local path over a store in the same process - AmeStateProvider, use_field, use_read_only_field, use_map, use_map_entry and use_amethystate - beside the Tauri path, which sits behind tauri-backend, on by default. Effects on the Tauri path depend on the field, map or entry they watch.

gpui 0.3.0 builds on gpui-pre 0.3.6 and needs Rust 1.95. AmeView<T> and AmeEntity<T> lose the store parameter nothing used; new_amethystate panics with the error when the slice will not open, and try_new_amethystate returns it.

dioxus builds against dioxus with default features off; its Tauri writes are spawned. leptos and dioxus build again on the Tauri path.

amethystate-reactor is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every example is the same todo app with lists, a page per list and settings, so each adapter's lifecycle gets exercised as pages mount and unmount. New: web-dioxus and web-yew beside web-leptos. tauri-typescript replaces tauri-settings and runs on the rewritten npm package; tauri-leptos and tauri-yew are ported to the same app. The windows-reactor example goes with its adapter.

Trunk output and the Tauri examples' lockfiles are ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The introduction opens with why amethystate was written. The installation page gets a browser section for localstorage, the TypeScript page is rewritten for the new package, and the Tauri page hands the store to init and lists the two new permissions. The gpui page follows the adapter; the leptos and yew pages name the adapters' own versions and no feature that does not exist. The windows-reactor pages go. The README names localStorage and says plainly that the adapters and about half of the engines are a proof of concept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both ci.ps1 and the workflow type-check and test js/. ci.ps1 also lints the tests for wasm32 and runs them in headless Chrome. amethystate-gpui leaves the workspace runs, because gpui-pre needs Rust 1.95 against the workspace's 1.90; ci.ps1 lints it on its own with cargo +1.95.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The workspace drops amethystate-reactor and gains the two page test crates. Breaking below 1.0: MemoryStore::open takes the migration steps, Store::subscribe returns a guard, SubscriptionId is a type of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ang on Windows

notify 8.2 stops a Windows watch with CancelIo and CloseHandle, and a read that had already completed with an event re-arms ReadDirectoryChangesW on the closed handle. When the handle value has been handed to a synchronous open in the meantime, the watcher thread and whoever opened the file both block on it for good. A text store closing while its directory changed - a test removing its temporary directory, an application removing or rewriting the file - could hang like that. In CI it hung a RON conformance test for an hour; locally one run in ten of the RON conformance tests hung, and none in a hundred after the move.

notify 9.0.0-rc.5 marks the watch as stopping before it cancels, so the late completion does not re-arm. 8.x has no release with the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Tam1SH
Tam1SH merged commit 20a5865 into master Sep 25, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant