Skip to content

Resources + errors

Two small primitives shape the surface a route presents to its callers: defineResource declares the public shape of a response; defineError makes thrown errors typed + status-tagged.

defineResource

(unchanged — see the reference for the full surface and the HTTP routes guide for how it plugs into a route handler.)

The public field list is the allowlist. Internal fields never leak. Studio's OpenAPI spec uses the public projection.

defineError

defineError(meta) returns a value that is both an Error instance you can throw directly and a callable factory that produces a contextual clone:

ts
import { defineError } from "@nwire/handler";

export const UserNotFound = defineError({
  code: "USER_NOT_FOUND",
  status: 404,
  summary: "No user found with that id.",
});

export const EmailAlreadyTaken = defineError({
  code: "EMAIL_ALREADY_TAKEN",
  status: 409,
  summary: "That email is registered to another account.",
});

Throw with context — the callable form attaches the context to the thrown clone:

ts
if (!user) throw UserNotFound({ userId: input.id });
if (existing) throw EmailAlreadyTaken({ email: input.email });

Throw without context — the definition itself is an Error:

ts
throw UserNotFound;

The HTTP adapter's top-level error mapper reads code, status, and summary and emits a structured envelope:

json
{
  "error": {
    "code": "USER_NOT_FOUND",
    "summary": "No user found with that id."
  }
}

Throws that don't carry a code + status become a generic { code: "internal_error", status: 500 } envelope; the adapter logs the original server-side and sends the client nothing else. This protects against accidental leaks of driver errors, raw stack traces, or PII attached to a thrown Error. httpKoa({ exposeErrors: true }) echoes the real message for local debugging — off by default in every environment. The same mapper answers wired routes, Studio's POST /_nwire/dispatch, and MCP's dispatch_action; see the error contract.

Built-in errors

@nwire/handler ships the common HTTP errors as defineError instances:

ts
import {
  Unauthorized, // 401
  Forbidden,    // 403
  NotFound,     // 404
  Conflict,     // 409
  Gone,         // 410
  BadRequest,   // 400
} from "@nwire/handler";

throw Unauthorized;
throw Forbidden({ resource: "post", id: input.postId });
throw NotFound({ resource: "user", id: input.id });

@nwire/errors re-exports these for compatibility with older imports; new code should import from @nwire/handler.

Where to next

MIT licensed.