name: openfetch description: Use when the user works with @hamdymohamedak/openfetch, openFetch, or a fetch-only HTTP client with interceptors, middleware, plugins (retry, timeout, hooks, debug), memory cache (TTL, stale-while-revalidate), fluent chaining (sugar), raw native Response, idempotency keys, or SSR/RSC-safe fetch in Node, browsers, Bun, Deno, or Workers. Use instead of guessing axios or XHR APIs.
openFetch agent skill
Library version this skill targets: v0.2.9 (@hamdymohamedak/openfetch). Prefer facts here and in quick reference over guessing other clients’ APIs.
Do not assume axios
- Transport is
fetchonly (no XHR). No upload progress bar via XHR. - Package is ESM-only (
importfrom"@hamdymohamedak/openfetch"). - Runtime: Node 18+ or any environment with
fetch+AbortController.
Install & links
- npm:
@hamdymohamedak/openfetch - Source: https://github.com/openfetch-js/OpenFetch
- Human docs: https://openfetch-js.github.io/openfetch-docs/
Imports
Main entry (many symbols are also re-exported here; bundlers can still tree-shake with "sideEffects": false):
import openFetch, {
create,
createClient,
createRetryMiddleware,
createCacheMiddleware,
MemoryCacheStore,
appendCacheKeyVaryHeaders,
OpenFetchError,
isOpenFetchError,
assertSafeHttpUrl,
generateIdempotencyKey,
hasIdempotencyKeyHeader,
ensureIdempotencyKeyHeader,
maskHeaderValues,
cloneResponse,
redactSensitiveUrlQuery,
// Same as @hamdymohamedak/openfetch/plugins:
retry,
timeout,
hooks,
debug,
strictFetch,
// Same as @hamdymohamedak/openfetch/sugar:
createFluentClient,
} from "@hamdymohamedak/openfetch";
Subpaths (explicit splits for tooling that respects package.json exports):
import { retry, timeout, hooks, debug, strictFetch } from "@hamdymohamedak/openfetch/plugins";
import { createFluentClient } from "@hamdymohamedak/openfetch/sugar";
- Default export
openFetch=createClient()with no initial defaults. createis an alias ofcreateClient.
Client & responses
const api = createClient({
baseURL: "https://api.example.com",
headers: { Authorization: "Bearer …" },
timeout: 10_000,
unwrapResponse: true,
assertSafeUrl: true, // runs assertSafeHttpUrl on the resolved URL before fetch
middlewares: [createRetryMiddleware()],
});
unwrapResponse: false(default): helpers returnOpenFetchResponse<T>→{ data, status, statusText, headers, config }.unwrapResponse: true: helpers returnT(body only).rawResponse: true:datais the nativeResponsewith an unread body; adapter skips parse +transformResponse. Request interceptors already ran; response interceptors still run (datais thatResponse). Middleware that assumes parsed JSON inctx.response.datawill not see transforms until you read the body. UsecloneResponsefrom the package if two independent reads are needed.
Merged config must resolve a url (absolute, or baseURL + relative path). Otherwise the client throws an Error whose message includes openfetch: `url` is required.
HTTP helpers
| Call | Notes |
|---|---|
get(url, config?) | Optional params object → query string |
post(url, data?, config?) | Plain objects → JSON + content-type unless overridden; FormData / URLSearchParams / Blob / binary views are not auto-JSON |
put, patch | Same body rules as post |
delete(url, config?) | No data argument; use config.data / config.body if the API needs a body |
head, options | Same style as get |
request(urlOrConfig, config?) | Full method, responseType, rawResponse, etc. |
Plugins (retry, timeout, hooks, debug, strictFetch)
Shorthand factories that return middleware; register with client.use(fn) (same as createRetryMiddleware, with small DX differences).
const client = createClient({ baseURL: "https://api.example.com" })
.use(retry({ attempts: 3, baseDelayMs: 400 })) // register retry before timeout
.use(timeout(12_000))
.use(debug({ includeRequestHeaders: true, maskStrategy: "partial" }));
retry(options)—attemptsaliasesmaxAttempts; supportstimeoutTotalMs,enforceTotalTimeout,timeoutPerAttemptMs,retryNonIdempotentMethods, etc. (see retry section).timeout(ms)— request timeout middleware.hooks({ onRequest, onResponse, onError })— lifecycle logging/tracing; for transforms prefer interceptors.debug({ … })— structured logs; masked headers viamaskStrategy(full|partial|hash); optional masked request headers; URL query redaction aligned withredactSensitiveUrlQuery.strictFetch()— ifredirectis unset, setsredirect: 'error'so redirects are not followed silently.
Order: register retry before timeout so the timeout middleware wraps attempts the way the docs describe.
Fluent client (createFluentClient)
Callable URL + method chaining (Wretch-style): fluent("/path").get().json(), fluent("/x").post(body).send().
- Terminals (
.json(),.text(),.blob(),.arrayBuffer(),.stream(),.send(),.raw()) each start one HTTP request unless.memo()is used on that chain. .memo()— one round-trip; body buffered once asArrayBuffer; multiple terminals reuse it (this is not HTTP cache)..raw()— same semantics asrawResponse: true.
Interceptors vs middleware
- Interceptors:
client.interceptors.request.use/response.use— transform config or response; request stack is LIFO, response FIFO. - Middleware:
client.use(fn)— async(ctx, next) => …around the realfetch; use for retry, cache, logging. Order matters (e.g. cache vs retry).
Retry (createRetryMiddleware / retry())
Exponential backoff, jitter, configurable HTTP statuses and network/parse retries.
- By default, retries for network / parse / retryable HTTP errors apply only to GET, HEAD, OPTIONS, TRACE — not POST/PUT/PATCH/DELETE (avoid duplicate side effects).
- Opt in for mutating methods:
retry: { retryNonIdempotentMethods: true }on the client or per request. - When
retryNonIdempotentMethodsis true,maxAttempts > 1, method is POST, and no idempotency header exists, middleware adds a stableIdempotency-Key(unlessautoIdempotencyKey: false). UsegenerateIdempotencyKey,hasIdempotencyKeyHeader,ensureIdempotencyKeyHeaderfor custom flows. timeoutTotalMs— monotonic budget for the whole retry sequence (including backoff); on expiry:OpenFetchErrorcodeERR_RETRY_TIMEOUT(not retried).enforceTotalTimeout(default true) merges a deadline intosignalper attempt.timeoutPerAttemptMs— overrides per-attemptrequest.timeoutinside the retry middleware.ERR_CANCELED(user/per-attempt timeout abort) is not retried.
Memory cache
new MemoryCacheStore({ maxEntries })+createCacheMiddleware(store, { ttlMs, staleWhileRevalidateMs, methods, key, varyHeaderNames, … }).- Default cache key includes method + full URL;
authorizationandcookieare always folded into the key unlessvaryHeaderNamesis explicitly[](dangerous for shared auth — see package warning / usesuppressAuthCacheKeyWarningonly when intentional). Prefer omittingvaryHeaderNamesor listing extra headers; useappendCacheKeyVaryHeadersfor custom keys. staleWhileRevalidateMs: can serve stale after TTL and refresh in background; that background path callsdispatchdirectly — not everyclient.use()middleware runs for it (see human docs architecture note).
Errors & logging
- Guard with
isOpenFetchError(err). error.toShape()/toJSON()— by default omits responsedataandheaders; passincludeResponseData: true/includeResponseHeaders: trueonly for trusted diagnostics.redactSensitiveUrlQuerydefaults on for the serialized URL in the shape.- The live error may still hold full
config(including secrets) — never forward raw instances to clients.
Security
assertSafeHttpUrl(url)— optional guard for untrusted URLs (http/httpsonly; blocks many literal private/loopback IPs). Does not stop DNS rebinding; pair with hostname allowlists / egress controls when relevant.assertSafeUrl: trueon the client — runsassertSafeHttpUrlon the fully resolved URL beforefetch.
Framework usage (minimal)
- React:
useEffect+await openFetch.get(url); useres.dataunlessunwrapResponseis true on the client. - RSC / server:
await api.get(…)in an async component is fine; keep secrets server-side. - Vue:
onMounted+ assignres.datato aref.
When to refuse or redirect
- Need XHR upload progress → say
fetchdoes not expose it; suggest multipart + server progress, or a different tool. - User asks for openFetch docs verbatim → point to the docs URL above or read quick reference in this folder.
Repo layout (for code navigation)
src/runtime/client.ts— client, verb helperssrc/transport/dispatch.ts—fetch, parse,validateStatus,rawResponsesrc/runtime/middleware.ts,interceptors.ts,retry.ts,cache.tssrc/domain/error.ts—OpenFetchError,toShapesrc/plugins/—retry,timeout,hooks,debug,strictFetchsrc/sugar/fluent.ts—createFluentClient,.memo(),.raw()