The boring parts of every app, already written
TypeScript packages for the work every app repeats: HTTP calls, retries, caching, validation, permissions, webhooks and logs. Install one, or all of them.
npm install @zap-studio/permit- 171 stars on GitHub
- 7k downloads a month
- MIT licensed
Example
Four packages in one request. It reads the cache first, checks the data, tries again when the call fails, and logs every step.
import { createCache } from "@zap-studio/cache";
import { createFetch } from "@zap-studio/fetch";
import { ConsoleLogger } from "@zap-studio/logger";
import { exponentialBackoff, runRetryPolicy } from "@zap-studio/retry";
import { z } from "zod";
const UserSchema = z.object({ id: z.number(), email: z.email() });
type User = z.infer<typeof UserSchema>;
const logger = new ConsoleLogger({ minLevel: "info" });
const users = createCache<string, User>(100, { ttl: 60_000 });
const { api } = createFetch({ baseURL: "https://api.example.com", logger });
const policy = exponentialBackoff({
maxAttempts: 3,
baseDelayMs: 100,
maxDelayMs: 2_000,
});
export const getUser = async (id: string): Promise<User> => {
const cached = users.get(id);
if (cached) {
return cached;
}
const user = await runRetryPolicy(policy, () =>
api.get(`/users/${id}`, UserSchema),
);
users.set(id, user);
return user;
};Packages
Pick the one you need. Each package works on its own, and each one lists the runtimes it supports.
164 kB for all of them, gzipped
| Package | Gzipped | Node | Bun | Deno | Workers | Browser |
|---|---|---|---|---|---|---|
| @zap-studio/cache | 5.1 kB | Runs here | Runs here | Runs here | Runs here | Runs here |
| @zap-studio/env | 7.7 kB | Runs here | Runs here | Runs here | Runs here | Runs here |
| @zap-studio/fetch | 5.3 kB | Runs here | Runs here | Runs here | Runs here | Runs here |
| @zap-studio/logger | 4.3 kB | Runs here | Runs here | Runs here | Runs here | Runs here |
| @zap-studio/monads | 4.2 kB | Runs here | Runs here | Runs here | Runs here | Runs here |
| @zap-studio/oxfmt | 483 B | Never ships to your app | Never ships to your app | Never ships to your app | Never ships to your app | Never ships to your app |
| @zap-studio/oxlint | 43 kB | Never ships to your app | Never ships to your app | Never ships to your app | Never ships to your app | Never ships to your app |
| @zap-studio/permit | 4.9 kB | Runs here | Runs here | Runs here | Runs here | Runs here |
| @zap-studio/react-hooks | 111 kB | Runs here | Runs here | Runs here | Runs here | Runs here |
| @zap-studio/retry | 7.1 kB | Runs here | Runs here | Runs here | Runs here | Runs here |
| @zap-studio/store | 2.5 kB | Runs here | Runs here | Runs here | Runs here | Runs here |
| @zap-studio/validation | 2.3 kB | Runs here | Runs here | Runs here | Runs here | Runs here |
| @zap-studio/webhooks | 6.4 kB | Runs here, with one catch | Runs here | Runs here | Runs here | Runs here |
| @zap-studio/webmcp | 3.3 kB | Runs here, with one catch | Runs here, with one catch | Runs here, with one catch | Runs here, with one catch | Runs here, with one catch |
Gzipped whole-package sizes, measured at build time. You ship only what you import.
Philosophy
Every package is built to the same four constraints.
- Type-safe
- Your schema decides the types. Wrong data fails at the call, with a clear error.
- Runtime-agnostic
- Web APIs first: fetch, Request, AbortSignal, crypto. Same code everywhere.
- Composable
- Small pieces that fit together. A retry policy is an argument. So is a logger, so is a schema.
- Tree-shakeable
- One import path per feature. Import one, ship one.
Start with one package
Install only what you need and ship it the same day. Every package is typed end to end, tree-shakeable, and runs on Node, Bun, Deno, Cloudflare Workers and the browser.
Make your first typed call