idempotent-fetch
v0.1.0
Published
Platform-agnostic fetch wrapper for Idempotency-Key retries, processing polling, and replay detection.
Downloads
133
Maintainers
Readme
idempotent-fetch
idempotent-fetch is a platform-agnostic wrapper around fetch for Idempotency-Key workflows.
Pairs with idempotency_kit for Phoenix/Ecto on the server side.
Runtime support: browsers, React Native, Node 18+, Bun, Deno, and edge runtimes.
It provides:
- automatic idempotency key generation
- network retries with exponential backoff
- processing-state polling
- replay detection via
x-idempotency-status: replayed
Usage
Basic request
import { idempotentFetch } from "idempotent-fetch";
const result = await idempotentFetch("https://api.example.com/workouts", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ name: "Leg day" }),
idempotency: {
keyPrefix: "workout-create",
maxPollAttempts: 30,
pollDelayMs: 2500,
networkRetryAttempts: 3,
timeoutMs: 30_000
}
});
if (result.replayed) {
console.log("response came from idempotency cache");
}
const payload = await result.response.json();Fixed idempotency key
import { idempotentFetch } from "idempotent-fetch";
await idempotentFetch("https://api.example.com/workouts", {
method: "POST",
body: JSON.stringify({ name: "Leg day" }),
idempotency: {
key: "workout-create-123"
}
});Custom processing matcher
import { idempotentFetch } from "idempotent-fetch";
const result = await idempotentFetch("https://api.example.com/jobs", {
method: "POST",
idempotency: {
isProcessingResponse: ({ response }) => response.status === 202
}
});Rebuild non-replayable bodies with requestFactory
import { idempotentFetch } from "idempotent-fetch";
await idempotentFetch("https://api.example.com/upload", {
method: "POST",
requestFactory: () => {
const stream = createFreshReadableStreamSomehow();
return {
body: stream,
headers: {
"content-type": "application/octet-stream"
}
};
},
idempotency: {
networkRetryAttempts: 2
}
});Installation
npm install idempotent-fetchAPI
idempotentFetch(input, options)
Returns:
response: ResponseidempotencyKey: stringreplayed: booleanpollAttempts: numbernetworkAttempts: number
Default processing detection expects:
- HTTP status
409 - JSON body with
errors.code === "idempotency_request_in_progress"
Override via idempotency.isProcessingResponse when needed.
When idempotency.isProcessingResponse is provided, idempotentFetch currently parses a cloned JSON
body eagerly for each response and passes it as responseBody to the matcher.
createIdempotencyKey(prefix?)
Creates a key like <prefix>-<random-id>. If prefix is missing/blank, uses request.
IdempotentFetchError
Errors thrown by retries/polling are instances of IdempotentFetchError with code:
"timeout""network""unknown""still_processing""non_replayable_body"
The original failure is available as error.cause.
Notes
- Works in modern browsers, React Native, Node 18+, Bun, Deno, and edge runtimes with a standards-compatible
fetch. - If
globalThis.fetchis unavailable, pass a fetch implementation viaoptions.fetch. timeoutMs: 0disables timeout.- Retries and polling resend the request. Use replayable bodies (for example JSON strings, blobs) or
requestFactory. Requestinputs that include a body are treated as non-replayable for retries/polls; userequestFactoryor set retries/polls to0.createIdempotencyKeyusescrypto.randomUUID()when available; fallback randomness is non-cryptographic. Supplyidempotency.keyif you need strict key control.
License
MIT
