npm package: @manrp/http-problem
Description: Typed HTTP & domain error abstraction with RFC7807 support, multi-runtime compatibility, and adapters for Fetch and GraphQL
Author: Juan Manuel Romero Proa
Latest version: 1.0.0

# @manrp/http-problem

![npm version](https://img.shields.io/npm/v/@manrp/http-problem)
![node version](https://img.shields.io/node/v/@manrp/http-problem)
![license](https://img.shields.io/npm/l/@manrp/http-problem)
![npm downloads](https://img.shields.io/npm/dm/@manrp/http-problem)

Typed HTTP & domain error abstraction with RFC7807 support, multi-runtime compatibility, and adapters for Fetch and GraphQL.

Inspired by hapi/boom, but designed for modern TypeScript with strong typing, extensibility, and zero runtime dependencies.

---

## ✨ Features

* 🔒 Fully typed HTTP errors
* 🧠 Domain → HTTP error mapping
* 📦 RFC 7807 (Problem Details) compliant output
* 🌐 Works in Node.js, Bun, Deno, and browser
* 🔌 Built-in adapters for Fetch API and GraphQL
* 🧩 Extensible domain errors via `withDomain`
* 🚫 No dependencies
* ⚡ High DX (factory-style API, no `new`)

---

## 📥 Installation

```bash
npm install @manrp/http-problem
```

---

## 🚀 Quick Start

```ts
import { HttpProblem } from '@manrp/http-problem';

throw HttpProblem.notFound('User not found');
```

---

## 🌐 HTTP Errors

Create HTTP errors using static factory methods:

```ts
HttpProblem.badRequest();
HttpProblem.unauthorized();
HttpProblem.forbidden();
HttpProblem.notFound();
HttpProblem.internalServerError();
```

All errors are strongly typed:

```ts
const err = HttpProblem.notFound();

err.code;   // "NOT_FOUND"
err.status; // 404
err.title;  // "Not Found"
```

---

## 🧠 Domain Errors

Map domain-level errors to HTTP automatically:

```ts
throw HttpProblem.fromDomain('VALIDATION_ERROR');
```

Includes metadata:

```ts
const err = HttpProblem.fromDomain('VALIDATION_ERROR');

err.meta?.domainCode; // "VALIDATION_ERROR"
```

---

## 🧩 Custom Domain Errors

Extend the system with your own domain errors:

```ts
const AppProblem = HttpProblem.withDomain({
  USER_BLOCKED: {
    http: 'FORBIDDEN',
    message: 'User is blocked'
  },
  PAYMENT_FAILED: {
    http: 'BAD_REQUEST',
    message: 'Payment failed'
  }
} as const);
```

Usage:

```ts
throw AppProblem.fromDomain('USER_BLOCKED');
```

### ✔ Features

* Fully typed (keys + HTTP mapping)
* Supports base + custom domain errors
* No global state
* Composable per module or context

---

## 🧱 Domain Composition

You control how domain errors are composed:

```ts
const Domain = {
  ...AuthDomain,
  ...PaymentDomain,
} as const;

const Problem = HttpProblem.withDomain(Domain);
```

---

## 📦 RFC 7807 (Problem Details)

Generate standardized error responses:

```ts
const err = HttpProblem.notFound('User not found');

err.toRFC7807();
```

Output:

```json
{
  "type": "about:blank",
  "title": "Not Found",
  "status": 404,
  "detail": "User not found",
  "code": "NOT_FOUND"
}
```

---

## 🌐 Fetch API Integration

Return a Response directly:

```ts
return HttpProblem.notFound().toResponse();
```

Content-Type:

```
application/problem+json
```

---

## 🔗 GraphQL Integration

Convert to GraphQL error format:

```ts
throw HttpProblem.notFound().toGraphQl();
```

---

## 🧠 Unknown Errors

Normalize unknown values safely:

```ts
HttpProblem.fromUnknown(err);
```

Force internal error:

```ts
HttpProblem.ensureInternal(err);
```

---

## 🔁 From HTTP Status

Create error from status code:

```ts
HttpProblem.fromStatus(404);
```

---

## 🔍 Type Guard

```ts
if (HttpProblem.isHttpProblem(err)) {
  console.log(err.code);
}
```

---

## ⚙️ Visibility Policy

Control exposed details:

```ts
err.toRFC7807({
  fallbackMessage: 'Unexpected error',
  visibility: {
    exposeDetails: () => false,
    exposeMeta: () => false
  }
});
```

---

## 🧠 Design Principles

* No global state
* Strong typing over magic
* Composition over configuration
* Runtime agnostic
* Predictable and explicit API

---

## 📄 License

MIT