A type-safe serialisation and validation wrapper for string storage APIs like localStorage
and sessionStorage
, using Standard Schema for validation.
Synchronous Storage
To create a type safe store, provide a key and a schema. The key will be where the value is stored, and the schema will be used to validate the value when getting it from storage. (When setting a value, no runtime validation happens, only TypeScript type checking.)
Defaults to localStorage
and JSON
serialisation.
import { createStore } from "safeway";
import { z } from "zod";
const store = createStore("count", z.number());
store.set(1); // typed based on schema input (number)
console.log(store.get()); // 1 - typed based on schema output (number | undefined)
store.remove();
console.log(store.get()); // undefined (still typed as number | undefined)
Schemas are allowed to include transformations, in which case store.set
's parameter will be based on the schema's expected input.
import { createStore } from "safeway";
import { z } from "zod";
const store = createStore(
"count",
z.number().transform((count) => ({ count })),
);
store.set(1); // typed based on schema input (number)
console.log(store.get()); // { count: 1 } - typed based on schema output ({ count: number } | undefined)
Custom serialisation
If JSON doesn't cover all your needs, you can provide your own serialisation methods (or use a compatible library like superjson). Should include parse
and stringify
methods.
import { createStore } from "safeway";
import { z } from "zod";
import superjson from "superjson";
const store = createStore("counts", z.set(z.number()), {
serializer: superjson,
});
Custom storage
If you want to use a different storage instance, you can provide it. Should include getItem
, setItem
and removeItem
methods.
import { createStore } from "safeway";
import { z } from "zod";
const store = createStore("count", z.number(), {
storage: sessionStorage,
});
Building a custom store creator
Instead of providing the same config every time, you can build a custom store creator with your own defaults.
import { buildStoreCreator } from "safeway";
import { z } from "zod";
import superjson from "superjson";
const createSuperStore = buildStoreCreator({
serializer: superjson,
storage: sessionStorage,
});
const store = createSuperStore("count", z.number());
Asynchronous Storage
createStore
requires both storage and schemas to be synchronous. If you need asynchronous storage and/or schemas, use createAsyncStore
instead.
The API is the same as createStore
, but all methods return promises.
Defaults to localStorage
and JSON
serialisation.
import { createAsyncStore } from "safeway";
import { z } from "zod";
import AsyncStorage from "@react-native-async-storage/async-storage";
const store = createAsyncStore("count", z.number(), {
storage: AsyncStorage,
});
await store.set(1); // typed based on schema input (number)
console.log(await store.get()); // 1 - typed based on schema output (number | undefined)
await store.remove();
console.log(await store.get()); // undefined (still typed as number | undefined)
Supports all the same customisation options as the synchronous storage, but with asynchronous storage methods allowed. A store creator can be built with buildAsyncStoreCreator
.