Skip to content

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.

Back to all packages