Skip to content
Merged
15 changes: 15 additions & 0 deletions .changeset/docs-seo-enrichment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@deessejs/errors': patch
---

Refresh the README and package.json metadata for the npm listing.

README:
- Adopt the shared ecosystem layout (badges block, What is included table, Quick start, Compatibility, Project structure, Publishing, Architecture notes, Contributing, Acknowledgements).
- Credit `deessejs/package-template` and `deessejs/fp` in the new Acknowledgements section.
- Surface the `addNote()` (PEP 678), `raise()`, `causes()`, and the `Result`/`Try` interop story in both the root and the package README.

Package metadata:
- Replace the placeholder description with a more searchable summary (Python-style, ESM, `@deessejs/fp` interop).
- Add `bugs.url`, `files`, `sideEffects: false`, `engines.node`, and `funding` (GitHub Sponsors).
- Refresh `keywords`: drop `npm-package`, add `standard-schema`, `esm`, `monorepo`.
280 changes: 157 additions & 123 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,184 +1,218 @@
# @deessejs/errors
<p align="center">
<h1 align="center">@deessejs/errors</h1>
</p>

[![npm](https://img.shields.io/npm/v/@deessejs/errors)](https://www.npmjs.com/package/@deessejs/errors)
[![TypeScript](https://img.shields.io/badge/typescript-%E2%9A%99%EF%B8%8F-blue)](https://www.typescriptlang.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
<p align="center">
<strong>Lightweight, type-safe error handling for TypeScript — Python-style.</strong> Function-based API, exception chaining, hierarchical inheritance, and rich error semantics. ESM-only, designed for first-class interoperability with <a href="https://github.com/deessejs/fp">@deessejs/fp</a>'s Result and Try.
</p>

A TypeScript error handling library with exception chaining, hierarchical inheritance, and rich error semantics — inspired by Python's error system.
<p align="center">
<a href="https://github.com/deessejs/errors/blob/main/LICENSE"><img src="https://img.shields.io/github/license/deessejs/errors" alt="License"></a>
<a href="https://github.com/deessejs/errors/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/deessejs/errors/ci.yml?label=CI" alt="CI"></a>
<a href="https://github.com/deessejs/errors/stargazers"><img src="https://img.shields.io/github/stars/deessejs/errors?style=social" alt="Stars"></a>
<a href="https://www.npmjs.com/package/@deessejs/errors"><img src="https://img.shields.io/npm/v/@deessejs/errors?color=brightgreen" alt="npm"></a>
</p>

## Features
<p align="center">
<a href="https://errors.deessejs.com"><img src="https://img.shields.io/badge/docs-errors.deessejs.com-blue" alt="Documentation"></a>
</p>

- **Exception Chaining** — Preserve the full context of errors with cause chains via `.from()`
- **Hierarchical Inheritance** — Organize errors in meaningful hierarchies with single or multiple inheritance
- **Rich Error Semantics** — Attach structured data, templates, and notes to errors
- **TypeScript First** — Full type safety with comprehensive type definitions
> **Sibling projects:** [@deessejs/fp](https://github.com/deessejs/fp) provides `Result` and `Try` types that integrate natively with [@deessejs/errors](https://github.com/deessejs/errors) error factories. Install them together to get a complete error-handling story without glue code.

## Installation
---

## What is included

| Layer | What you get | Why it matters |
| ---------------------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| **`error()`** | Define error factories with name, message templates, fields, and inheritance. | Python-style error definitions without classes. |
| **`.from()` chaining** | Cause chain via `.from()` + `causes()` traversal. | Link errors together while preserving the full chain context. |
| **Single or multiple inheritance** | `inherits:` accepts a factory or an array. | Organize error hierarchies that match your domain. |
| **`is()` type checking** | Runtime and type-safe classifier with inheritance support. | Discriminate errors without brittle `instanceof`. |
| **`.addNote()`** | Attach runtime context to error instances. | Python 3.11-style notes (PEP 678) for trail-of-breadcrumbs debugging. |
| **Message templates** | `{field}` placeholders with `:upper`, `:lower`, `:json` modifiers. | Readable messages composed from structured fields at construction time. |
| **Standard Schema fields** | Accepts Zod / Valibot / ArkType schemas. | Validated structured data on every error instance, no hand-rolled guards. |
| **`raise()`** | Idiomatic throw helper. | Type-narrowed (`never`) `raise(err)` for control-flow readability. |
| [@deessejs/fp](https://github.com/deessejs/fp) integration | `Result`/`Try` accept `ErrorInstance` directly. | Type-safe error pipelines end-to-end, no string-error footguns. |

## Why this library

- **Simple by default.** No class hierarchies to manage, no decorators under reflection. Just factory functions and chained calls.
- **ESM-only.** Modern packaging, no CJS shim, no `module`/`main` duplication.
- **Minimal runtime.** The only runtime dependency is `@standard-schema/spec`.
- **TypeScript first-class.** Strict types, no `any` leakages, full inference. JSDoc on every public symbol.
- **Real testing.** Vitest with type-level and runtime tests, including cause-chain traversal.

## Quick start

### Prerequisites

- Node.js 22.x for consumers (the package emits ESM)
- pnpm 10+ for development (`corepack enable` if not installed)
- TypeScript 5.x for consumers (`dist/*.d.ts` is published)

### Install

```bash
npm install @deessejs/errors
# or
pnpm add @deessejs/errors
# or
yarn add @deessejs/errors
```

## Quick Start
[@deessejs/fp](https://github.com/deessejs/fp) is optional - install it if you want to compose `Result`/`Try` types around `ErrorInstance`.

### Creating Errors
### Usage

```typescript
import { error } from '@deessejs/errors';
import { error, raise, is, causes } from '@deessejs/errors';

// Simple error
const ValidationError = error({ name: 'ValidationError' });
const err = ValidationError();

// Error with message template
// Define an error factory with a templated message
const ValidationError = error({
name: 'ValidationError',
message: 'Field "{field}" is invalid: {reason}',
});

// Construct a typed error
const err = ValidationError({ field: 'email', reason: 'invalid format' });
// err.message === 'Field "email" is invalid: invalid format'
```

### Exception Chaining
// Chain a cause
const cause = error({ name: 'NetworkError' })();
err.from(cause);

```typescript
import { error } from '@deessejs/errors';

const ValidationError = error({ name: 'ValidationError' });
const ProcessingError = error({ name: 'ProcessingError' });

const validationErr = ValidationError({ field: 'email' });
const processingErr = ProcessingError();
// Throw it
raise(err);

// Chain errors with .from()
processingErr.from(validationErr);

console.log(processingErr.message); // "ProcessingError"
console.log(processingErr.cause); // validationErr
console.log(processingErr.causes); // [validationErr]
// Later, type-check and walk the chain
is(err, ValidationError); // true
causes(err); // [cause]
```

### Hierarchical Inheritance
### Engine compatibility

```typescript
import { error } from '@deessejs/errors';
| Runtime | Minimum version |
| ---------- | -------------------- |
| Node.js | 22.0.0 |
| pnpm | 10 (for development) |
| TypeScript | 5.x |

// Single inheritance
const AppError = error({ name: 'AppError' });
const ValidationError = error({
name: 'ValidationError',
inherits: AppError,
});
ESM-only. Consumers using a CJS resolver need to use dynamic `import()` or migrate to ESM.

// Multiple inheritance
const NetworkError = error({ name: 'NetworkError' });
const StorageError = error({ name: 'StorageError' });
const CombinedError = error({
name: 'CombinedError',
inherits: [NetworkError, StorageError],
});
```
## Available commands

### Type Checking
### Package: `@deessejs/errors`

```typescript
import { error, is } from '@deessejs/errors';
| Command | What it does |
| ------------------------------------------- | -------------------------------------------- |
| `pnpm --filter @deessejs/errors build` | Build `dist/` (`tsc -p tsconfig.build.json`) |
| `pnpm --filter @deessejs/errors test` | Run vitest in watch mode |
| `pnpm --filter @deessejs/errors test:run` | Run vitest once |
| `pnpm --filter @deessejs/errors type-check` | `tsc --noEmit` |
| `pnpm --filter @deessejs/errors lint` | Run ESLint |

const AppError = error({ name: 'AppError' });
const ValidationError = error({
name: 'ValidationError',
inherits: AppError,
});
### Root (monorepo)

const err = ValidationError();
| Command | What it does |
| ----------------- | -------------------------- |
| `pnpm build` | Build via Turborepo |
| `pnpm test` | Run all tests |
| `pnpm lint` | Lint every workspace |
| `pnpm type-check` | Type-check every workspace |
| `pnpm format` | Format with Prettier |

is(err, ValidationError); // true
is(err, AppError); // true (inherits from AppError)
```
### App: `web` (documentation site)

### Traverse Cause Chain
| Command | What it does |
| ------------------------- | ---------------------------------- |
| `pnpm --filter web dev` | Start the docs site in dev mode |
| `pnpm --filter web build` | Build the docs site for production |

```typescript
import { error, causes } from '@deessejs/errors';
## Compatibility

const Err1 = error({ name: 'Err1' });
const Err2 = error({ name: 'Err2' });
const Err3 = error({ name: 'Err3' });
### Runtime dependency

const err1 = Err1();
const err2 = Err2().from(err1);
const err3 = Err3().from(err2);
| Package | Required | Notes |
| ----------------------- | -------------- | -------------------------------------------------------------------------------------------------------- |
| `@standard-schema/spec` | Yes, `>=1.0.0` | The interface used by `fields`. Schema implementations (Zod, Valibot, ArkType) are passed by the caller. |

// Iterate through the cause chain
for (const cause of causes(err3)) {
console.log(cause.name);
}
// Output: Err2, Err1
```
### Peer dependencies

## Why @deessejs/errors?
| Package | Required | Notes |
| ---------------------------------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------- |
| [@deessejs/fp](https://github.com/deessejs/fp) | Optional, peer `>=1.0.0` | Recommended if you want `Result`/`Try` types around `ErrorInstance`. Not required for using `@deessejs/errors` alone. |

Built-in JavaScript errors are limited. `@deessejs/errors` brings Python-style error handling to TypeScript.
### Engines

| Feature | Built-in `Error` | @deessejs/errors |
| ------------------------ | ---------------- | ---------------- |
| Exception chaining | ❌ | ✅ |
| Hierarchical inheritance | ❌ | ✅ |
| Message templates | ❌ | ✅ |
| Type-safe fields | ❌ | ✅ |
| Standard Schema support | ❌ | ✅ |
| Field | Value |
| ---------------- | -------------- |
| `engines.node` | `>=22.14.0` |
| `packageManager` | `pnpm@10.34.5` |

```typescript
// Traditional approach — limited context
throw new Error('Validation failed'); // ❌ Generic, no structure
## Project structure

// @deessejs/errors — rich, maintainable errors
const err = ValidationError({ field: 'email', reason: 'invalid format' });
err.from(originalError); // ✅ Chain exceptions, preserve context
```
.
├── packages/
│ └── errors/ # The library — @deessejs/errors on npm
│ ├── src/ # Source code (ESM)
│ ├── tests/ # Vitest suites
│ ├── dist/ # Build output (gitignored)
│ └── tsconfig.build.json
├── apps/
│ └── web/ # Documentation site (Next.js + Fumadocs)
├── docs/
│ ├── internal/ # Engineering plans, runbooks
│ │ ├── product/
│ │ └── versions/
│ └── engineering/
├── pnpm-workspace.yaml
├── turbo.json # Turborepo pipelines
├── .changeset/ # Changesets for versioning
└── README.md
```

## FAQ
## Publishing

### How do I create a custom error type?
Releases are fully automated via Changesets + npm Trusted Publishing (OIDC). No long-lived `NPM_TOKEN` is required.

```typescript
import { error } from '@deessejs/errors';
| What | How |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| Bump version | Add a `.changeset/<topic>.md` file with semver and description on a PR to `staging` |
| Open the release PR | Cherry-pick selected commits from `staging` into `release/vX.Y.Z` and PR to `main` |
| Publish | Merge to `main` - `release.yml` detects changesets and publishes via Trusted Publishing to npm with provenance attestation |
| Hotfix | Branch from `main` as `release/hotfix-<slug>`, open PR directly to `main` with `[hotfix]` label. Same workflow fires. |
| Rollback | Use `pnpm changeset version` then revert the merge. npm deprecations: `pnpm npm deprecate @deessejs/errors@<rev> '<msg>'` |

const ValidationError = error({ name: 'ValidationError' });
const err = ValidationError({ field: 'email' });
```
For the full release runbook, see [`docs/internal/engineering/process/releasing-a-new-version.md`](docs/internal/engineering/process/releasing-a-new-version.md).

### How do I chain exceptions?
## Architecture notes

```typescript
import { error } from '@deessejs/errors';
- **ESM-only.** The package exports ES modules. Consumers using legacy CJS resolvers must use dynamic `import()`.
- **Strict types.** `error()` returns a typed factory; `is()` narrows. No `any` leakages.
- **Composition over inheritance.** All error primitives compose via instance methods (`.from()`, `.addNote()`, `inherits`). No class hierarchy on the consumer side.
- **Zero decorators.** Pure factory functions. The library is straightforward to read in DevTools and `node --prof`.
- **Smoke-tested before publish.** The release workflow imports the built artifact and verifies key exports are present. A broken build fails the publish step before reaching npm.
- **Symmetric interop with [@deessejs/fp](https://github.com/deessejs/fp).** `Result` constructors accept `ErrorInstance` so you never have to coerce a typed error to a string.

const validationErr = ValidationError({ field: 'email' });
const processingErr = ProcessingError().from(validationErr);
## Contributing

console.log(processingErr.cause); // validationErr
```
Open an issue to discuss larger changes. For typos, broken links, and small fixes, PRs are welcome.

### How do I check if an error is of a specific type?
Before submitting a PR:

```typescript
import { error, is } from '@deessejs/errors';
1. Run `pnpm --filter @deessejs/errors test:run` and `pnpm --filter @deessejs/errors lint`.
2. Add a `.changeset/<topic>.md` if the change is user-facing (patch / minor / major).
3. Update `docs/internal/product/README.md` if the API surface changes.

const AppError = error({ name: 'AppError' });
const ValidationError = error({ name: 'ValidationError', inherits: AppError });
## Acknowledgements

is(err, AppError); // true if err is ValidationError or any descendant
```
The README layout and monorepo tooling for this project are based on the [deessejs/package-template](https://github.com/deessejs/package-template). The shipped README borrows its structure from the [deessejs/fp README](https://github.com/deessejs/fp), adapted for the @deessejs/errors API surface.

## Documentation
## License

For full documentation, visit [errors.deessejs.com](https://errors.deessejs.com)
[MIT](./LICENSE). See the LICENSE file for details.

## License
## Support

MIT
- Issues: [github.com/deessejs/errors/issues](https://github.com/deessejs/errors/issues)
- Discussions: [github.com/deessejs/errors/discussions](https://github.com/deessejs/errors/discussions)
- Email: [support@deessejs.com](mailto:support@deessejs.com)
- Documentation: [errors.deessejs.com](https://errors.deessejs.com)
Loading
Loading