> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cbrock.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Tagged Errors

> Typed, discriminated error unions.

Tagged errors are native `Error` instances extended with a `_tag` discriminant. They let you build discriminated unions of errors that TypeScript can check exhaustively — and that work with real stack traces, `console.log`, and error monitoring services.

```typescript theme={null}
import { chas } from 'ts-chas';

// Without tagged errors: error is `unknown`
try {
  await fetchUser(id);
} catch (e) {
  e; // unknown — you have no idea what went wrong
}

// With tagged errors: errors are in the type signature
const result = await fetchUser(id);
// Result<User, NotFoundErr | UnauthorizedErr>
```

## Defining errors

`chas.defineErrs()` takes an object where each key becomes a `_tag` and the value is a factory function that returns the error's extra data:

```typescript theme={null}
import { chas } from 'ts-chas';

const AppError = chas.defineErrs({
  NotFound:     (resource: string, id?: string) => ({ resource, id }),
  Unauthorized: (userId: string) => ({ userId }),
  Validation:   (field: string, message: string) => ({ field, message }),
});
```

Each factory produces a real `Error` instance with:

* A `_tag` field matching the key name
* A `name` field set to the tag
* A full stack trace
* All the data properties you returned from the factory

## Creating errors

Call the factory directly, or use the `.err()` helper to wrap it in a `Result`:

```typescript theme={null}
// Create the error instance
const e = AppError.NotFound('user', '123');
// Error { _tag: 'NotFound', name: 'NotFound', resource: 'user', id: '123', stack: '...' }

// Create an Err result in one call
const result = AppError.NotFound.err('user', '123');
// Result<never, NotFoundErr>

// Async variant
const asyncResult = AppError.NotFound.errAsync('user', '123');
// ResultAsync<never, NotFoundErr>
```

## Inferring types

```typescript theme={null}
// Union of all error variants
type AppErr = chas.InferErrs<typeof AppError>;
// NotFoundErr | UnauthorizedErr | ValidationErr

// A single variant
type NotFoundErr = chas.InferErr<typeof AppError.NotFound>;
// { _tag: 'NotFound'; resource: string; id?: string } & Error

// Extract a specific variant from a union
type Auth = chas.ExtractErr<AppErr, 'Unauthorized'>;
```

## Using errors in function signatures

```typescript theme={null}
function fetchUser(id: string): chas.ResultAsync<User, AppErr> {
  if (!isAuthenticated()) {
    return AppError.Unauthorized.errAsync(currentUserId);
  }
  return chas.fromPromise(
    db.users.find(id),
    () => AppError.NotFound('user', id)
  );
}
```

## Exhaustive matching

`chas.matchErr()` requires a handler for every variant. TypeScript will error if you miss one:

```typescript theme={null}
const result = await fetchUser(id);

if (result.isErr()) {
  const message = chas.matchErr(result.unwrapErr(), {
    NotFound:     (e) => `${e.resource} with id ${e.id} was not found`,
    Unauthorized: (e) => `User ${e.userId} lacks permission`,
    Validation:   (e) => `${e.field}: ${e.message}`,
  });
  console.error(message);
}
```

## Partial matching

`chas.matchErrPartial()` lets you handle only the variants you care about. The `_` handler catches everything else and receives the remaining union type:

```typescript theme={null}
const message = chas.matchErrPartial(error, {
  NotFound: (e) => `Could not find ${e.resource}`,
  _: (e) => `Something went wrong: ${e.message}`,
  // `e` in `_` is typed as UnauthorizedErr | ValidationErr
});
```

## Catching a specific tag mid-chain

`.catchTag()` handles a single variant inline, removing it from the error union. The remaining chain has a narrower error type:

```typescript theme={null}
const result = await fetchUser(id)
  .catchTag(AppError.NotFound, (e) => {
    console.log(`No ${e.resource} found, using guest`);
    return chas.ok(GUEST_USER);
  });
// Result<User, UnauthorizedErr | ValidationErr>
// NotFoundErr is no longer in the union
```

You can also match by tag string instead of factory:

```typescript theme={null}
.catchTag('NotFound', (e) => chas.ok(GUEST_USER))
// Note: `e` is less precisely typed when using a string
```

## Tapping a tag without catching

`.tapTag()` runs a side effect on a specific variant but leaves the result unchanged:

```typescript theme={null}
const result = await fetchUser(id)
  .tapTag(AppError.NotFound, (e) => {
    analytics.track('resource_not_found', { resource: e.resource });
  });
// Result<User, NotFoundErr | UnauthorizedErr> — unchanged
```

## Guard integration

Use `is.tagged()` to check an unknown value against a tagged error factory:

```typescript theme={null}
import { is } from 'ts-chas/guard';

function handleError(e: unknown) {
  if (AppError.NotFound.is(e)) {
    // e is narrowed to NotFoundErr
    console.log(`Missing: ${e.resource}`);
  }
}
```

<Tip>
  Each error factory has a `.is(value)` method as a convenience type guard. You can also use `is.tagged(AppError.NotFound)` from `ts-chas/guard` for the same effect.
</Tip>

## Error wrapping with `cause`

If your factory data includes a `cause` property that is an `Error`, it is set as the native `Error.cause`, creating a proper error chain:

```typescript theme={null}
const AppError = chas.defineErrs({
  Http: (status: number, cause?: Error) => ({ status, cause }),
});

const inner = new Error('Connection refused');
const outer = AppError.Http(503, inner);

outer.cause === inner; // true — full chain preserved
```
