yes
v0.2.7
Published
The ultimate primitive for async TypeScript: retries, caching, rate limits and validation as fast, typed options on async functions
Readme
yes adds retries, caching, deduplication, timeouts, rate limits and validation, as typed options, to the async function you, or your agent, already wrote, with its exact type kept. Where an agent would hand-roll a retry loop and a Map cache, a little differently in every file, it writes one option whose behavior is documented and tested once, and you review one line.
It is built to disappear. An operation is plain TypeScript: awaited, it returns what your function returns and throws what it throws. A string-key cache hit takes under a microsecond, a cached query ships about 5 KB gzipped on a server and under 6 KB in a browser, with no dependencies, every call path is benchmarked against a raw async call with a budget pnpm bench enforces, and an area you never use ships nothing.
Built inside Pullfrog to keep its GitHub API calls reliable when GitHub says no, and running there in production. Schemas and tools build on the same primitive today, and MCP, REST and RPC are coming. Start with one function: declare a read with yes.query and a write with yes.mutation.
import * as yes from "yes";
const getUser = yes.query({
run: async (id: string) => db.users.findUnique({ where: { id } }),
ttl: 60_000,
retry: [100, 500],
});
await getUser("abc"); // executes
await getUser("abc"); // cache hit
const addCredits = yes.mutation({
run: async (input: { userId: string; amount: number }) => db.credits.add(input),
});
await Promise.all([addCredits({ userId: "abc", amount: 1 }), addCredits({ userId: "abc", amount: 1 })]); // both runnpm install yesyes runs on Node 22 or later, Bun and Deno, and bundles for browsers and edge runtimes, Cloudflare Workers and Next.js edge routes included, with no Node built-in to polyfill. Its types need TypeScript 5.4 or later and check clean on TypeScript 7.
To teach Claude Code, Codex or Cursor to reach for yes instead of hand-rolling a retry loop, install its Agent Skill:
npx skills add colinhacks/yesAsk it anything
| Ask it | The answer | Read more |
| --- | --- | --- |
| Retry when it throws? | yes, with retry: [100, 500] | Retries |
| Cache the result? | yes, with ttl: 60_000 | Caching |
| Share concurrent calls? | yes, always, for a read | Reads and writes |
| Keep my function's type? | yes, exactly typeof run | Types |
| Validate what goes in and out? | yes, with any Standard Schema | Validation |
| Stop when I abort? | yes, with ctx.signal | Cancellation |
| Survive a 529 from a model API? | yes, with retry, timeout and a shared rateLimit | Model APIs |
| Give up on a slow attempt? | yes, with timeout: 5_000 | Timeouts and limits |
| Stay under an API's rate limit? | yes, with rateLimit or concurrency | Timeouts and limits |
| Share a cache across servers? | yes, with yes.redisCache | Stores |
| Let a model call it? | yes, as a Standard Tool | Tools |
| Share or cache a write? | no: a mutation always runs | Reads and writes |
Coming soon
Every layer planned for yes is an operation underneath, so a retry or a ttl will mean the same thing on each. This is direction, not commitment, and the sketches can change:
- MCP servers: tools served to any MCP client with
yes.mcp, on the official SDK. - REST: type-safe, OpenAPI-first routes and routers with
yes.rest. - RPC and a typed client: named procedures documented in OpenAPI, and a client typed from the router.
Docs
Every page is in docs/content/docs/, starting with the quick start. pnpm run docs serves the site at http://localhost:4000.
Develop
The repo runs on Node 24, which executes TypeScript files directly. A script that imports yes's source runs with --conditions=yes-src, so package.json's #hash import resolves to ops/ rather than to a built dist/:
pnpm install
pnpm test # tsc, then vitest with type assertions
node --conditions=yes-src play.ts # a runnable tour of the API
pnpm run docs # the docs site at http://localhost:4000
pnpm --filter yes-docs examples # type-check every example in the docs