Apply Wellcrafted Result patterns to fallible operations and preserve failures at boundaries. Use when replacing try/catch, adding trySync or tryAsync, choosing fallback or propagation, or mapping errors.
70
85%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
This skill owns the boundary between thrown exceptions and Result values, plus correct Result consumption. Compose with define-errors for variant design, logging for diagnostics, query-layer for RPC presentation, and hono for response APIs.
Ground every Wellcrafted behavior claim in the official wellcrafted-dev/wellcrafted source and tests. When maintaining this guidance, confirm that Epicenter's installed version matches the source being read. If it does not, report the version drift; dependency freshness is handled outside this skill. Treat other skills, examples, generated documentation, and DeepWiki as leads, not authority.
Read the scoped references only when needed:
trySync or tryAsync should cover, especially around cleanup.| Required contract | Pattern |
|---|---|
Caller receives Result<T, E> | Adapt the throwing operation with trySync or tryAsync |
| Failure has a valid fallback | Return Ok(fallback) from catch |
| Failure must propagate as data | Return a typed defineErrors factory result from catch |
Only known external failures should become Err | Map known exceptions and rethrow unknown ones |
| Surrounding API is exception-based | Keep its throwing contract |
| Cleanup must run on success, failure, cancellation, or early return | Use finally |
Use trySync for a synchronous operation and tryAsync for an operation returning a Promise.
const { data: response, error } = await tryAsync({
try: () => fetch(url),
catch: (cause) => RequestError.TransportFailed({ cause }),
});
if (error !== null) return Err(error);
return Ok(response);defineErrors factories already return Err(...). Pass the raw cause into the factory and let the factory compose its message with extractErrorMessage. Do not use raw Err(cause) at a catch boundary: thrown values may be null or undefined, and an untyped cause loses the domain failure.
Keep a Result as data while the caller can still recover, report, retry, or
propagate. Use unwrap only where the surrounding API already throws as its
failure channel:
import { unwrap } from 'wellcrafted/result';
const document = unwrap(await openDocument(id));unwrap returns Ok.data and throws Err.error. The throw is the boundary's
contract, not a sign the failure was unexpected. Guard on error !== null
instead when this function must recover, add context, or clean up.
Result<T, E>, inspect or deliberately forward its error branch.error is the raw E. Return Err(error), not error.if (result.error !== null) return result.error !== null is the reliable discriminator. Never construct Err(null) or Err(undefined).Ok<T> and the inferred type collapses to Ok<T>.Err, not to destructure fields you do not use.Prefer an immediate guard so the success path stays linear.
tryAsync returns a Promise. Choose its owner explicitly:
await when this function inspects the Result.return tryAsync(...) when the caller owns the Promise<Result<...>>.void tryAsync(...): ordinary failures fulfill with Err, so a Promise rejection handler cannot observe them. A best-effort operation still needs an async owner that awaits the Result and explicitly logs or ignores its error branch.void save().then((result) => toastOnError(result, 'Save failed')). If the Promise can reject, adapt or catch that rejection first.Traditional try-catch is appropriate when:
finally block owns cleanup;yield a failure rather than return a Result;Do not turn unknown programming errors into a generic domain failure. Mapping every throw to Err can hide bugs behind a misleading retryable error.
Err branch is handled, forwarded, logged, presented, or explicitly ignored by a named best-effort owner.825a995
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.