I have a cross-platform (Initially iPad) re-frame app running on Tauri. I'm now considering implementing a rehydration mechanism which I have never done before. That means UI state and content, which includes datascript, should be persisted somehow. I'm thinking of using the Rust core to play that role. Has anyone implemented something similar? What would be a sensible way to do it? Thanks!
You shouldn't need to use Tauri-specific plugins to read/write your native filesystem. Browser APIs let you pop up a native dialog to grant file/folder access. Once granted I think you can save the directory handle to indexed-db so you don't need to re-grant per session, though I haven't actually needed to do this as yet. I've used this for a data import function in a current project and it's pretty straightforward
I was wondering if there's a systematic way to do it, like datasync or such. You want to maintain your datascript state and potentially more
If it's just local persistence, it's pretty straightforward. Sounds like you may have a datascript dB and a separate state atom you want to persist? The simplest solution there would be to add watches to both that write the current state to localStorage on every update, then reload on app load. You could do something similar to an EDN file using one of the FileSystem APIs with a little bit of extra effort. If you want to get clever, your datascript DB watch can write a transaction log to indexed-db, giving you a poor-man's local datomic. Your mention of datasync though suggests you're thinking about client <-> server sync? That's a different problem / can of worms...
> Once granted I think you can save the directory handle to indexed-db so you don't need to re-grant per session Whaaaat really?
Does this work in normal browsers or only Tauri?
I might be missing something, but although tauri uses the platform's webview, your frontend code does not have the same filesystem access as a Progressive Web App/website. They add extra security barriers so you'd need to go through the tauri.conf permissions system at least. For example, the browser's upload worked without modification, but the browser's download did not automatically work when compiled as a tauri application.
I have no experience with tauri - just using those APIs in a standard SPA running in chrome/chromium/edge
@chris358 don't take that as gospel - haven't tried it yet, but it was something that stuck with me during a recent read of (I think) MDN API docs. I'll see if I can dig out the reference and report back to you
... I've now fact checked myself π Can't find the original source (it wasn't the MDN docs) - but that included an example of writing the handle to indexed-db. Reading the specification here https://wicg.github.io/file-system-access/ , it's alluded to indirectly in section 2.3.1 - my reading of it is that there's some wiggle room in the implementation - on load from indexed DB, you'll probably be prompted for confirmation that it's still OK, but not necessarily. I'll report back again if I can find the original source again. All of that said - you have a local file bucket without any permissions concerns using the OPFS API, which should be fine for most use cases. You won't see the files on your local FS, but easy to implement an export function using the file system access API for episodic use...
@cormacc, the datasync idea is just a rough parallel - not literal implementation. The underlying mechanism should allow for an efficient, potentially incremental, sync within datascript, not with server. But then gain, much of this is armchair. I haven't experimented much in this space. On a different note, the platform specific considerations, like ios file access and so on are making me wonder whether I should go Rust-first. Ideally, the solution will have to be as platform-agnostic as possible.
The nice thing about the browser APIs is that they are platform agnostic. I'm using them for native filesystem and USB port access across Linux, Windows and MacOS. Haven't played with iPad/iPhone though. Re. incremental sync, the datascript / indexed-db transaction log gives you that by definition, though you do have to re-run your transactions to rebuild the DB at the start of a session
Tried to research a bit. Here's one thing I found: "... Web APIs (localStorage/IndexedDB) are fine for βhotβ UI state, quick resume, and small blobs. But on iOS/WKWebView theyβre subject to quota/eviction policies, so donβt treat them as durable. Apple/WebKit have documented eviction behavior and quota caps; IndexedDB is supported but not guaranteed to stick around forever if the OS needs space." https://webkit.org/blog/14403/updates-to-storage-policy
I wonder about the performance implications of using tauri's official KV store as the primary rehydration driver. But it's durable and platform-agnostic. https://v2.tauri.app/plugin/store
my intuition is that it'll be a fast part of your system if your intention is to store blobs of data that you then read into the application. The only way to know for sure is measuring though :) in my personal app something I ran into is that rehydration seemed 2-3 times slower while in the dev mode compared to when it's built for release because the most intensive part of my rehydration steps were the actual reading into the system, not the retrieval from disk/decompression. I think the rehydration steps benefitted from the cljs build optimizations.
Don't know about rehydration, but if you want to persist data between sessions you should be able to manage that fine using just the filesystem, localStorage and/or indexed-db web APIs provided by the webview2 engine Tauri (I think) uses?
+1 on using existing web storage APIs
i literally implemented that this week!
localstorage is the easiest for persisting local state that's non-essential, but if it's data that needs to be backed up you can consider the tauri-fs extension. On ios it's limited to the Applications folder though.
The general workflow is using the tauri-dialog extension to prompt the user for a file path (dialog/save) and then you take the result of that call and use it with the fs extension's writeFile or writeTextFile to persist data. Don't forget to serialize your data properly and use clojure.edn to parse it when reading it back in. (https://stackoverflow.com/questions/24661655/clojure-clojurescript-clojure-core-read-string-clojure-edn-read-string-and-c)
you'll also need to update your file (src-tauri/capabilities/tauri.conf) with the right permissions. I'd suggest calling the functions from the repl, and then checking the application's console log for the error message. It'll show you exactly which permissions can allow the action so you can select the most restrictive options and not give the program any more access than necessary :)
Thanks @ada. On a related note, you're using exactly this stack (cljs + Tauri)? I'm betting my production app on it, with capacitor as potential fallback should any deal breaker turn up. So far so good though. Any gotchas you faced?
I'm using that stack, but my targets are desktop apps. My binaries are < 30mb and I've been able to do everything I need using the plugins. I'd try them before jumping to rust although the inter-process-communication between cljs and the rust backend worked great for me... my current roadblock is the cost of code-signing for distribution, but that should already be covered by your Apple Developer fee and is not a Tauri specific challenge.
You're right, Apple developer solves the signing issue. Tauri is solid on desktop, but mobile is less trodden path. Surprisingly smooth to me for a new tech. Hopefully, there won't be any deal-breakers down the road.
Re. persistent file access permissions -- seems it is a thing, on chromium-based browsers at least --- found a detailed write-up here: https://developer.chrome.com/blog/persistent-permissions-for-the-file-system-access-api