storage

Save small values in the browser or in cookies with types, and read the same cookies on the server.

Device featuresClient ServerBuilt onlocalStoragecookies

Every export of kitr/storage, kitr/storage/react, kitr/storage/server, kitr/storage/provider, read from the published type definitions. The guide shows them in use.

kitr/storage

createAsyncStorageDriver

function createAsyncStorageDriver(options: {
  name: string;
  load(): Promise<Record<string, string>>;
  write(key: string, raw: string): Promise<void>;
  erase(key: string): Promise<void>;
  onError?: (error: unknown) => void;
}): StorageDriver;

Build a driver over an async native store. load runs once at boot and fills the cache; write/erase run in the background after each change, so reads stay synchronous.

createMemoryKvDriver

function createMemoryKvDriver(): KvDriver;

createMemoryStorageDriver

function createMemoryStorageDriver(name?: string): StorageDriver;

In-memory driver: tests, the server, and session items on native platforms.

createWebStorageDriver

function createWebStorageDriver(name: StorageArea): StorageDriver | null;

Web Storage as a driver. Null outside a browser, so items fall back to their defaults.

defineCookie

function defineCookie<T = string>(name: string, options: CookieItemOptions<T>): CookieItem<T>;

A typed cookie, readable on both sides and writable from the browser or the server.

const locale = defineCookie('locale', { default: 'en', maxAge: 60 * 60 * 24 * 365 })
locale.get()                      // client: document.cookie
locale.get(request.headers)       // server: any Headers / Cookie header string
locale.set('de')                  // client
cookies(event).set(locale, 'de')  // server (kitr/storage/server)

defineKv

function defineKv<T>(name: string, options: KvItemOptions<T>): KvItem<T>;

A value shared by the whole app on the server (scope: 'app'): feature state, counters, cached lookups. Backed by memory unless config/storage.ts sets a kv driver (Redis, Cloudflare KV, a file... anything with get/set/remove).

const banner = defineKv('banner', { default: { text: '' } })
await banner.set({ text: 'Maintenance at 22:00' })

defineStorage

function defineStorage<T>(name: string, options: StorageItemOptions<T>): StorageItem<T>;

A typed item in localStorage or sessionStorage.

const sidebar = defineStorage('sidebar', { default: { open: true } })
sidebar.get().open; sidebar.set((s) => ({ ...s, open: false }))

Safe everywhere: on the server (or when storage is blocked) get() returns the default and set() is a no-op, so the same module renders on both sides.

getKvDriver

function getKvDriver(): KvDriver;

getStorageDriver

function getStorageDriver(name: StorageArea): StorageDriver | null;

listStorage

function listStorage(name: StorageArea): Array<{
  key: string;
  raw: string | null;
}>;

Every key in an area, for the devtools panel.

logStorage

function logStorage(app: KitrApp, label: string, data?: unknown): void;

notifyCookiesChanged

function notifyCookiesChanged(): void;

Call after something else wrote document.cookie so useCookie() re-reads.

parseCookies

function parseCookies(header: string | null | undefined): Record<string, string>;

Parse a Cookie header into a map. Values are decoded with decodeURIComponent.

removeStorageKey

function removeStorageKey(name: StorageArea, key: string): void;

serializeCookie

function serializeCookie(name: string, value: string | null, options?: CookieOptions): string;

Build a Set-Cookie value. value === null expires the cookie.

setCookieDefaults

function setCookieDefaults(defaults: Omit<CookieOptions, 'name'>): void;

setKvDriver

function setKvDriver(driver: KvDriver): void;

Set by the storage provider from config/storage.ts (kv). Any unstorage-shaped driver works.

setStorageDriver

function setStorageDriver(driver: StorageDriver, areas?: {
  local?: StorageDriver;
  session?: StorageDriver;
}): void;

Replace the Web Storage backing with a platform driver. Called by the storage provider when a platform registered one (app.drivers.register('storage', driver)); session items keep an in-memory driver unless areas says otherwise.

setStoragePrefix

function setStoragePrefix(prefix: string): void;

Set by the provider from config/storage.ts; items created later pick it up.

defineConfig

const defineConfig: <const C extends StorageConfig>(config: C) => C;

jsonCodec

const jsonCodec: <T>() => Codec<T>;

stringCodec

const stringCodec: Codec<string>;

Codec

interface Codec<T> {
  encode(value: T): string;
  decode(raw: string): T;
}

CookieItem

interface CookieItem<T> {
  readonly name: string;
  readonly default: T;
  readonly options: CookieOptions;
  /** Read from `document.cookie`, or from the given source on the server. */
  get(source?: CookieSource): T;
  /** Write `document.cookie` (client). On the server use `cookies(event).set(item, value)`. */
  set(value: T, options?: CookieOptions): void;
  remove(): void;
  subscribe(listener: (value: T) => void): () => void;
  /** The `Set-Cookie` header value for this item. */
  serialize(value: T | null, options?: CookieOptions): string;
  /** Parse one raw cookie value into T (or the default). */
  decode(raw: string | undefined): T;
}

CookieItemOptions

interface CookieItemOptions<T> extends CookieOptions {
  default: T;
  schema?: StandardSchemaV1<unknown, T>;
  /** Default: JSON for objects, plain string for string defaults. */
  codec?: Codec<T>;
}

CookieOptions

interface CookieOptions {
  path?: string;
  domain?: string;
  /** Seconds. */
  maxAge?: number;
  expires?: Date;
  secure?: boolean;
  /** Server-set cookies only; the browser cannot write httpOnly cookies. */
  httpOnly?: boolean;
  sameSite?: 'lax' | 'strict' | 'none';
}

KvDriver

interface KvDriver {
  get(key: string): Promise<string | null>;
  set(key: string, raw: string, options?: {
    ttl?: number;
  }): Promise<void>;
  remove(key: string): Promise<void>;
  keys?(prefix?: string): Promise<string[]>;
}

KvItem

interface KvItem<T> {
  readonly key: string;
  readonly default: T;
  get(): Promise<T>;
  set(value: T | ((current: T) => T), options?: {
    ttl?: number;
  }): Promise<void>;
  remove(): Promise<void>;
}

KvItemOptions

interface KvItemOptions<T> {
  default: T;
  schema?: StandardSchemaV1<unknown, T>;
  codec?: Codec<T>;
  /** Milliseconds to keep a value. */
  ttl?: number;
}

StorageConfig

interface StorageConfig {
  /** Prefix for every storage key and cookie name, e.g. `myapp:`. Default none. */
  prefix?: string;
  /** Defaults merged into every cookie. */
  cookie?: Omit<CookieOptions, 'name'>;
  /** Server-side key-value store behind `defineKv` (app scope). Default: in-memory. */
  kv?: KvDriver;
}

StorageDriver

interface StorageDriver {
  name: string;
  /** Load every stored key into memory before the first render. */
  hydrate?(): Promise<void>;
  get(key: string): string | null;
  set(key: string, raw: string): void;
  remove(key: string): void;
  keys(): string[];
  /** Changes made elsewhere (another tab, the native side). */
  subscribe?(listener: (key: string | null) => void): () => void;
}

What a platform supplies to back defineStorage items: Capacitor Preferences on mobile, the Tauri store or an Electron JSON file on desktop, Web Storage in the browser. Reads are synchronous from a cache hydrate() fills at boot; writes go through in the background.

StorageItem

interface StorageItem<T> {
  readonly key: string;
  readonly area: StorageArea;
  get(): T;
  set(value: T | ((current: T) => T)): void;
  remove(): void;
  /** Fires on set/remove here and in other tabs (`storage` event). */
  subscribe(listener: (value: T) => void): () => void;
  /** The default, for server snapshots. */
  readonly default: T;
}

StorageItemOptions

interface StorageItemOptions<T> {
  /** Returned when the key is missing, unparsable, invalid or on the server. */
  default: T;
  area?: StorageArea;
  /** Validate what comes back from storage (Standard Schema: zod, valibot...). Invalid → default. */
  schema?: StandardSchemaV1<unknown, T>;
  codec?: Codec<T>;
  /** Bump when the shape changes; stored values with another version are migrated or dropped. */
  version?: number;
  migrate?: (raw: unknown, fromVersion: number) => T;
  /** Keep this many ms, then treat as missing. Stored beside the value. */
  ttl?: number;
}

CookieSource

type CookieSource = string | null | undefined | Headers | {
  cookie?: string | null;
};

Anything that can hold cookies: a Cookie header string, a Headers object or document.

StorageArea

type StorageArea = 'local' | 'session';

kitr/storage/react

CookieHeader

function CookieHeader({ value, children }: {
  value: string | null | undefined;
  children: ReactNode;
}): import("react").FunctionComponentElement<import("react").ProviderProps<string | null | undefined>>;

useCookie

function useCookie<T>(item: CookieItem<T>): [
  T,
  (value: T) => void,
  () => void
];

Read and write a cookie from a component. On the server the value comes from <CookieHeader> when present, otherwise the default.

useStorage

function useStorage<T>(item: StorageItem<T>): [
  T,
  (value: T | ((current: T) => T)) => void,
  () => void
];

Read and write a storage item from a component. Hydration-safe: the server (and the first client render) see the default, then the stored value applies, so markup always matches.

kitr/storage/server

cookies

function cookies(event: CookieEventLike): ServerCookies;

function cookies(request: Request, responseHeaders: Headers): ServerCookies;

Typed cookies on the server: cookies(event) in a controller or middleware, cookies(request, responseHeaders) anywhere you only have the Fetch primitives.

CookieEventLike

interface CookieEventLike {
  req: {
    headers: Headers;
  };
  res: {
    headers: Headers;
  };
}

The parts of an h3 v2 event (or anything request/response shaped) that cookies need.

ServerCookies

interface ServerCookies {
  get<T>(item: CookieItem<T>): T;
  /** Appends a `Set-Cookie` header; `httpOnly` and the other item options apply. */
  set<T>(item: CookieItem<T>, value: T, options?: CookieOptions): void;
  remove(item: CookieItem<unknown>): void;
  /** The raw `Cookie` header, for `<CookieHeader>` during SSR. */
  readonly header: string | null;
}

kitr/storage/provider

storageProvider

const storageProvider: import("@kitr/core").KitrProvider;

Applies config/storage.ts (key prefix, cookie defaults, kv driver), switches defineStorage items to the platform's native driver when one is registered, hydrates it before the first render, and contributes the Storage panel to the devtools.