r/typescript • u/atrtde • 17m ago
internships in plain js with no type safety pushed me to build my own open source toolkit (env validation, retry, caching, logging, state, and more...)
during my internships, i worked at a few companies that hadn't migrated to typescript yet. plain javascript, no type safety, no runtime validation.
env vars were just process.env.WHATEVER, no check, nothing telling you it's undefined until something breaks in prod. basically, it was plenty of bugs that a type system or a schema would have caught in two seconds. anyway.
that experience is the origin of zap-studio. i wanted a proper answer to "no type safety, no validation," so i built the first package around that: strict, standard-schema-based validation you can actually trust at runtime, not just at compile time. (following Standard Schema spec, so you can use zod, or whatever library you like).
after that, it became a habit. every time i hit a real problem in a project, instead of hacking around it again, i built a small package for it.
env vars silently merging wrong when two schemas define the same key differently? built a validator that errors on that instead of picking one silently.
retry logic that retries everyone at the same second and causes a second outage? built retry policies with jitter.
small library forcing winston or pino on everyone who imports it, even people who don't want logging? built a tiny logger interface instead.
state management that either shallow-merges everything (zustand) or needs a dozen imports for a cached derived value (jotai)? built a small store for that too.
each package does one thing, and does it well (the unix philosophy): strict typescript, esm, tree-shakeable, zero unnecessary dependencies, runs the same on node, bun, deno, cloudflare workers and the browser.
and because they share the same foundations (standard schema for validation, a small optional logger interface), they connect to each other naturally without needing a framework or a provider to glue them together.
it's mit licensed, i'm the only maintainer right now, and i use all of it in my own projects. 14 packages so far.
and oh, several packages also support open telemetry natively. it's opt-in through a peer dependency, so if you don't register an sdk, it costs nothing. but if you do, you get spans for things like env validation or a fetch call, for free, with no wrapper code on your side.
the repo if you want to take a look: https://github.com/zap-studio/monorepo
example of how to use the packages altogether:
import { createEnvironment } from "@zap-studio/env";
import { createCache } from "@zap-studio/cache";
import { createFetch } from "@zap-studio/fetch";
import { exponentialBackoff, runRetryPolicy } from "@zap-studio/retry";
import { ConsoleLogger } from "@zap-studio/logger";
import { z } from "zod";
const UserSchema = z.object({ id: z.number(), name: z.string() });
const env = createEnvironment({
server: { API_URL: z.string().url() },
runtimeEnv: process.env,
});
const logger = new ConsoleLogger({ minLevel: "debug" });
const cache = createCache<string, unknown>(100, { ttl: 60_000 });
const { api } = createFetch({ baseURL: env.API_URL, logger });
const policy = exponentialBackoff({
maxAttempts: 5,
baseDelayMs: 100,
maxDelayMs: 2_000,
jitter: "full",
});
async function getUser(id: string) {
const cached = cache.get(id);
if (cached) return cached;
const user = await runRetryPolicy(
policy,
() => api.get(`/users/${id}`, UserSchema),
{ logger },
);
cache.set(id, user);
return user;
}