Results & Observability
Turn throwing code into typed results, and tap every NiceError for logging.
Two things that work well together: a result type for running code that might fail without try/catch, and
a single place to hook into so every NiceError can be sent to your logger or telemetry.
Running code that might fail — niceTry
Section titled “Running code that might fail — niceTry”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 fnBehind the scenes the catch runs through castNiceError: a thrown domain
error comes through as-is, while anything else is wrapped into an isUnhandled error.
TNiceResult and its builders
Section titled “TNiceResult and its builders”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 just aTNiceResultwith one extra field (expected), so the two fit together neatly — see Error Handling.
Watching every error
Section titled “Watching every error”onNiceError runs every time a NiceError is created — send those to Sentry, OpenTelemetry, or your devtools.
import { onNiceError, setNiceErrorLogger } from "@nice-code/error";
const off = onNiceError((error) => report(error.toStructuredLog()));// ...lateroff(); // remove the taperror.toStructuredLog() returns a flat, log-friendly object distinct from toJsonObject() (the wire
form):
{ domain, ids, message, httpStatusCode, isUnhandled, timeCreated, originError? }Routing the library’s own diagnostics
Section titled “Routing the library’s own diagnostics”The library’s internal warnings default to console. Redirect them through your own logger:
setNiceErrorLogger({ warn: myLogger.warn, debug: myLogger.debug, error: myLogger.error });Internal subpath
Section titled “Internal subpath”The main @nice-code/error surface is all most apps need. The lower-level helpers — inspecting the wire
format, the err_cast_not_nice fallback domain, isNiceErrorObject, context-state types — sit behind a
separate subpath, meant for libraries built on top of nice-error:
import { isNiceErrorObject, err_cast_not_nice } from "@nice-code/error/internal";