Background
I am attempting to develop a cross-platform desktop app using Svelte and Tauri
When the app starts i need to load a settings.json-file from the filesystem into a custom Svelte store.
It needs to be a custom store because I must validate the data using a custom set-function before writing to it
The store will hold an object.
I am using regular Svelte and not Svelte-kit as SSR is not necessary.
Problems
Tests
Example
It would be a lot of code if I were to post all the failed attempts, so I will provide a example of what I am attempting to achieve.
Everything in the code works when createStore is not async, except reading the settings-file.
import { writable, get as getStore } from 'svelte/store'; // Svelte store
import _set from 'lodash.set'; // Creating objects with any key/path
import _merge from 'lodash.merge'; // Merging objects
import { fs } from '@tauri-apps/api'; // Accessing local filesystem
async function createStore() {
// Read settings from the file system
let settings = {}
try { settings = JSON.parse(await fs.readTextFile('./settings.json')); }
catch {}
// Create the store
const store = writable(settings);
// Custom set function
function set (key, value) {
if(!key) return;
// Use lodash to create an object
const change = _set({}, key, value);
// Retreive the current store and merge it with the object above
const currentStore = getStore(store)
const updated = _merge({}, currentStore, change)
// Update the store
store.update(() => updated)
// Save the updated settings back to the filesystem
fs.writeFile({
contents: JSON.stringify(updated, null, 2),
path: './settings.json'}
)
}
// Bundle the custom store
const customStore = {
subscribe: store.subscribe,
set
}
return customStore;
}
export default createStore();
When having a custom store which needs to be initialized asynchronously, I do this via an async method on the store which I'd call from the App
component, if the store is directly needed
(note that fs.writeFile()
also returns a Promise. If there was an error, this wouldn't be handled yet...)
<script>
import settings from './settings'
import {onMount} from 'svelte'
let appInitialized
onMount(async () => {
try {
await settings.init()
appInitialized = true
}catch(error) {
console.error(error)
}
})
</script>
{#if appInitialized}
'showing App'
{:else}
'initializing App'
{/if}
alternative component logic when there's just the one store to initialize using an {#await}
block
<script>
import settings from './settings'
</script>
{#await settings.init()}
'initializing store'
{:then}
'show App'
{:catch error}
'Couldn't initialize - '{error.message}
{/await}
or one if there were more stores to initialize
<script>
import settings from './settings'
import store2 from './store2'
import store3 from './store3'
const initStores = [
settings.init(),
store2.init(),
store3.init()
]
</script>
{#await Promise.all(initStores)}
'initializing stores'
{:then}
'showing App'
{:catch error}
'Couldn't initialize - '{error.message}
{/await}
import { writable, get } from 'svelte/store';
import { fs } from '@tauri-apps/api';
function createStore() {
let initialValue = {}
// destructure the store on creation to have 'direct access' to methods
const {subscribe, update, set} = writable(initialValue);
return {
subscribe,
async init() {
const savedSettings = JSON.parse(await fs.readTextFile('./settings.json'))
set(savedSettings);
},
changeSetting(key, value) {
if(!key) return;
const storeValue = get(this)
storeValue[key] = value
update(_ => storeValue)
fs.writeFile({
contents: JSON.stringify(storeValue, null, 2),
path: './settings.json'
})
}
}
}
export default createStore();