Skip to content
Zap Studio
cache
Esc
navigateopen⌘Jpreview
Docs

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

PackageGzipped
@zap-studio/cache5.1 kB
@zap-studio/env7.7 kB
@zap-studio/fetch5.3 kB
@zap-studio/logger4.3 kB
@zap-studio/monads4.2 kB
@zap-studio/oxfmt483 B
@zap-studio/oxlint43 kB
@zap-studio/permit4.9 kB
@zap-studio/react-hooks111 kB
@zap-studio/retry7.1 kB
@zap-studio/store2.5 kB
@zap-studio/validation2.3 kB
@zap-studio/webhooks6.4 kB
@zap-studio/webmcp3.3 kB

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