Enabling Persistent Storage in IndexedDB
It has now been 60 days since my book; SvelteKit Up and Running, was published 🎉. You would think that would be enough time to recover from technical writing but apparently, it isn’t. As such, I’ll be keeping this post short.
Lately, I’ve been working on an offline first PWA (built with SvelteKit) that makes use of the IndexedDB API to store data locally on the user’s device. To do this, I’ve relied heavily on the Dexie wrapper, which makes managing the schema a breeze. However, Dexie doesn’t provide a way to instantiate or mark a database as “persistent.” This means that at anytime the user’s browser could remove data stored within application’s database. This typically only happens when device storage is running low but I imagine users of the application would be fairly upset to open the app to discover their precious data has mysteriously disappeared.
As previously mentioned, Dexie itself does not provide a method for enabling persistence. However, the authors of Dexie have encountered this dilemma and provide a strategy in their documentation. It’s a simple and effective solution because it only needs to make two calls to the StorageManager API (which is far less complex than the IndexedDB API). The API methods persist() and persisted() are the relevant calls. In my project, I created two functions to wrap these methods and return the resulting promises. As I didn’t want these methods being run in server-side code, I’ve wrapped them in the browser
environment check provided by SvelteKit.
db.js
// Persistent Storage methods as taken
// from https://dexie.org/docs/StorageManager
// adapted for use within SvelteKit
import { browser } from '$app/environment';
// ...
export const persist = async () => {
if(browser) {
console.log('setting persistence...');
return await navigator.storage && navigator.storage.persist && navigator.storage.persist();
}
return {};
}
export const isStoragePersisted = async () => {
if(browser) {
console.log('checking persistence...');
return await navigator.storage && navigator.storage.persisted && navigator.storage.persisted();
}
return {};
}
Within the project’s root layout, I can then check if the client-side database is persistent with a simple call to isStoragePersisted()
. If not, I annoy prompt the user to enable persistent storage and in the prompt’s callback, call persist()
. This then triggers the browser prompt which asks the user if it’s alright to enable persistent storage for the application.
+layout.svelte
import { persist, isStoragePersisted } from 'db.js';
import { onMount } from 'svelte';
import { confirmDialog } from 'toast.js';
onMount(async() => {
const status = await isStoragePersisted();
if(!status) {
confirmDialog('Make storage persistent now?', persist, console.log('denied'), true);
}
});
If you’re curious about what’s happening in toast.js, it’s making use of Svelte Toast to create notifications. If you’d like to see the rest of the project, the code can be viewed on GitHub.