You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(typed)(#113): kind discriminator on safe error envelope (#115)
* feat(typed)(#113): kind discriminator on safe error envelope
Replace TypedSafeErr<R> with a discriminated union on `kind`:
- `kind: "http"` — server responded with a non-2xx status declared in
`responses`. `error.status` narrows to the `ErrorCodes` union and
`error.data` narrows to the body shape for that status. `response`
is a real `Response`.
- `kind: "network"` — no server response (TCP/TLS failure, DNS, timeout,
abort). `error` is the raw `Error`; `response` is `undefined`.
Why: the previous shape coerced network failures into a synthetic
`{ status: 0, data: error.message }` envelope. When `ErrorCodes` was
declared as `404 | 429`, TypeScript narrowed `error.status` to that
union but at runtime `status` could be 0 — the type lied. The new
shape exposes the raw `Error` so callers branch on `kind === "network"`
before touching `status`.
Migration: callers now check `result.kind` before reading `error.status`:
if (!result.ok) {
if (result.kind === "network") result.error // Error
else if (result.error.status === 429) ... // narrows
}
The success branch (`ok: true`) is unchanged — issue #113's proposal
keeps `kind` only on the error side.
Closes#113
* chore(review): drop defensive Error coercion in safe wrapper
Drivers and misina's internal request path always throw `Error`
subclasses per AGENTS.md ("drivers throw TypeError('fetch failed')"
to trip the NetworkError mapper). The `e instanceof Error ? e : new
Error(...)` shim was defensive code against a contract violation that
internal callers cannot produce — `e as Error` matches the same
runtime behavior without the dead branch.
0 commit comments