Persistence
Persistence in Hyper-fetch allows you to store cache and request queue data across application sessions. This is
achieved by providing a custom storage implementation that can save and retrieve data from a persistent source like
localStorage or AsyncStorage. This guide will walk you through setting up persistence for both cache and queues.
- How to enable data persistence for caching and request queues.
- The difference between synchronous and asynchronous persistence storages.
- How to implement cache persistence to store data between sessions.
- How to use queue persistence for offline support.
- The limitations of using persistent queues in a browser environment.
Persistence storages can only handle JSON-serializable data. Remember that all file objects need to be converted to a format like Base64 to be stored correctly.
Cache Persistence
To enable cache persistence, you need to provide a custom storage implementation to the Cache instance. This storage
object must have set, get, keys, delete, and clear methods.
// An example using MMKV for React Native
import { MMKV } from "react-native-mmkv";
import { Cache, Client, CacheStorageType } from "@hyper-fetch/core";
export const storage = new MMKV({ id: "my-key" });
const persistenceStorage: CacheStorageType = {
set: (key, data) => {
storage.set(key, JSON.stringify(data));
},
get: (key) => {
const value = storage.getString(key);
return value ? JSON.parse(value) : undefined;
},
keys: () => storage.getAllKeys(),
delete: (key) => storage.delete(key),
clear: () => storage.clearAll(),
};
export const client = new Client({
url: "https://api.my-app.com",
cache: () =>
new Cache({
storage: persistenceStorage,
}),
});
Async Persistence Storages
For asynchronous storages like IndexedDB, you can use the lazyStorage option. This approach loads data on-demand,
which can improve performance by not loading the entire cache into memory at once. Data is fetched when cache.get() is
called, and components are updated once the async operation completes.
// An example using idb-keyval for the browser
import { get, set, del, keys } from "idb-keyval";
import { Cache, Client, CacheAsyncStorageType } from "@hyper-fetch/core";
const asyncStorage: CacheAsyncStorageType = {
set: (key, data) => set(key, data),
get: (key) => get(key),
keys: () => keys(),
delete: (key) => del(key),
};
export const client = new Client({
url: "https://api.my-app.com",
cache: () =>
new Cache({
lazyStorage: asyncStorage,
}),
});
Queue Persistence
Queue persistence ensures that requests are not lost if the application closes or loses internet connection. When the
application restarts, the dispatcher will attempt to resend any queued requests. To enable it, you need to provide a
synchronous storage implementation to the Dispatcher.
When requests are stored in a JSON-serializing storage (e.g. MMKV, AsyncStorage), they are automatically reconstructed
back into proper Request class instances when read from the queue. This means you can use any JSON-based storage
without worrying about losing request functionality.
Persistent queues are not recommended for browser environments due to concurrency issues between multiple tabs or windows. This can lead to duplicated requests and potential data corruption. They are best suited for environments like React Native where only a single instance of the application is running.
Fetching Request Persistence
You can make requests persistent by providing storage to the fetchDispatcher.
import { MMKV } from "react-native-mmkv";
import { Client, Dispatcher, DispatcherStorageType } from "@hyper-fetch/core";
export const storage = new MMKV({ id: "my-key" });
const persistenceStorage: DispatcherStorageType = {
set: (key, data) => {
storage.set(key, JSON.stringify(data));
},
get: (key) => {
const value = storage.getString(key);
return value ? JSON.parse(value) : undefined;
},
keys: () => storage.getAllKeys(),
entries: function* () {
const keys = storage.getAllKeys();
for (const key of keys) {
yield [key, JSON.parse(storage.getString(key) || "null")];
}
},
delete: (key) => storage.delete(key),
clear: () => storage.clearAll(),
};
export const client = new Client({
url: "https://api.my-app.com",
fetchDispatcher: () =>
new Dispatcher({
storage: persistenceStorage,
}),
});
Submit Request Persistence
Similarly, requests sent to submitDispatcher can be persisted by providing storage to the submitDispatcher.
import { MMKV } from "react-native-mmkv";
import { Client, Dispatcher, DispatcherStorageType } from "@hyper-fetch/core";
export const storage = new MMKV({ id: "my-key" });
const persistenceStorage: DispatcherStorageType = {
set: (key, data) => {
storage.set(key, JSON.stringify(data));
},
get: (key) => {
const value = storage.getString(key);
return value ? JSON.parse(value) : undefined;
},
keys: () => storage.getAllKeys(),
entries: function* () {
const keys = storage.getAllKeys();
for (const key of keys) {
yield [key, JSON.parse(storage.getString(key) || "null")];
}
},
delete: (key) => storage.delete(key),
clear: () => storage.clearAll(),
};
export const client = new Client({
url: "https://api.my-app.com",
submitDispatcher: () =>
new Dispatcher({
storage: persistenceStorage,
}),
});
You've learned how to implement persistence in Hyper-fetch!
- You can enable cache persistence using synchronous or asynchronous storages.
- You know how to set up queue persistence for
fetchDispatcherandsubmitDispatcher. - You understand how to create custom storage adapters for persistence.
- You are aware of the concurrency issues with persistent queues in browsers.
