Storage
Where the browser client keeps the session, and how to change it.
The browser client persists the session so a reload does not sign the user out. Which medium it uses is your choice.
Without a storage option it picks Storage.Local when window exists, and an
in-memory store of the client's own everywhere else. A client module that is
also evaluated during server rendering therefore never touches localStorage,
and two clients created on the server never share a session.
import { createClient, Storage } from '@nulvless/nulvless-js';
createClient(url, key, { storage: Storage.Local });Browser only. createServerClient has no session storage — a server holds no
single user's session, so storage and storageKeys are not part of
ServerClientOptions.
The adapters
Storage.LocallocalStorageOptionalSurvives page reloads and browser restarts.
Storage.SessionsessionStorageOptionalCleared when the tab or browser closes.
Storage.MemoryMapOptionalLost on reload. One instance shared by every client that uses it — create a
new MemoryStorageAdapter() when each client needs its own.
Storage.CacheCache Storage APIOptionalSurvives reloads, and is reachable from service workers.
Choosing between them is a product decision, not a technical one:
- Local keeps people signed in between visits. What most apps want.
- Session signs them out when the tab closes. Shared machines, higher-risk products.
- Memory persists nothing — correct on the server, and in tests where leaking state between cases is worse than re-authenticating.
- Cache is for when a service worker needs the session too.
Keys
Two entries are written, and both key names can be changed:
createClient(url, key, {
storageKeys: {
token: 'my-app-token',
accounts: 'my-app-accounts',
},
});They default to nlv-<slug>-token and nlv-<slug>-token-accounts. The accounts
entry only matters with multi-account
enabled.
A custom adapter
Any object with these three methods works:
interface NulvlessStorage {
getItem(key: string): Promise<string | null>;
setItem(key: string, value: string): Promise<void>;
removeItem(key: string): Promise<void>;
}All three are async, so an adapter backed by IndexedDB, a native secure store, or an encrypted wrapper fits without the SDK knowing the difference:
const encrypted: NulvlessStorage = {
async getItem(key) {
const raw = await SecureStore.getItemAsync(key);
return raw ? decrypt(raw) : null;
},
async setItem(key, value) {
await SecureStore.setItemAsync(key, encrypt(value));
},
async removeItem(key) {
await SecureStore.deleteItemAsync(key);
},
};
createClient(url, key, { storage: encrypted });