Morrys Repositories

Morrys Repositories

  • Docs
  • GitHub

›caching

wora

  • wora

caching

  • Cache Persist
  • Relay Store
  • Apollo Cache

networking

  • NetInfo
  • Detect Network

offline

  • Offline Architecture
  • Thinking Offline
  • Offline First
  • Relay Offline
  • Apollo Offline

Cache Persist

wora/cache-persist allows you, with very little effort of work, to manage the state within your application and its persistence in storage.

All you need to know to use it is the Cache class and the configurations it makes available to you.

Let’s start right now by viewing the wora/cache-persist Cache:

export interface ICache {
    // functions to read
    get(key: string): any;
    getState(): Readonly<DataCache>;
    getAllKeys(): Array<string>;
    has(key: string): boolean;
    // functions to write
    set(key: string, value: any): void;
    delete(key: string): void;
    remove(key: string): void;
    purge(): void;
    replace(data: any): void;
    // functions to manage persistence
    isRehydrated(): boolean;
    restore(): Promise<DataCache>;
    flush(): Promise<void>;
}

export type DataCache = {
    [key: string]: any;
};

As you can see from the interface above, there are 3 categories of functions:

  • to read: these functions, synchronous, allow you to read state data
  • to write: these functions, synchronous, allow you to write data in the state
  • to manage persistence: these functions allow you to manage the persistence of state within the storage

The read and write functions are very similar to those of the Map object, so I do not need to add anything else while the restore, isRehydrated and flush functions are described below so that you better understand when and how to use them.

  • restore: it is the most important function of all, in fact it must necessarily be used in order to connect the store to its reference storage and initialize the cache state. Apart from special situations, e.g. Server Side Rendering, this function is recommended to be called when starting the application.
  • flush: this function allows you to make sure that the storage is synchronized with state data.
  • isRehydrated: this function lets you know if the cache has been connected with the storage via the restore function

Well, now we can see how to use the cache within our application.

First, let’s install the packages we need:

  • wora/cache-persist using yarn or npm:
yarn add @wora/cache-persist

Now, I can show you the simplest use case:

import { Cache } from "@wora/cache-persist";
export const cache = new Cache();

// ...
await cache.restore();
// ...

cache.set('name', 'lorenzo');
const name = cache.get('name');

That’s all :)

Obviously only in very rare cases are we lucky enough to have to manage such a simple case where we use the localStorage (or for ReactNative the AsyncStorage) without one of these problems:

  • How can I initialize the cache with data?
  • There is data in the storage that is not compatible with the new version of the application, how can I migrate it?
  • How can I use the same storage to manage different states? (e.g. different state based on the logged in user)
  • How can I use indexedDB?
  • How can I use session storage?
  • How can I prevent some state data from being saved within the storage?
  • How can I encrypt the data before it is saved in the storage?
  • How do I handle storage errors when saving data?

The library gives an answer to these problems (and not only these) through its configurations.

For this reason I will start by describing all its configurations and then I will show you how to use them to answer the problems indicated above

Cache Options:

prefix: [Optional][Default: cache] when this option is defined, the prefixLayer mutation included in the library is used in order to add / remove the prefix to persisted keys.

serialize: [Optional][Default: true] when this option is true, the jsonSerialize mutation included in the library is used in order to handles JSON serialization and deserialization of values

mutateKeys: [Optional] with this option you can configure an array of mutation functions that will be executed in order to change the keys while writing / reading in the storage. The prefix option will insert the prefixLayer function as the first in the list.

mutateValues: [Optional] with this option you can configure an array of mutation functions that will be executed in order to change the values while writing / reading in the storage. The serialize option will insert the jsonSerialize function as the last in the list.

webStorage: [Optional][Default: local] with this option it is possible to decide to use the sessionStorage by enhancing the property with session

storage: [Optional][Default: localStorage/AsyncStorage] when you need to use custom storage or the indexedb, this property must be set.

throttle: [Optional][Default: 500] Defines the expiration interval of the debounce

errorHandling: [Optional] Allows you to define the callback function that is executed in case errors are raised during the write process to the storage.

disablePersist: [Optional][Default: false] Allows you to use the cache as an inmemory cache.

export type CacheOptions = {
    initialState?: DataCache;
    serialize?: boolean;
    prefix?: string | undefined | null;
    mergeState?: (restoredState?: DataCache, initialState?: DataCache) => Promise<DataCache> | DataCache;
    mutateKeys?: Array<IMutateKey>;
    mutateValues?: Array<IMutateValue>;
    storage?: ICacheStorage;
    webStorage?: 'local' | 'session';
    disablePersist?: boolean;
    errorHandling?: (cache: ICache, error: any) => boolean;
    throttle?: number;
};

How can I initialize the cache with data?

import { Cache } from "@wora/cache-persist";
const initialState = {
  name: "lorenzo",
}

const cache = new Cache({
  initialState,
});
// ...
await cache.restore();
// ...
const name = cache.get('name');

There is data in the storage that is not compatible with the new version of the application, how can I migrate it?

import { Cache } from "@wora/cache-persist";
const initialState = {
  name: "lorenzo",
};

const  mergeState = (restoredState, initialState) => {
  const migrationState = // your logic 
  return migrationState;
};

const cache = new Cache({
  initialState,
  mergeState
});
// ...
await cache.restore();
// ...
const name = cache.get('name');

How can I use the same storage to manage different states? (e.g. different state based on the logged in user)

import { Cache } from "@wora/cache-persist";

export const cacheApp = new Cache({
   prefix: 'app'
});

export getUserCache = async (user: string) => {
  const cacheUser = new Cache({
     prefix: user
  });
  await cacheUser.restore();
  return cacheUser;
}

// ...
await cacheApp.restore();
// ...

How can I use indexedDB?

import {Cache, CacheStorage, CacheOptions } from "@wora/cache-persist";
import { IDBStorage } from '@wora/cache-persist/lib/idbstorage';

const idbStorages: CacheStorage[] = IDBStorage.create( {
    name: "cache", 
    storeNames: ["persist"]
});

const idb: CacheOptions = {
    storage: idbStorages[0],
    serialize: false,
}

const cache = new Cache(idb);

// ...
await cache.restore();
cache.set('name', 'lorenzo');
const name = cache.get('name')

How can I use session storage?

import { Cache } from "@wora/cache-persist";

const cache = new Cache({
  webStorage: 'session',
});
// ...
await cache.restore();
// ...
const name = cache.get('name');

How can I prevent some state data from being saved within the storage?

import { filterKeys } from '@wora/cache-persist/lib/layers/filterKeys';
import { IMutateKey } from '@wora/cache-persist';

const filterNoPersist: IMutateKey = filterKeys(key => !key.includes("exclude"));

const cache = new Cache({
    mutateKeys: [filterNoPersist],
});
// ...

await cache.restore();

cache.set('include', 'persist');
cache.set('exclude', 'do not persist');

How can I encrypt the data before it is saved in the storage?

import { Cache, IMutateValue, mutateValuesLayer } from '@wora/cache-persist';

export const encrypt: IMutateValue = mutateValues(
    (value) => crypt(value),
    (value) => decrypt(value),
);

const cache = new Cache({
    mutateValues: [encrypt],
});
// ...

await cache.restore();

cache.set('name', 'lorenzo');

How do I handle storage errors when saving data?

import { Cache } from '@wora/cache-persist';

const errorHandling = (cache: Cache, error: Error) => {
  // your logic
};

const cache = new Cache({
    errorHandling,
});
// ...

await cache.restore();

cache.set('name', 'lorenzo');

What’s next

For those interested in knowing how it was implemented, I recommend reading: State persistence in JavaScript — wora/cache-persist — Deep into its Source Code

Last updated on 2021-1-19 by Lorenzo Di Giacomo
← woraRelay Store →
  • Cache Options:
    • How can I initialize the cache with data?
    • There is data in the storage that is not compatible with the new version of the application, how can I migrate it?
    • How can I use the same storage to manage different states? (e.g. different state based on the logged in user)
    • How can I use indexedDB?
    • How can I use session storage?
    • How can I prevent some state data from being saved within the storage?
    • How can I encrypt the data before it is saved in the storage?
    • How do I handle storage errors when saving data?
  • What’s next
Morrys Repositories
Docs
about wora
Community
User Showcase
More
GitHubStar
Follow @m0rrys
Facebook Open Source
Copyright © 2021 Lorenzo Di Giacomo