Recover & Transform
recover and transform are the two hooks that change the return value. They’re separate from observability hooks (onError/onSuccess), which are side-effect-only.
recover
Section titled “recover”A true catch-all that wraps the entire error path. If it returns a non-undefined value, that value becomes the result instead of throwing.
const config = await expectStatus(200, api.getFeatureFlags(), { recover: () => DEFAULT_FLAGS,})// config is FeatureFlags — never throwsWhen to use
Section titled “When to use”- Feature flags / config — fall back to defaults on any error
- Cached data — serve stale data when the API is down
- Optional data — return
nullinstead of throwing
// Serve stale cache on failureconst data = await expectStatus(200, api.getDashboard(), { recover: () => cache.get('dashboard'),})
// Optional: return nullconst profile = await expectStatus(200, api.getProfile(id), { recover: () => null,})Return undefined to re-throw
Section titled “Return undefined to re-throw”If recover returns undefined, the original error is re-thrown:
await expectStatus(200, response, { recover: (err) => { if (err.message.includes('rate limit')) return FALLBACK return undefined // re-throws the original error },})Execution order
Section titled “Execution order”onError fires before recover. Both receive the same error.
transform
Section titled “transform”Reshapes the success body before returning:
const wrapped = await expectStatus(200, response, { transform: (body) => ({ data: body, fetchedAt: Date.now() }),})When to use
Section titled “When to use”- Wrapping — add metadata to the response body
- Normalizing — reshape to match your app’s internal types
- Selecting — pluck a nested field
// Pluck a nested fieldconst users = await expectStatus(200, api.listUsers(), { transform: (body) => body.data.users,})In instance defaults
Section titled “In instance defaults”Both hooks can be set in defaults — per-call overrides shadow them:
const expectStatus = createExpectStatus({ defaults: { transform: (body) => ({ data: body }), recover: (err) => ({ error: err.message }), },})
// Per-call overrideawait expectStatus(200, response, { transform: (body) => body.items, // shadows default transform})Type impact
Section titled “Type impact”When transform or recover is provided, the return type widens to Promise<unknown> because the hooks can return anything.
Next steps
Section titled “Next steps”- Error Resolution — where recover sits in the priority chain
- Safe Results —
throws: falseas an alternative to recover - Observability Hooks — side-effect-only hooks