Fewer moving parts. Better async code.
Tuple-based errors, structured concurrency, and resource management for TypeScript — built on the primitives you already have.
import { safeTry } from "yieldless/error";
import { runTaskGroup } from "yieldless/task";
const [err, repo] = await safeTry(loadRepository(id));
if (err) {
return [err, null] as const;
}
return await runTaskGroup(async (group) => {
const refs = group.spawn((s) => loadRefs(repo.path, s));
const status = group.spawn((s) => loadStatus(repo.path, s));
return { repo, refs: await refs, status: await status };
});Design goals
What it optimizes for
Native control flow
Built on Promise, AbortController, AsyncLocalStorage, and Symbol.asyncDispose. No scheduler, no runtime, no magic.
Visible failure paths
If something can fail, the tuple is right there. No hidden exception channel unless you choose to rethrow.
Incremental adoption
Use one module at a time. No framework wrapper, no global runtime, no all-or-nothing commitment.
Modules
Pick what you need
Every module works independently. Import one, or compose several around the same [error, value] and AbortSignal conventions.
yieldless/error
Tuple-based error handling
yieldless/task
Structured concurrency
yieldless/queue
Backpressure-aware handoff
yieldless/limiter
Semaphores and rate limits
yieldless/cache
TTL/LRU read-through cache
yieldless/batcher
DataLoader-style keyed batches
yieldless/breaker
Circuit breaking for dependencies
yieldless/router
Tuple HTTP route handlers
yieldless/ipc
Typed Electron IPC bridge
Plus result, resource, schedule, pubsub, singleflight, schema, node, and test.
Ready to simplify?
Start with one module. Adopt more when they earn their place. Nothing wraps your application unless you ask it to.