# Error Handling Gotchas

Non-obvious behavior and pitfalls when working with puffy-core's error handling system.

## Table of Contents
- [PuffyResponse Is a Custom Array Subclass](#puffyresponse-is-a-custom-array-subclass)
- [Error Metadata Merging Prioritizes Later Errors](#error-metadata-merging-prioritizes-later-errors)
- [Chain Functions Stop on PuffyResponse Errors](#chain-functions-stop-on-puffyresponse-errors)
- [wrapErrors Merge Mode Flattens the Stack](#wraperrors-merge-mode-flattens-the-stack)

## PuffyResponse Is a Custom Array Subclass

`catchErrors()` does not return a plain `[errors, data]` array. It returns a `PuffyResponse` instance, which extends `Array`. This matters because:

- `chainAsync` and `chainSync` check `instanceof PuffyResponse` to decide whether to unwrap or pass through. A plain array `[null, value]` will be passed as-is to the next function, not unwrapped.
- If you manually construct `[errors, data]` and return it from a chained function, the chain will **not** treat it as an error response — it will treat it as a normal value.

```js
// This stops the chain on error (correct):
previous => catchErrors(riskyOperation())

// This does NOT stop the chain (plain array, not PuffyResponse):
previous => [new Error('fail'), null]
```

## Error Metadata Merging Prioritizes Later Errors

When using `getErrorMetadata(errors)`, metadata from errors later in the array overrides earlier metadata for the same keys. Since errors are accumulated in inverse order (first error = top of stack, last error = original cause), this means the **original cause's metadata wins**.

```js
const err1 = wrapCustomErrors({ code: 'OUTER', status: 500 })('Wrapper')
const err2 = wrapCustomErrors({ code: 'INNER', detail: 'x' })('Root cause')
const meta = getErrorMetadata([err1, err2])
// meta = { code: 'INNER', status: 500, detail: 'x' }
//         ^ 'INNER' from err2 overrides 'OUTER' from err1
```

## Chain Functions Stop on PuffyResponse Errors

`chainAsync` and `chainSync` stop executing remaining functions the moment a function returns a `PuffyResponse` containing errors. The errors are wrapped with an additional context message (`'chainAsync' function failed` or `'chainSync' function failed`).

However, if a function **throws** directly (instead of returning a PuffyResponse with errors), the error is caught but **not** double-wrapped — it propagates as a single error.

```js
// Double-wrapped (PuffyResponse with errors):
const [errors] = await chainAsync(
    () => catchErrors(Promise.reject(new Error('Boom')))
)
// errors = [{ message: "'chainAsync' function failed" }, { message: 'Boom' }]

// Single error (direct throw):
const [errors] = await chainAsync(
    () => { throw new Error('Boom') }
)
// errors = [{ message: 'Boom' }]
```

## wrapErrors Merge Mode Flattens the Stack

By default, `wrapErrors('context', existingErrors)` creates a new error and nests the existing errors beneath it. With `{ merge: true }`, it flattens all errors into a single level instead.

```js
const inner = [new Error('A'), new Error('B')]

// Default: nested (3 errors in stack)
wrapErrors('Wrapper', inner)
// → [Error('Wrapper'), Error('A'), Error('B')]

// Merge mode: merged message (1 error)
wrapErrors('Wrapper', inner, { merge: true })
// → [Error('Wrapper\nA\nB')]
```

Choose merge mode when you want a single error for logging; use the default when you need to inspect individual errors programmatically.
