npm.io
0.6.0 • Published yesterday

@mounaji_npm/error-handler

Licence
MIT
Version
0.6.0
Deps
0
Size
37 kB
Vulns
0
Weekly
0

@mounaji_npm/error-handler

Robust, framework-agnostic error handling for the Mounaji micro-module stack. One taxonomy, one handler — normalize any throw into a typed error, log it with structured + redacted context, shoot an event onto your EventBus, and respond with a client-safe payload.

  • Typed taxonomyAppError + ValidationError, NotFoundError, ForbiddenError, ConflictError, RateLimitError, ExternalServiceError, … Each carries code, status, severity, expose, retryable, context, and a native cause chain.
  • normalizeError() turns any thrown value (Error, string, HTTP-ish object, ZodError, Postgres/Supabase SQLSTATE, network errors) into an AppError, so downstream code handles a single shape. Extensible via custom mappers.
  • Structured logging — built-in zero-dep JSON logger (or inject your own), level auto-derived from severity.
  • Secret redaction — deep, cycle-safe masking of password, token, authorization, apiKey, … before anything is logged or emitted.
  • Event shooting — emits a redacted record onto any EventBus-like { emit(type, payload) } (e.g. @mounaji_npm/event-core), severity mapped into the notifications pipeline.
  • Adapters — Express middleware, Next.js App Router wrapper, Node uncaughtException/unhandledRejection hooks.
  • Browser-safe core — the main entry has no Node built-ins, so error classes/serializers are shared between client and server.

Install

npm i @mounaji_npm/error-handler
# optional, for event shooting:
npm i @mounaji_npm/event-core

1. Create the handler (once per service)

// lib/errors.js
import { createErrorHandler } from '@mounaji_npm/error-handler';
import { EventBus } from '@mounaji_npm/event-core';

export const errors = createErrorHandler({
  serviceName: 'api',
  events: EventBus,                    // optional → emits 'system.error'
  exposeStackInResponse: process.env.NODE_ENV !== 'production',
  redactKeys: ['x-internal-key'],      // extra keys to mask
  onError: (appErr, record) => {       // optional extra sink
    // Sentry.captureException(appErr, { extra: record });
  },
});

2. Throw typed errors in your code

import { ValidationError, NotFoundError } from '@mounaji_npm/error-handler';

if (!body.email) throw new ValidationError('email is required', { details: [{ path: 'email' }] });
const user = await db.get(id);
if (!user) throw new NotFoundError('User not found', { context: { id } });

3. Wire the adapter

Express (@mounaji_npm/error-handler/express):

import { createExpressErrorMiddleware, notFoundMiddleware, asyncRoute } from '@mounaji_npm/error-handler/express';

app.get('/users/:id', asyncRoute(async (req, res) => { /* throw freely */ }));
app.use(notFoundMiddleware());               // 404 for unmatched routes
app.use(createExpressErrorMiddleware(errors)); // MUST be last

Next.js App Router (@mounaji_npm/error-handler/next):

// app/api/things/route.js
import { withErrorHandling } from '@mounaji_npm/error-handler/next';
import { errors } from '@/lib/errors';

export const POST = withErrorHandling(errors, async (request) => {
  const body = await request.json();
  if (!body.name) throw new ValidationError('name is required');
  return Response.json({ ok: true });
});

Node process net (@mounaji_npm/error-handler/node):

import { installProcessHandlers } from '@mounaji_npm/error-handler/node';
installProcessHandlers(errors, { exitOnUncaught: true }); // log + emit, then let the supervisor restart

Response shape

expose: true errors reveal their message (and details); everything else gets a generic message per status so internals never leak:

// 422 ValidationError (expose)          // 500 wrapped internal error (hidden)
{ "error": { "code": "VALIDATION_ERROR", { "error": { "code": "INTERNAL_ERROR",
  "message": "email is required",          "message": "An internal error occurred. Please try again later.",
  "status": 422, "retryable": false,       "status": 500, "retryable": false,
  "details": [{ "path": "email" }] } }     "requestId": "…" } }

Event record

events.emit('system.error', record) where record is the redacted serializeForLog output plus { event, service, eventSeverity }. Subscribe with EventBus.on('system.error', …) to route into notifications, dashboards, or alerting. eventSeverity is mapped to event-core's scale (error/criticaldanger).

Architecture

errors.js     AppError taxonomy + normalizeError (+ built-in library mappers)
codes.js      ERROR_CODES · SEVERITY · status→generic message map
redact.js     deep secret redaction
logger.js     zero-dep structured logger (pluggable)
serialize.js  serializeForLog (full, redacted) · serializeForClient (safe)
handler.js    createErrorHandler → capture / toResponse / wrap
adapters/     express.js · next.js · process.js

Framework-agnostic and source-only (no build step), like @mounaji_npm/event-core.