# unjs/ofetch — codebase unpack

> Canonical page: https://codevetter.com/unpack/ofetch

This is a deterministic Repo Unpack scan of unjs/ofetch — a structural map, not a review, security audit, or maintainer-endorsed description.

- Scanned commit: 1dbc37fd1ceab832fc7c90cad81b1091c95ba563 (2026-07-08)
- Last upstream commit: chore: fix typos in readme (#608)
- Generated by codevetter 1.15.0 on 2026-10-02
- 32 files scanned · 352 KB

Source inspection; upstream code and tests were not executed.

## Languages

- TypeScript: 11 files
- JavaScript: 8 files
- JSON: 3 files
- Markdown: 3 files
- YAML: 2 files

## Repository structure

- examples/ — 9 files
- src/ — 7 files
- .github/ — 3 files
- test/ — 2 files

Probable source entrypoint: `src/index.ts` (inferred from the tree, not upstream docs)

## Source layout — src/

- src/base.ts — 1 file
- src/error.ts — 1 file
- src/fetch.ts — 1 file
- src/index.ts — 1 file
- src/types.ts — 1 file
- src/utils.ts — 1 file
- src/utils.url.ts — 1 file

## Package

- package.json: ofetch v2.0.0-alpha.3 — 12 declared dependencies

## Structural health signals

Average score 8.9/10 across 10 files; 1 hotspots. These are deterministic review leads, not proof of a bug.

## Recent history

- 1dbc37fd1cea (2026-07-08) chore: fix typos in readme (#608)
- 9102908aeeca (2026-06-28) docs: fix license link in readme (#601)
- dfbe3ca4ef8a (2026-03-11) chore(deps): update autofix-ci/action digest to 7a166d7 (#540)
- d50a2047d431 (2026-02-04) docs: add proxy configuration for Bun and Deno (#539)
- d61b2fcf7755 (2025-10-31) chore(deps): update all non-major dependencies (#509)

## Overview

ofetch is a TypeScript HTTP client library for applications using native fetch in Node.js, browsers, and workers. At commit 1dbc37fd1ceab832fc7c90cad81b1091c95ba563, its v2 alpha implementation adds response decoding, request serialization, lifecycle hooks, configurable retries, timeouts, and reusable defaults. Its central abstraction is a callable client with raw, native, and create variants; transport remains delegated to the host runtime or an injected fetch implementation.

## System map

The system is a library consumed through an ESM package entrypoint. Seven source modules divide client creation, request execution, helpers, errors, and public types. State lives in factory closures and per-request contexts, while network behavior belongs to native or injected fetch. Build and CI configuration describe npm distribution and Node validation.
- The package identifies version 2.0.0-alpha.3, exports dist/index.mjs with dist/index.d.mts types, publishes only dist, and declares sideEffects false. — [package.json#L2-L14](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/package.json#L2-L14)
- src/index.ts re-exports runtime APIs through base.ts and public types, creates ofetch with its fetch wrapper, and exports $fetch as the same client. — [src/index.ts#L1-L15](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/index.ts#L1-L15) [src/base.ts#L1-L2](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/base.ts#L1-L2)
- The entrypoint wrapper allows later global fetch patching when fetch existed at import time; otherwise it installs a rejecting fallback. — [src/index.ts#L8-L11](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/index.ts#L8-L11)
- createFetch captures the transport, owns nested error/raw functions, and attaches raw, native, and create methods to the callable client. — [src/fetch.ts#L36-L39](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L36-L39) [src/fetch.ts#L259-L279](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L259-L279)
- fetch.ts orchestrates transport, utils.ts handles option resolution and hooks, utils.url.ts handles string URLs, and error.ts constructs contextual failures. — [src/fetch.ts#L1-L19](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L1-L19) [src/utils.ts#L85-L119](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.ts#L85-L119) [src/utils.url.ts#L40-L51](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.url.ts#L40-L51) [src/error.ts#L21-L42](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/error.ts#L21-L42)
- The inspected request pipeline stores transient state in FetchContext and response._data; transport is the external output boundary rather than a repository persistence layer. — [src/fetch.ts#L93-L103](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L93-L103) [src/fetch.ts#L180-L184](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L180-L184) [src/types.ts#L94-L99](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L94-L99) [src/types.ts#L137-L139](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L137-L139)
- pnpm 10.20.0 drives obuild, Vitest, ESLint, and Prettier scripts; release additionally invokes changelogen with alpha publishing and push flags. — [package.json#L15-L38](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/package.json#L15-L38) [pnpm-lock.yaml#L9-L46](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/pnpm-lock.yaml#L9-L46)
- CI declares Ubuntu jobs across Node 20, 22, and 24; autofix separately runs lint:fix with read-only contents permission and a pinned autofix action. — [.github/workflows/ci.yml#L5-L19](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/.github/workflows/ci.yml#L5-L19) [.github/workflows/autofix.yml#L5-L18](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/.github/workflows/autofix.yml#L5-L18)

## Feature catalog

The user-facing surface is an HTTP client API with typed options and results. Automatic serialization and decoding remove common fetch boilerplate. Factories and hooks support shared application behavior, while retry and timeout options control failures. Raw responses, streams, and transport dispatchers expose lower-level capabilities.
- $fetch returns decoded _data, raw returns the augmented Response, and native delegates directly to the captured fetch transport. — [src/fetch.ts#L259-L266](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L259-L266) [src/types.ts#L5-L16](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L5-L16)
- Response decoding supports JSON, text, blob, arrayBuffer, and stream; parseResponse supplies a custom synchronous parser of response text. — [src/types.ts#L38-L40](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L38-L40) [src/types.ts#L123-L135](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L123-L135) [src/fetch.ts#L208-L232](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L208-L232)
- Payload handling serializes eligible objects, preserves strings, selects URL-encoded serialization for the exact form content type, and supplies missing JSON headers. — [src/fetch.ts#L130-L153](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L130-L153) [examples/body.mjs#L3-L9](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/examples/body.mjs#L3-L9)
- FormData and URLSearchParams bypass JSON classification, while web and Node stream bodies automatically receive duplex half unless explicitly configured. — [src/utils.ts#L31-L42](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.ts#L31-L42) [src/fetch.ts#L154-L165](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L154-L165)
- String requests support baseURL and query parameters; params remains a deprecated alias, with values merged from defaults and individual calls. — [src/types.ts#L25-L36](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L25-L36) [src/utils.ts#L101-L109](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.ts#L101-L109) [src/fetch.ts#L114-L127](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L114-L127) [examples/query-string.mjs#L3-L7](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/examples/query-string.mjs#L3-L7)
- Users can configure retry count, eligible status codes, fixed or contextual delay, timeout milliseconds, and suppression of HTTP status errors. — [src/types.ts#L29-L29](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L29-L29) [src/types.ts#L61-L70](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L61-L70) [src/fetch.ts#L49-L75](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L49-L75) [src/fetch.ts#L242-L254](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L242-L254)
- Four lifecycle hooks accept functions or arrays, and create builds reusable clients with shared options and optional custom transport. — [src/types.ts#L80-L83](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L80-L83) [src/types.ts#L108-L117](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L108-L117) [src/fetch.ts#L268-L277](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L268-L277) [src/utils.ts#L140-L152](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.ts#L140-L152)
- Node transport customization includes an undici dispatcher option; documentation demonstrates ProxyAgent, SSE stream consumption, and FetchOptions augmentation. — [src/types.ts#L49-L59](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L49-L59) [README.md#L293-L307](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/README.md#L293-L307) [README.md#L314-L324](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/README.md#L314-L324) [README.md#L356-L383](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/README.md#L356-L383)

## Data flow

Inputs enter as FetchRequest and FetchOptions, then become a mutable FetchContext. Resolution merges shared defaults with call-specific headers and query values before hooks and normalization. Fetch produces a Response whose body becomes _data, which hooks can inspect before return or rejection. Retries carry transformed context back through the same pipeline.
- Each raw call allocates a context containing the original request, resolved options, and initially undefined response and error fields. — [src/fetch.ts#L89-L103](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L89-L103) [src/types.ts#L94-L99](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L94-L99)
- Header resolution chooses explicit input headers over Request headers, then overlays them on defaults using Headers.set; it does not merge Request headers when explicit headers are supplied. — [src/utils.ts#L94-L99](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.ts#L94-L99) [src/utils.ts#L121-L137](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.ts#L121-L137) [test/index.test.ts#L385-L397](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/test/index.test.ts#L385-L397)
- Query precedence is default params, default query, input params, then input query; other options use shallow defaults/input spread. — [src/utils.ts#L101-L118](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.ts#L101-L118)
- Method capitalization precedes onRequest; that hook can change the request or options before string URL transformation and body serialization. — [src/fetch.ts#L105-L130](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L105-L130) [src/types.ts#L104-L110](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L104-L110)
- withQuery omits undefined values in new queries; in existing queries it deletes undefined keys, appends array elements, and replaces scalar values. — [src/utils.url.ts#L64-L102](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.url.ts#L64-L102)
- Query normalization turns null into an empty string, numbers and booleans into strings, and objects into JSON; serialization uses URLSearchParams. — [src/utils.url.ts#L72-L83](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.url.ts#L72-L83) [src/utils.url.ts#L105-L118](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.url.ts#L105-L118)
- Truthy timeout adds a generated AbortSignal, optionally combined with the caller signal, before resolved options cross the fetch boundary as RequestInit. — [src/fetch.ts#L171-L184](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L171-L184)
- Decoded data belongs to response._data; the normal client returns that value, while FetchError getters retain access to context and parsed error data. — [src/fetch.ts#L215-L230](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L215-L230) [src/fetch.ts#L259-L262](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L259-L262) [src/error.ts#L44-L63](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/error.ts#L44-L63)

## Behavior traces

Source reading followed initialization, a JSON POST, and HTTP failure with retry through their complete call chains. Additional branches cover custom clients, streaming, and transport rejection. The same raw function owns preprocessing, fetch, decoding, hooks, and status handling. These are static execution traces rather than observed runs.
- Initialization proceeds index.ts → base.ts exports → entrypoint fetch wrapper → createFetch({ fetch }) → ofetch/$fetch aliases; unsupported global fetch uses the rejecting wrapper. — [src/index.ts#L1-L15](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/index.ts#L1-L15) [src/base.ts#L1-L2](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/base.ts#L1-L2) [src/fetch.ts#L36-L37](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L36-L37) [src/fetch.ts#L259-L279](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L259-L279)
- A JSON POST proceeds $fetch → $fetchRaw → resolveFetchOptions → uppercase method → onRequest → withBase/withQuery → isJSONSerializable → JSON.stringify and default headers. — [examples/body.mjs#L3-L9](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/examples/body.mjs#L3-L9) [src/fetch.ts#L93-L153](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L93-L153) [src/fetch.ts#L259-L262](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L259-L262) [src/utils.ts#L17-L42](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.ts#L17-L42)
- After POST preparation, fetch runs → response type is selected → response text is parsed into _data → onResponse runs → status is checked → $fetch returns _data. — [src/fetch.ts#L180-L184](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L180-L184) [src/fetch.ts#L200-L221](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L200-L221) [src/fetch.ts#L235-L262](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L235-L262)
- A qualifying 408 response is decoded first → onResponse → onResponseError → onError → retry budget/status check → optional retryDelay → recursive $fetchRaw with retry decremented. — [src/fetch.ts#L49-L75](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L49-L75) [src/fetch.ts#L208-L221](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L208-L221) [src/fetch.ts#L235-L254](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L235-L254) [test/index.test.ts#L270-L295](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/test/index.test.ts#L270-L295)
- When retry is unavailable or exhausted, onError calls createFetchError, optionally trims the V8 stack, and throws an error exposing URL, status, context, and decoded body. — [src/fetch.ts#L79-L86](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L79-L86) [src/error.ts#L24-L63](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/error.ts#L24-L63)
- A transport rejection sets context.error → awaits onRequestError → enters onError; AbortError suppresses automatic retry only when timeout is absent. — [src/fetch.ts#L39-L49](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L39-L49) [src/fetch.ts#L185-L193](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L185-L193) [test/index.test.ts#L298-L310](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/test/index.test.ts#L298-L310)
- Client creation proceeds $fetch.create → shallow global/default merging → createFetch; later calls independently merge headers and query through resolveFetchOptions. — [src/fetch.ts#L268-L277](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L268-L277) [src/utils.ts#L94-L118](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.ts#L94-L118) [test/index.test.ts#L347-L382](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/test/index.test.ts#L347-L382)
- An SSE response follows content-type detection → stream branch → response.body or _bodyInit assigned to _data → caller receives the stream and reads chunks itself. — [src/utils.ts#L72-L76](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.ts#L72-L76) [src/fetch.ts#L224-L227](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L224-L227) [src/fetch.ts#L259-L262](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L259-L262) [README.md#L297-L306](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/README.md#L297-L306)

## Testing signals

Tests and CI were inspected, not executed; no passing result or measured coverage is established. The inspected Vitest suite primarily exercises native fetch against a local H3 server. Coverage spans serialization, decoding, headers, errors, hooks, retries, and timeout behavior, with some assertions narrower than their titles imply. This is a bounded inventory rather than exhaustive behavioral assurance.
- test/index.test.ts uses Vitest, a global fetch spy, and an H3 server bound to an ephemeral localhost port, with lifecycle setup and teardown. — [test/index.test.ts#L1-L22](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/test/index.test.ts#L1-L22) [test/index.test.ts#L67-L76](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/test/index.test.ts#L67-L76)
- Decoding tests cover custom parseResponse, explicit JSON/blob/text/arrayBuffer selection, binary Blob detection, 204, and HEAD. — [test/index.test.ts#L82-L111](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/test/index.test.ts#L82-L111) [test/index.test.ts#L253-L261](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/test/index.test.ts#L253-L261)
- Payload tests cover object/array serialization, header representations, string and Buffer bodies, web/Node streams, FormData, and URLSearchParams. — [test/index.test.ts#L119-L159](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/test/index.test.ts#L119-L159) [test/index.test.ts#L161-L231](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/test/index.test.ts#L161-L231)
- Error/default tests assert 404 error context and parsed body, ignoreResponseError, header/query precedence, and Request header overrides. — [test/index.test.ts#L234-L250](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/test/index.test.ts#L234-L250) [test/index.test.ts#L347-L397](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/test/index.test.ts#L347-L397)
- Retry-delay tests compare competing requests by Promise.race; abort tests assert rejection, and timeout tests assert relative completion and timeout cause fields rather than exact retry counts. — [test/index.test.ts#L270-L310](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/test/index.test.ts#L270-L310) [test/index.test.ts#L324-L344](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/test/index.test.ts#L324-L344)
- Hook tests assert propagation of exceptions from all four hooks and callback counts for successful/error responses with single hooks and arrays. — [test/index.test.ts#L400-L435](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/test/index.test.ts#L400-L435) [test/index.test.ts#L438-L505](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/test/index.test.ts#L438-L505)
- The inspected suite's final options test contacts jsonplaceholder; it does not establish SSE, proxy/TLS, malformed-JSON, fragment handling, or streamed-body replay coverage. — [test/index.test.ts#L508-L525](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/test/index.test.ts#L508-L525) [src/fetch.ts#L72-L75](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L72-L75) [src/fetch.ts#L215-L230](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L215-L230) [src/utils.url.ts#L69-L102](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.url.ts#L69-L102)
- Inspected configuration enables V8 coverage with src inclusion and text/clover/json reporters; CI declares Node 20/22/24 lint, build, coverage, and Codecov without a browser or worker job. — [vitest.config.ts#L3-L9](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/vitest.config.ts#L3-L9) [package.json#L22-L26](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/package.json#L22-L26) [.github/workflows/ci.yml#L7-L19](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/.github/workflows/ci.yml#L7-L19)

## Risk map

The main review hotspots are URL rewriting, retry replay, abort composition, and error exposure. Several observable boundaries bypass normalized FetchError handling. Documentation also contains compatibility descriptions that differ from this alpha implementation. The following are source observations or explicitly marked hypotheses, not confirmed vulnerabilities.
- FetchError messages include the full request URL, and getters expose request/options/response; logging these objects can expose sensitive query or header data supplied by callers. (inferred) — [src/error.ts#L26-L49](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/error.ts#L26-L49) [examples/error-handling.mjs#L7-L12](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/examples/error-handling.mjs#L7-L12) [examples/headers.mjs#L5-L6](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/examples/headers.mjs#L5-L6)
- withBase uses a string prefix check and slash joining rather than origin-aware URL resolution; withQuery splits only at '?', with no explicit fragment handling. — [src/utils.url.ts#L40-L50](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.url.ts#L40-L50) [src/utils.url.ts#L69-L102](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.url.ts#L69-L102)
- Explicit numeric retries can replay payload methods and reuse transformed bodies; consumed streams and non-idempotent operations therefore need targeted replay validation. (inferred) — [src/fetch.ts#L49-L75](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L49-L75) [src/fetch.ts#L154-L165](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L154-L165) [src/utils.ts#L10-L14](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.ts#L10-L14)
- Retries retain context.options.signal, then compose another timeout signal; AbortError retry suppression is disabled whenever a timeout option is present. — [src/fetch.ts#L43-L47](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L43-L47) [src/fetch.ts#L72-L75](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L72-L75) [src/fetch.ts#L171-L177](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L171-L177)
- Serialization, timeout construction, JSON parsing, and most hooks lie outside the fetch try/catch, so their exceptions can bypass onRequestError and normalized retry/error handling. — [src/fetch.ts#L110-L177](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L110-L177) [src/fetch.ts#L180-L198](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L180-L198) [src/fetch.ts#L215-L253](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L215-L253)
- isJSONSerializable accesses value.buffer after checking typeof object; its earlier 't === null' condition cannot recognize null, although top-level falsy bodies skip the caller's payload branch. — [src/utils.ts#L17-L32](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.ts#L17-L32) [src/fetch.ts#L130-L131](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L130-L131)
- README names ufo for URL handling and describes response.ok-based failures, while current source uses local URL helpers and specifically rejects statuses 400–599. — [README.md#L78-L80](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/README.md#L78-L80) [README.md#L148-L158](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/README.md#L148-L158) [src/fetch.ts#L1-L3](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L1-L3) [src/fetch.ts#L242-L246](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L242-L246)
- Network-sensitive examples include GH_TOKEN authentication and public gist creation; README demonstrates disabling TLS verification, and the release script publishes and pushes. — [examples/headers.mjs#L3-L17](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/examples/headers.mjs#L3-L17) [README.md#L334-L342](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/README.md#L334-L342) [package.json#L21-L21](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/package.json#L21-L21)

## Extension points

Extension primarily happens through factories, typed options, and lifecycle hooks. Transport injection permits alternative fetch implementations without changing the request pipeline. Parsers and response mappings offer decoding customization, while dispatchers expose runtime-specific networking. Source-level helper tables define the narrower places where core behavior can be extended.
- CreateFetchOptions.fetch injects a fetch-compatible transport into createFetch, allowing tests or adapters to replace network execution. — [src/types.ts#L80-L83](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L80-L83) [src/fetch.ts#L36-L37](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L36-L37) [src/fetch.ts#L180-L184](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L180-L184)
- $fetch.create accepts shared defaults and custom global options, preserving inherited globals while new defaultOptions have final shallow precedence. — [src/types.ts#L15-L15](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L15-L15) [src/fetch.ts#L268-L277](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L268-L277)
- FetchHooks defines four typed lifecycle interfaces; callHooks awaits array members sequentially using the same mutable context. — [src/types.ts#L94-L117](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L94-L117) [src/utils.ts#L140-L152](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.ts#L140-L152)
- parseResponse replaces JSON.parse after text consumption and takes precedence over responseType by selecting the JSON branch. — [src/types.ts#L38-L40](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L38-L40) [src/fetch.ts#L209-L220](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L209-L220)
- ResponseMap and MappedResponseType centralize compile-time result mapping; adding response types requires coordinating these types with runtime decoding. — [src/types.ts#L123-L135](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L123-L135) [src/fetch.ts#L215-L230](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L215-L230)
- retryStatusCodes and retryDelay permit application-specific retry selection and contextual delay calculation without modifying the built-in status set. — [src/types.ts#L64-L70](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L64-L70) [src/fetch.ts#L57-L69](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L57-L69)
- FetchOptions can be augmented through TypeScript module declaration merging, with custom fields available to shared onRequest hooks. — [README.md#L356-L383](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/README.md#L356-L383) [src/types.ts#L22-L24](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L22-L24)
- dispatcher and legacy agent options expose transport configuration; core payload-method and content-type sets are localized in utils.ts for source-level extensions. — [src/types.ts#L49-L59](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L49-L59) [src/utils.ts#L10-L14](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.ts#L10-L14) [src/utils.ts#L45-L82](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.ts#L45-L82) [examples/proxy.mjs#L3-L16](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/examples/proxy.mjs#L3-L16)

## Contributor handoff

Start with the public factory and raw pipeline, then follow the helper or type implicated by the task. Preserve option precedence and lifecycle ordering, which affect every client call. Tests and CI were inspected, not executed; their existence supplies investigation targets rather than passing evidence. The report inspected 17 source/example/test files plus documentation and configuration, but remains bounded rather than exhaustive.
- Read index.ts, base.ts, fetch.ts, and types.ts first to distinguish exported client aliases, runtime factories, and generic response contracts. — [src/index.ts#L1-L15](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/index.ts#L1-L15) [src/base.ts#L1-L2](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/base.ts#L1-L2) [src/fetch.ts#L259-L279](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L259-L279) [src/types.ts#L5-L16](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L5-L16)
- Preserve explicit header replacement and query precedence when editing resolution; the defaults test exercises specialized merging rather than arbitrary recursive deep merging. — [src/utils.ts#L94-L118](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.ts#L94-L118) [test/index.test.ts#L347-L397](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/test/index.test.ts#L347-L397)
- Treat fetch.ts as a broad-impact edit boundary: onRequest precedes normalization, decoding precedes onResponse, and onResponse precedes status failure handling. — [src/fetch.ts#L105-L130](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L105-L130) [src/fetch.ts#L208-L254](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L208-L254)
- Use local URL helper behavior as authority despite README's ufo references; query array append/delete rules and base prefix logic deserve explicit regression cases. — [README.md#L148-L158](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/README.md#L148-L158) [src/utils.url.ts#L40-L50](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.url.ts#L40-L50) [src/utils.url.ts#L89-L102](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/utils.url.ts#L89-L102)
- Keep .ts import extensions and explicit public declarations consistent with strict NodeNext, isolatedDeclarations, and noEmit configuration. — [tsconfig.json#L2-L20](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/tsconfig.json#L2-L20) [src/fetch.ts#L1-L19](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/fetch.ts#L1-L19) [src/types.ts#L123-L139](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/src/types.ts#L123-L139)
- Preserve pnpm and repository lint conventions: ESLint extends unjs, Prettier specifies es5 trailing commas, and Renovate extends the shared unjs configuration. — [package.json#L18-L19](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/package.json#L18-L19) [package.json#L38-L38](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/package.json#L38-L38) [eslint.config.mjs#L1-L12](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/eslint.config.mjs#L1-L12) [.prettierrc#L1-L3](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/.prettierrc#L1-L3) [renovate.json#L1-L3](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/renovate.json#L1-L3)
- Tests and CI were inspected, not executed. In a future execution-authorized task, use focused Vitest cases first, then pnpm test/build as appropriate; account for the suite's external jsonplaceholder calls. — [package.json#L16-L22](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/package.json#L16-L22) [test/index.test.ts#L508-L525](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/test/index.test.ts#L508-L525) [.github/workflows/ci.yml#L15-L19](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/.github/workflows/ci.yml#L15-L19)
- Avoid running examples or release as routine validation: headers.mjs reads GH_TOKEN and posts a public gist, playground contacts httpstat.us, and release publishes an alpha package and pushes. — [examples/headers.mjs#L3-L17](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/examples/headers.mjs#L3-L17) [test/playground.ts#L3-L15](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/test/playground.ts#L3-L15) [package.json#L21-L21](https://github.com/unjs/ofetch/blob/1dbc37fd1ceab832fc7c90cad81b1091c95ba563/package.json#L21-L21)

Source repository: https://github.com/unjs/ofetch
Full evidence corpus: https://github.com/Codevetter/codevetter/tree/main/benchmarks/repo-unpacks/ofetch.json

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
