Quick start
Install
Section titled “Install”Each kit installs independently — pick only what you need. The peer dependencies below are kept as peers (not bundled) so you control their versions and so unused features cost nothing.
store-kit
Section titled “store-kit”Persisted Zustand stores with versioned migrations. Install zustand alongside it: store-kit declares Zustand as a peer (>=4.5.0 <6.0.0) so your app picks the patch level, and any other library that uses Zustand shares the same instance.
pnpm add @arshad-shah/store-kit zustandnpm install @arshad-shah/store-kit zustandyarn add @arshad-shah/store-kit zustandbun add @arshad-shah/store-kit zustandfetch-kit
Section titled “fetch-kit”Typed fetch client with retries, timeouts, and an error class hierarchy. Both peers are optional (declared via peerDependenciesMeta): install nothing extra if you only use the core client. Add react if you’ll import the useFetch / useMutation hooks from @arshad-shah/fetch-kit/react, and zod if you want runtime schema validation on responses (the validator is a parse interface, so any compatible library works — zod is just the most common).
# core client onlypnpm add @arshad-shah/fetch-kit# add `react zod` if you'll use the hooks or schema validationnpm install @arshad-shah/fetch-kit# add `react zod` if you'll use the hooks or schema validationyarn add @arshad-shah/fetch-kit# add `react zod` if you'll use the hooks or schema validationbun add @arshad-shah/fetch-kit# add `react zod` if you'll use the hooks or schema validationlog-kit
Section titled “log-kit”Structured logger with pluggable transports. Zero peers, zero runtime deps — the rarest profile in the JS ecosystem. Transports (console, http, file, datadog) are subpath imports under @arshad-shah/log-kit/transports/*, tree-shaken to only what you actually import.
pnpm add @arshad-shah/log-kitnpm install @arshad-shah/log-kityarn add @arshad-shah/log-kitbun add @arshad-shah/log-kitconfig-kit
Section titled “config-kit”Schema-validated config loaded from env vars, files, and remote sources. zod is a required peer because loadConfig returns Zod-inferred types — you can pass any object with a .parse() method at runtime, but the static types assume Zod’s inference. @arshad-shah/log-kit is an optional peer: install it if you want config-kit to surface diagnostics (source resolution, validation failures) through your existing logger instead of console.
pnpm add @arshad-shah/config-kit zodnpm install @arshad-shah/config-kit zodyarn add @arshad-shah/config-kit zodbun add @arshad-shah/config-kit zodA persisted counter
Section titled “A persisted counter”import { createStore } from "@arshad-shah/store-kit";
export const useCounter = createStore({ name: "counter", initial: { count: 0 }, actions: (set) => ({ increment: () => set((s) => ({ count: s.count + 1 })), decrement: () => set((s) => ({ count: s.count - 1 })), }), persist: { storage: "local", version: 1 },});function Counter() { const count = useCounter((s) => s.count); const { increment, decrement } = useCounter.getState(); return ( <> <button onClick={decrement}>-</button> <span>{count}</span> <button onClick={increment}>+</button> </> );}The state survives page refreshes. Bump version and add a migrate map when the shape changes.
A typed API client
Section titled “A typed API client”import { createClient } from "@arshad-shah/fetch-kit";
export const api = createClient({ baseUrl: "https://api.example.com", timeout: 10_000, retry: { attempts: 3, backoff: "exponential" }, auth: () => localStorage.getItem("token"),});
const user = await api.get<User>("/users/me");A React data hook
Section titled “A React data hook”import { useFetch } from "@arshad-shah/fetch-kit/react";
function Profile() { const { data, error, loading, refetch } = useFetch<User>(api, "/users/me"); if (loading) return <Spinner />; if (error) return <button onClick={refetch}>Retry</button>; return <h1>{data!.name}</h1>;}A structured logger
Section titled “A structured logger”import { createLogger } from "@arshad-shah/log-kit";import { consoleTransport } from "@arshad-shah/log-kit/transports/console";
export const log = createLogger({ level: "info", transports: [consoleTransport({ pretty: process.env.NODE_ENV !== "production" })],});
const end = log.mark("server.boot");await startServer();end({ port: 3000 });Typed config from env
Section titled “Typed config from env”import { z } from "zod";import { loadConfig, processEnvSource, dotenvFileSource } from "@arshad-shah/config-kit";
export const config = await loadConfig({ schema: z.object({ NODE_ENV: z.enum(["development", "production", "test"]).default("development"), PORT: z.coerce.number().int().positive().default(3000), DATABASE_URL: z.string().url(), }), sources: [dotenvFileSource(".env"), processEnvSource()],});
config.PORT; // number, fully typedThat’s the whole API surface for typical use. Read the package overviews next: