Skip to content

Results & Observability

Turn throwing code into typed results, and send every NiceError to your logger.

Two things that work well together. A result type lets you run code that might fail without try/catch. A single hook sees every NiceError, so you can send them to your logger or telemetry.

niceTry / niceTryAsync run a function that might throw and hand you back a typed result. If it fails, the error is always a NiceError.

import { niceTry, niceTryAsync, type TNiceResult } from "@nice-code/error";
const r = niceTry(() => JSON.parse(input));
if (!r.ok) report(r.error); // r.error is a NiceError (isUnhandled === true for a raw throw)
else use(r.output);
const r2 = await niceTryAsync(() => fetchUser(id)); // accepts a sync or async fn

Behind the scenes the catch runs through castNiceError: a thrown domain error comes through as-is, while anything else is wrapped into an isUnhandled error.

type TNiceResult<OUT, ERR extends NiceError = NiceError> =
| { ok: true; output: OUT }
| { ok: false; error: ERR };

Build results by hand with niceOk / niceErr when a function returns a TNiceResult directly:

import { niceOk, niceErr } from "@nice-code/error";
function parsePort(raw: string): TNiceResult<number> {
const n = Number(raw);
return Number.isInteger(n) ? niceOk(n) : niceErr(err_config.fromId("bad_port", { raw }));
}

@nice-code/action’s action outcome is a TNiceResult with one extra field (expected), so the two fit together. See Error Handling.

onNiceError runs every time a NiceError is created. Send them to Sentry, OpenTelemetry, or your devtools.

import { onNiceError, setNiceErrorLogger } from "@nice-code/error";
const off = onNiceError((error) => report(error.toStructuredLog()));
// ...later
off(); // remove the tap

error.toStructuredLog() returns a flat object made for logs, distinct from toJsonObject() (the wire form):

{ domain, ids, message, httpStatusCode, isUnhandled, timeCreated, originError? }

The library’s own warnings go to console by default. Redirect them through your logger:

setNiceErrorLogger({ warn: myLogger.warn, debug: myLogger.debug, error: myLogger.error });

The main @nice-code/error import is all most apps need. Lower-level helpers sit behind a separate subpath, meant for libraries built on nice-error: inspecting the wire format, the err_cast_not_nice fallback domain, isNiceErrorObject and context-state types.

import { isNiceErrorObject, err_cast_not_nice } from "@nice-code/error/internal";