result-not-throw
Result, not throw
If it doesn't hold up in production, it doesn't make the cut.
Throwing in business logic hides control flow. Failures become invisible until they propagate to a try/catch three layers up — by which point the type system has nothing useful to say and the caller has no idea what failure modes exist.
In my code, business logic returns Result<T, E>. Throws are reserved for boundaries (Fastify handlers, CLI entry points, framework adapters) where there's no caller to ok/err against.
This is the ait pattern, lifted into a public skill. The canonical implementation lives at @ait/core/types/result.ts.
When this skill is active
You are writing or editing one of:
- A service-layer method (
*Service,*Repository,*UseCase) - A domain function in
packages/*/src/that is not a handler - A pure utility that can fail meaningfully (parsing, validation, IO)
If it's a Fastify handler, a CLI command, top-level main(), or a test, this skill does not apply — those are boundaries.