Skip to main content
Version: v8.0.0

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.

What you'll learn
  1. How to enable data persistence for caching and request queues.
  2. The difference between synchronous and asynchronous persistence storages.
  3. How to implement cache persistence to store data between sessions.
  4. How to use queue persistence for offline support.
  5. The limitations of using persistent queues in a browser environment.
note

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.

caution

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,
}),
});

Congratulations!

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 fetchDispatcher and submitDispatcher.
  • You understand how to create custom storage adapters for persistence.
  • You are aware of the concurrency issues with persistent queues in browsers.