返回资讯中心
外部精选
软件工程
#开发者实时流

Unthrown: Explicit errors as values for TS – a third channel for the unexpected

Explicit errors as values for TypeScript, with a separate defect channel for the unexpected

Hacker News 最新baptou122026/10/21 · 24 天前12 分钟阅读

以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。

unthrown

Explicit errors as values for TypeScript, with a separate defect channel for the unexpected

Documentation · Get Started · Why unthrown?

LLM documentation index · Agent skill · Migration skill

Ordinary errors are unthrown — returned as values, not flung up the stack. Only a true defect ever throws, and only at get.

Most errors-as-values libraries model anticipated failures in Result<T, E> but have no channel for the unexpected — a thrown TypeError, an un-triaged promise rejection, a bug in a callback. Fold both into the same E and a bug starts to look like a domain error.

unthrown keeps a third runtime state — a Defect — that is invisible to the type. E lists only your anticipated errors; anything unexpected becomes a defect that short-circuits to the edge, where you log it and return a 500.

See Why unthrown? for the comparison with neverthrow, boxed, and effect.

pnpm add unthrown

No peer dependencies — the exhaustive error matcher is built-in and exported as match / P.

import { fromPromise, P, TaggedError } from "unthrown";

// Our modeled domain failure: class NotFound extends TaggedError("NotFound") {} // What the upstream fetchUser rejects with when the user is absent: class NotFoundError extends Error {}

// Cross an async boundary — every rejection MUST be triaged into E or a defect. // defect is injected as qualify's second argument. const user = fromPromise(fetchUser(id), (cause, defect) => cause instanceof NotFoundError ? new NotFound() : defect(cause), );

// Handle every channel once, at the edge — no surrounding try/catch. const status = await user.match({ ok: () => 200, // errCases receives the exhaustive matcher — name every case of E; adding // one to E is a compile error here until you handle it: errCases: (matcher) => matcher.with(P.tag("NotFound"), () => 404), defect: (cause) => { logger.error(cause); // everything unexpected return 500; }, });

A throw inside any combinator (.map, .flatMap, …) is caught and becomes a defect, so the edge of your program needs a single match and no try/catch.

Package Description

unthrown The core Result / AsyncResult, interop, TaggedError, built-in exhaustive error matching.

@unthrown/vitest Vitest matchers: toBeOk, toBeOkWith, toBeErr, toBeErrWith, toBeErrTagged, toBeDefect, toBeDefectWith.

@unthrown/saga SagaAsync(): a sequence of steps with compensating undos, unwound LIFO on the first failure.

@unthrown/effect Effect interop: Result ↔ Exit (bijection), Effect’s Result, Effect (Effect 4).

@unthrown/neverthrow neverthrow interop: Result ↔ Result, AsyncResult ↔ ResultAsync.

@unthrown/boxed Boxed interop: Result ↔ Result, AsyncResult ↔ Future<Result>.

@unthrown/prisma Prisma Client extension: try* query methods returning AsyncResult, per-operation errors.

@unthrown/drizzle Drizzle ORM Postgres database: every query an AsyncResult, tagged constraint violations, Result transactions.

@unthrown/orpc oRPC (v2) bridge: Result-returning handlers, AsyncResult client, typed errors end-to-end.

@unthrown/pothos Pothos plugin: GraphQL fields resolved by a Result, each failure a typed member of the field's result union.

@unthrown/standard-schema fromSchema / fromSchemaAsync: any Standard Schema validator into a Result.

@unthrown/oxlint oxlint plugin. recommended preset: no-ambiguous-error-type, no-async-result-race, no-catch-all-pattern, no-unhandled-result, no-unused-matcher, prefer-async-result. Opt-in: no-get-or-throw, no-throw, prefer-pre-lifted.

Examples

Four small runnable packages live under ./examples: three model one checkout end to end — the domain, @unthrown/prisma persistence, and an oRPC edge — and the fourth adopts unthrown over existing error types with no TaggedError at all. Each is compiled and covered by tests, unlike the snippets above. See the annotated walkthroughs.

This is a pnpm + turbo monorepo. Common tasks:

pnpm install pnpm build # build all packages (tsdown, dual CJS/ESM) pnpm test # run the Vitest suites pnpm typecheck # tsc --noEmit across packages pnpm lint # oxlint pnpm format # oxfmt

@unthrown/drizzle's suite runs against a real PostgreSQL started by testcontainers, so pnpm test needs a running Docker daemon. Every other package's tests are self-contained: pnpm turbo run test --filter='!@unthrown/drizzle' runs them without it.

The repository publishes two agent skills: unthrown, with the same error-modeling and boundary rules as the documentation, and migrating-to-unthrown, for porting a codebase from neverthrow or Boxed. Install them into a supported coding agent with:

npx skills add btravstack/unthrown --skill unthrown npx skills add btravstack/unthrown --skill migrating-to-unthrown

The documentation build also publishes llms.txt and llms-full.txt for agents that prefer a curated index or a complete Markdown context.

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →