{"id":13392535,"url":"https://github.com/elbywan/wretch","last_synced_at":"2025-05-14T08:05:19.104Z","repository":{"id":41499383,"uuid":"103435985","full_name":"elbywan/wretch","owner":"elbywan","description":"A tiny wrapper built around fetch with an intuitive syntax. :candy:","archived":false,"fork":false,"pushed_at":"2025-02-20T09:21:15.000Z","size":4639,"stargazers_count":4958,"open_issues_count":2,"forks_count":100,"subscribers_count":23,"default_branch":"master","last_synced_at":"2025-05-14T08:05:03.706Z","etag":null,"topics":["ajax","fetch","formdata","http","http-client","http-request","javascript","json","nodejs","promise","request","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/elbywan.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"elbywan"}},"created_at":"2017-09-13T18:27:25.000Z","updated_at":"2025-05-12T18:40:28.000Z","dependencies_parsed_at":"2022-08-10T02:35:07.884Z","dependency_job_id":"cb2c9ff7-e8e6-4704-9062-2a8c1552b17e","html_url":"https://github.com/elbywan/wretch","commit_stats":{"total_commits":438,"total_committers":22,"mean_commits":19.90909090909091,"dds":0.2442922374429224,"last_synced_commit":"db86a5f0aa4f3d5b826a4bb4917d828183c1212a"},"previous_names":[],"tags_count":75,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elbywan%2Fwretch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elbywan%2Fwretch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elbywan%2Fwretch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elbywan%2Fwretch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/elbywan","download_url":"https://codeload.github.com/elbywan/wretch/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254101588,"owners_count":22014907,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ajax","fetch","formdata","http","http-client","http-request","javascript","json","nodejs","promise","request","typescript"],"created_at":"2024-07-30T17:00:26.974Z","updated_at":"2025-05-14T08:05:19.074Z","avatar_url":"https://github.com/elbywan.png","language":"TypeScript","readme":"\u003ch1 align=\"center\"\u003e\n\t\u003ca href=\"https://elbywan.github.io/wretch\"\u003e\u003cimg src=\"assets/wretch.svg\" alt=\"wretch-logo\" width=\"220px\"\u003e\u003c/a\u003e\u003cbr\u003e\n\t\u003cbr\u003e\n    \u003ca href=\"https://elbywan.github.io/wretch\"\u003eWretch\u003c/a\u003e\u003cbr\u003e\n\t\u003cbr\u003e\n  \u003ca href=\"https://github.com/elbywan/wretch/actions/workflows/node.yml\"\u003e\u003cimg alt=\"github-badge\" src=\"https://github.com/elbywan/wretch/actions/workflows/node.yml/badge.svg\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/wretch\"\u003e\u003cimg alt=\"npm-badge\" src=\"https://img.shields.io/npm/v/wretch.svg?colorB=ff733e\" height=\"20\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/wretch\"\u003e\u003cimg alt=\"npm-downloads-badge\" src=\"https://img.shields.io/npm/dm/wretch.svg?colorB=53aabb\" height=\"20\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://coveralls.io/github/elbywan/wretch?branch=master\"\u003e\u003cimg src=\"https://coveralls.io/repos/github/elbywan/wretch/badge.svg?branch=master\" alt=\"Coverage Status\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://bundlejs.com/?q=wretch#sharing\"\u003e\u003cimg src='https://deno.bundlejs.com/badge?q=wretch'/\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/elbywan/wretch/blob/master/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-blue.svg\" alt=\"license-badge\" height=\"20\"\u003e\u003c/a\u003e\n\u003c/h1\u003e\n\u003ch4 align=\"center\"\u003e\n\tA tiny (~2KB g-zipped) wrapper built around \u003ca href=\"https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch\"\u003efetch\u003c/a\u003e with an intuitive syntax.\n\u003c/h4\u003e\n\u003ch5 align=\"center\"\u003e\n    \u003ci\u003ef[ETCH] [WR]apper\u003c/i\u003e\n\u003c/h6\u003e\n\n\u003cbr\u003e\n\n##### Wretch 2.11 is now live 🎉 ! Please have a look at the [releases](https://github.com/elbywan/wretch/releases) and the [changelog](https://github.com/elbywan/wretch/blob/master/CHANGELOG.md) after each update for new features and breaking changes. If you want to try out the hot stuff, please look into the [dev](https://github.com/elbywan/wretch/tree/dev) branch.\n\n##### And if you like the library please consider becoming a [sponsor](https://github.com/sponsors/elbywan) ❤️.\n\n# Features\n\n#### `wretch` is a small wrapper around [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) designed to simplify the way to perform network requests and handle responses.\n\n- 🪶 **Small** - core is less than 2KB g-zipped\n- 💡 **Intuitive** - lean API, handles errors, headers and (de)serialization\n- 🧊 **Immutable** - every call creates a cloned instance that can then be reused safely\n- 🔌 **Modular** - plug addons to add new features, and middlewares to intercept requests\n- 🧩 **Isomorphic** - compatible with modern browsers, Node.js 14+ and Deno\n- 🦺 **Type safe** - strongly typed, written in TypeScript\n- ✅ **Proven** - fully covered by unit tests and widely used\n- 💓 **Maintained** - alive and well for many years\n\n# Table of Contents\n\n- [**Motivation**](#motivation)\n- [**Installation**](#installation)\n- [**Compatibility**](#compatibility)\n- [**Usage**](#usage)\n- [**Api**](#api-)\n- [**Addons**](#addons)\n- [**Middlewares**](#middlewares)\n- [**Migration from v1**](#migration-from-v1)\n- [**License**](#license)\n\n# Motivation\n\n#### Because having to write a second callback to process a response body feels awkward.\n\nFetch needs a second callback to process the response body.\n\n```javascript\nfetch(\"examples/example.json\")\n  .then(response =\u003e response.json())\n  .then(json =\u003e {\n    //Do stuff with the parsed json\n  });\n```\n\nWretch does it for you.\n\n```javascript\n// Use .res for the raw response, .text for raw text, .json for json, .blob for a blob ...\nwretch(\"examples/example.json\")\n  .get()\n  .json(json =\u003e {\n    // Do stuff with the parsed json\n  });\n```\n\n#### Because manually checking and throwing every request error code is tedious.\n\nFetch won’t reject on HTTP error status.\n\n```javascript\nfetch(\"anything\")\n  .then(response =\u003e {\n    if(!response.ok) {\n      if(response.status === 404) throw new Error(\"Not found\")\n      else if(response.status === 401) throw new Error(\"Unauthorized\")\n      else if(response.status === 418) throw new Error(\"I'm a teapot !\")\n      else throw new Error(\"Other error\")\n    }\n    else // ...\n  })\n  .then(data =\u003e /* ... */)\n  .catch(error =\u003e { /* ... */ })\n```\n\nWretch throws when the response is not successful and contains helper methods to handle common codes.\n\n```javascript\nwretch(\"anything\")\n  .get()\n  .notFound(error =\u003e { /* ... */ })\n  .unauthorized(error =\u003e { /* ... */ })\n  .error(418, error =\u003e { /* ... */ })\n  .res(response =\u003e /* ... */)\n  .catch(error =\u003e { /* uncaught errors */ })\n```\n\n#### Because sending a json object should be easy.\n\nWith fetch you have to set the header, the method and the body manually.\n\n```javascript\nfetch(\"endpoint\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body: JSON.stringify({ \"hello\": \"world\" })\n}).then(response =\u003e /* ... */)\n// Omitting the data retrieval and error management parts…\n```\n\nWith wretch, you have shorthands at your disposal.\n\n```javascript\nwretch(\"endpoint\")\n  .post({ \"hello\": \"world\" })\n  .res(response =\u003e /* ... */)\n```\n\n#### Because configuration should not rhyme with repetition.\n\nA Wretch object is immutable which means that you can reuse previous instances safely.\n\n```javascript\n// Cross origin authenticated requests on an external API\nconst externalApi = wretch(\"http://external.api\") // Base url\n  // Authorization header\n  .auth(`Bearer ${token}`)\n  // Cors fetch options\n  .options({ credentials: \"include\", mode: \"cors\" })\n  // Handle 403 errors\n  .resolve((_) =\u003e _.forbidden(handle403));\n\n// Fetch a resource\nconst resource = await externalApi\n  // Add a custom header for this request\n  .headers({ \"If-Unmodified-Since\": \"Wed, 21 Oct 2015 07:28:00 GMT\" })\n  .get(\"/resource/1\")\n  .json(handleResource);\n\n// Post a resource\nexternalApi\n  .url(\"/resource\")\n  .post({ \"Shiny new\": \"resource object\" })\n  .json(handleNewResourceResult);\n```\n\n# Installation\n\n## Package Manager\n\n```sh\nnpm i wretch # or yarn/pnpm add wretch\n```\n\n## \u0026lt;script\u0026gt; tag\n\nThe package contains multiple bundles depending on the format and feature set located under the `/dist/bundle` folder.\n\n\u003cdetails\u003e\n\n\u003csummary\u003eBundle variants\u003c/summary\u003e\n\u003cbr\u003e\n\n\u003e 💡 If you pick the core bundle, then to plug addons you must import them separately from `/dist/bundle/addons/[addonName].min.js`\n\n| Feature set        | File Name           |\n| ------------------ | ------------------- |\n| Core features only | `wretch.min.js`     |\n| Core + all addons  | `wretch.all.min.js` |\n\n| Format   | Extension  |\n| -------- | ---------- |\n| ESM      | `.min.mjs` |\n| CommonJS | `.min.cjs` |\n| UMD      | `.min.js`  |\n\n\u003c/details\u003e\n\n```html\n\u003c!--\n  Pick your favourite CDN:\n    - https://unpkg.com/wretch\n    - https://cdn.jsdelivr.net/npm/wretch/\n    - https://www.skypack.dev/view/wretch\n    - https://cdnjs.com/libraries/wretch\n    - …\n--\u003e\n\n\u003c!-- UMD import as window.wretch --\u003e\n\u003cscript src=\"https://unpkg.com/wretch\"\u003e\u003c/script\u003e\n\n\u003c!-- Modern import --\u003e\n\u003cscript type=\"module\"\u003e\n  import wretch from 'https://cdn.skypack.dev/wretch/dist/bundle/wretch.all.min.mjs'\n\n  // ... //\n\u003c/script\u003e\n```\n\n# Compatibility\n\n## Browsers\n\n`wretch@^2` is compatible with modern browsers only. For older browsers please use `wretch@^1`.\n\n## Node.js\n\nWretch is compatible with and tested in _Node.js \u003e= 14_. Older versions of node may work\nbut it is not guaranteed.\n\n### Polyfills (Node.js \u003c 18)\n\n**Starting from Node.js 18, [node includes experimental fetch support](https://nodejs.org/en/blog/announcements/v18-release-announce/). Wretch will work without installing any polyfill.**\n\nFor older versions, the Node.js standard library does not provide a native implementation of fetch (and other Browsers-only APIs) and polyfilling is mandatory.\n\n_The non-global way (preferred):_\n\n```javascript\nimport fetch, { FormData } from \"node-fetch\"\n\n// w is a reusable wretch instance\nconst w = wretch().polyfills({\n  fetch,\n  FormData,\n});\n```\n\n_Globally:_\n\n```javascript\nimport fetch, { FormData } from \"node-fetch\";\n\n// Either mutate the global object…\nglobal.fetch = fetch;\nglobal.FormData = FormData;\n\n// …or use the static wretch.polyfills method to impact every wretch instance created afterwards.\nwretch.polyfills({\n  fetch,\n  FormData,\n});\n```\n\n## Deno\n\nWorks with [Deno](https://deno.land/) \u003e=\n[0.41.0](https://github.com/denoland/deno/releases/tag/v0.41.0) out of the box.\n\nTypes should be imported from `/dist/types.d.ts`.\n\n```ts\n// You can import wretch from any CDN that serve ESModules.\nimport wretch from \"https://cdn.skypack.dev/wretch\";\n\nconst text = await wretch(\"https://httpstat.us\").get(\"/200\").text();\nconsole.log(text); // -\u003e 200 OK\n```\n\n# Usage\n\n## Import\n\n```typescript\n// ECMAScript modules\nimport wretch from \"wretch\"\n// CommonJS\nconst wretch = require(\"wretch\")\n// Global variable (script tag)\nwindow.wretch\n```\n\n## Minimal Example\n\n```js\nimport wretch from \"wretch\"\n\n// Instantiate and configure wretch\nconst api =\n  wretch(\"https://jsonplaceholder.typicode.com\", { mode: \"cors\" })\n    .errorType(\"json\")\n    .resolve(r =\u003e r.json())\n\ntry {\n  // Fetch users\n  const users = await api.get(\"/users\")\n  // Find all posts from a given user\n  const user = users.find(({ name }) =\u003e name === \"Nicholas Runolfsdottir V\")\n  const postsByUser = await api.get(`/posts?userId=${user.id}`)\n  // Create a new post\n  const newPost = await api.url(\"/posts\").post({\n    title: \"New Post\",\n    body: \"My shiny new post\"\n  })\n  // Patch it\n  await api.url(\"/posts/\" + newPost.id).patch({\n    title: \"Updated Post\",\n    body: \"Edited body\"\n  })\n  // Fetch it\n  await api.get(\"/posts/\" + newPost.id)\n} catch (error) {\n  // The API could return an empty object - in which case the status text is logged instead.\n  const message =\n    typeof error.message === \"object\" \u0026\u0026 Object.keys(error.message).length \u003e 0\n      ? JSON.stringify(error.message)\n      : error.response.statusText\n  console.error(`${error.status}: ${message}`)\n}\n```\n\n## Chaining\n\n**A high level overview of the successive steps that can be chained to perform a request and parse the result.**\n\n```ts\n// First, instantiate wretch\nwretch(baseUrl, baseOptions)\n```\n\n_The \"request\" chain starts here._\n\n```ts\n  // Optional - A set of helper methods to set the default options, set accept header, change the current url…\n  .\u003chelper method(s)\u003e()\n  // Optional - Serialize an object to json or FormData formats and sets the body \u0026 header field if needed\n  .\u003cbody type\u003e()\n    // Required - Sends the get/put/post/delete/patch request.\n  .\u003chttp method\u003e()\n```\n\n_The \"response\" chain starts here._\n\n_Fetch is called after the request chain ends and before the response chain starts._\u003cbr\u003e\n_The request is on the fly and now it is time to chain catchers and finally call a response type handler._\n\n```ts\n  // Optional - You can chain error handlers here\n  .\u003ccatcher(s)\u003e()\n  // Required - Specify the data type you need, which will be parsed and handed to you\n  .\u003cresponse type\u003e()\n  // \u003e\u003e Ends the response chain.\n```\n\n_From this point on, wretch returns a standard Promise._\n\n```ts\n  .then(…)\n  .catch(…)\n```\n\n# [API 🔗](https://elbywan.github.io/wretch/api)\n\n\u003e 💡 The API documentation is now autogenerated and hosted separately, click the links access it.\n\n### [Static Methods 🔗](https://elbywan.github.io/wretch/api/functions/index.default)\n\nThese methods are available from the main default export and can be used to instantiate wretch and configure it globally.\n\n```js\nimport wretch from \"wretch\"\n\nwretch.options({ mode: \"cors\" })\n\nlet w = wretch(\"http://domain.com/\", { cache: \"default\" })\n```\n\n### [Helper Methods 🔗](https://elbywan.github.io/wretch/api/interfaces/index.Wretch#accept)\n\nHelper Methods are used to configure the request and program actions.\n\n```js\nw = w\n  .url(\"/resource/1\")\n  .headers({ \"Cache-Control\": no-cache })\n  .content(\"text/html\")\n```\n\n### [Body Types 🔗](https://elbywan.github.io/wretch/api/interfaces/index.Wretch#body)\n\nSpecify a body type if uploading data. Can also be added through the HTTP Method argument.\n\n```js\nw = w.body(\"\u003chtml\u003e\u003cbody\u003e\u003cdiv/\u003e\u003c/body\u003e\u003c/html\u003e\")\n```\n\n### [HTTP Methods 🔗](https://elbywan.github.io/wretch/api/interfaces/index.Wretch#delete)\n\nSets the HTTP method and sends the request.\n\nCalling an HTTP method ends the request chain and returns a response chain.\nYou can pass optional url and body arguments to these methods.\n\n```js\n// These shorthands:\nwretch().get(\"/url\");\nwretch().post({ json: \"body\" }, \"/url\");\n// Are equivalent to:\nwretch().url(\"/url\").get();\nwretch().json({ json: \"body\" }).url(\"/url\").post();\n```\n\n**NOTE:** if the body argument is an `Object` it is assumed that it is a JSON payload and it will have the same behaviour as calling `.json(body)` unless the `Content-Type` header has been set to something else beforehand.\n\n\n### [Catchers 🔗](https://elbywan.github.io/wretch/api/interfaces/index.WretchResponseChain#badRequest)\n\nCatchers are optional, but if none are provided an error will still be thrown for http error codes and it will be up to you to catch it.\n\n```js\nwretch(\"...\")\n  .get()\n  .badRequest((err) =\u003e console.log(err.status))\n  .unauthorized((err) =\u003e console.log(err.status))\n  .forbidden((err) =\u003e console.log(err.status))\n  .notFound((err) =\u003e console.log(err.status))\n  .timeout((err) =\u003e console.log(err.status))\n  .internalError((err) =\u003e console.log(err.status))\n  .error(418, (err) =\u003e console.log(err.status))\n  .fetchError((err) =\u003e console.log(err))\n  .res();\n```\n\nThe error passed to catchers is enhanced with additional properties.\n\n```ts\ntype WretchError = Error \u0026 {\n  status: number;\n  response: WretchResponse;\n  text?: string;\n  json?: Object;\n};\n```\n\nThe original request is passed along the error and can be used in order to\nperform an additional request.\n\n```js\nwretch(\"/resource\")\n  .get()\n  .unauthorized(async (error, req) =\u003e {\n    // Renew credentials\n    const token = await wretch(\"/renewtoken\").get().text();\n    storeToken(token);\n    // Replay the original request with new credentials\n    return req.auth(token).get().unauthorized((err) =\u003e {\n      throw err;\n    }).json();\n  })\n  .json()\n  // The promise chain is preserved as expected\n  // \".then\" will be performed on the result of the original request\n  // or the replayed one (if a 401 error was thrown)\n  .then(callback);\n```\n\n### [Response Types 🔗](https://elbywan.github.io/wretch/api/interfaces/index.WretchResponseChain#arrayBuffer)\n\nSetting the final response body type ends the chain and returns a regular promise.\n\nAll these methods accept an optional callback, and will return a Promise\nresolved with either the return value of the provided callback or the expected\ntype.\n\n```js\n// Without a callback\nwretch(\"...\").get().json().then(json =\u003e /* json is the parsed json of the response body */)\n// Without a callback using await\nconst json = await wretch(\"...\").get().json()\n// With a callback the value returned is passed to the Promise\nwretch(\"...\").get().json(json =\u003e \"Hello world!\").then(console.log) // =\u003e Hello world!\n```\n\n_If an error is caught by catchers, the response type handler will not be\ncalled._\n\n# Addons\n\nAddons are separate pieces of code that you can import and plug into `wretch` to add new features.\n\n```js\nimport FormDataAddon from \"wretch/addons/formData\"\nimport QueryStringAddon from \"wretch/addons/queryString\"\n\n// Add both addons\nconst w = wretch().addon(FormDataAddon).addon(QueryStringAddon)\n\n// Additional features are now available\nw.formData({ hello: \"world\" }).query({ check: true })\n```\n\nTypescript should also be fully supported and will provide completions.\n\nhttps://user-images.githubusercontent.com/3428394/182319457-504a0856-abdd-4c1d-bd04-df5a061e515d.mov\n\n### [QueryString 🔗](https://elbywan.github.io/wretch/api/interfaces/addons_queryString.QueryStringAddon)\n\nUsed to construct and append the query string part of the URL from an object.\n\n```js\nimport QueryStringAddon from \"wretch/addons/queryString\"\n\nlet w = wretch(\"http://example.com\").addon(QueryStringAddon);\n// url is http://example.com\nw = w.query({ a: 1, b: 2 });\n// url is now http://example.com?a=1\u0026b=2\nw = w.query({ c: 3, d: [4, 5] });\n// url is now http://example.com?a=1\u0026b=2c=3\u0026d=4\u0026d=5\nw = w.query(\"five\u0026six\u0026seven=eight\");\n// url is now http://example.com?a=1\u0026b=2c=3\u0026d=4\u0026d=5\u0026five\u0026six\u0026seven=eight\nw = w.query({ reset: true }, true);\n// url is now  http://example.com?reset=true\n```\n\n### [FormData 🔗](https://elbywan.github.io/wretch/api/interfaces/addons_formData.FormDataAddon)\n\nAdds a helper method to serialize a `multipart/form-data` body from an object.\n\n```js\nimport FormDataAddon from \"wretch/addons/formData\"\n\nconst form = {\n  duck: \"Muscovy\",\n  duckProperties: {\n    beak: {\n      color: \"yellow\",\n    },\n    legs: 2,\n  },\n  ignored: {\n    key: 0,\n  },\n};\n\n// Will append the following keys to the FormData payload:\n// \"duck\", \"duckProperties[beak][color]\", \"duckProperties[legs]\"\nwretch(\"...\").addon(FormDataAddon).formData(form, [\"ignored\"]).post();\n```\n\n### [FormUrl 🔗](https://elbywan.github.io/wretch/api/interfaces/addons_formUrl.FormUrlAddon)\n\nAdds a method to serialize a `application/x-www-form-urlencoded` body from an object.\n\n```js\nimport FormUrlAddon from \"wretch/addons/formUrl\"\n\nconst form = { a: 1, b: { c: 2 } };\nconst alreadyEncodedForm = \"a=1\u0026b=%7B%22c%22%3A2%7D\";\n\n// Automatically sets the content-type header to \"application/x-www-form-urlencoded\"\nwretch(\"...\").addon(FormUrlAddon).formUrl(form).post();\nwretch(\"...\").addon(FormUrlAddon).formUrl(alreadyEncodedForm).post();\n```\n\n### [Abort 🔗](https://elbywan.github.io/wretch/api/functions/addons_abort.default)\n\nAdds the ability to abort requests and set timeouts using AbortController and signals under the hood.\n\n```js\nimport AbortAddon from \"wretch/addons/abort\"\n```\n\n_Only compatible with browsers that support\n[AbortControllers](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).\nOtherwise, you could use a (partial)\n[polyfill](https://www.npmjs.com/package/abortcontroller-polyfill)._\n\nUse cases :\n\n```js\nconst [c, w] = wretch(\"...\")\n  .addon(AbortAddon())\n  .get()\n  .onAbort((_) =\u003e console.log(\"Aborted !\"))\n  .controller();\n\nw.text((_) =\u003e console.log(\"should never be called\"));\nc.abort();\n\n// Or :\n\nconst controller = new AbortController();\n\nwretch(\"...\")\n  .addon(AbortAddon())\n  .signal(controller)\n  .get()\n  .onAbort((_) =\u003e console.log(\"Aborted !\"))\n  .text((_) =\u003e console.log(\"should never be called\"));\n\ncontroller.abort();\n```\n\n```js\n// 1 second timeout\nwretch(\"...\").addon(AbortAddon()).get().setTimeout(1000).json(_ =\u003e\n  // will not be called if the request timeouts\n)\n```\n\n### [BasicAuth 🔗](https://elbywan.github.io/wretch/api/interfaces/addons_basicAuth.BasicAuthAddon)\n\nAdds the ability to set the `Authorization` header for the [basic authentication scheme](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#basic_authentication_scheme) without the need to manually encode the username/password.\n\nAlso, allows using URLs with `wretch` that contain credentials, which would otherwise throw an error.\n\n```js\nimport BasicAuthAddon from \"wretch/addons/basicAuth\"\n\nconst user = \"user\"\nconst pass = \"pass\"\n\n// Automatically sets the Authorization header to \"Basic \" + \u003cbase64 encoded credentials\u003e\nwretch(\"...\").addon(BasicAuthAddon).basicAuth(user, pass).get()\n\n// Allows using URLs with credentials in them\nwretch(`https://${user}:${pass}@...`).addon(BasicAuthAddon).get()\n```\n\n### [Progress 🔗](https://elbywan.github.io/wretch/api/interfaces/addons_progress.ProgressResolver)\n\nAdds the ability to monitor progress when downloading a response.\n\n_Compatible with all platforms implementing the [TransformStream WebAPI](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream#browser_compatibility)._\n\n\n```js\nimport ProgressAddon from \"wretch/addons/progress\"\n\nwretch(\"some_url\")\n  .addon(ProgressAddon())\n  .get()\n  // Called with the number of bytes loaded and the total number of bytes to load\n  .progress((loaded, total) =\u003e {\n    console.log(`${(loaded / total * 100).toFixed(0)}%`)\n  })\n  .text()\n```\n\n### [Performance 🔗](https://elbywan.github.io/wretch/api/functions/addons_perfs.default)\n\nAdds the ability to measure requests using the Performance Timings API.\n\nUses the Performance API (browsers \u0026 Node.js) to expose timings related to the underlying request.\n\n\u003e 💡 Make sure to follow the additional instructions in the documentation to setup Node.js if necessary.\n\n# Middlewares\n\nMiddlewares are functions that can intercept requests before being processed by\nFetch. Wretch includes a helper to help replicate the\n[middleware](http://expressjs.com/en/guide/using-middleware.html) style.\n\n```js\nimport wretch from \"wretch\"\nimport { retry, dedupe } from \"wretch/middlewares\"\n\nconst w = wretch().middlewares([retry(), dedupe()])\n```\n\n\u003e 💡 The following middlewares were previously provided by the [`wretch-middlewares`](https://github.com/elbywan/wretch-middlewares/) package.\n\n### [Retry 🔗](https://elbywan.github.io/wretch/api/types/middlewares_retry.RetryMiddleware)\n\n**Retries a request multiple times in case of an error (or until a custom condition is true).**\n\n\u003e **💡 By default, the request will be retried if the response status is not in the 2xx range.**\n\u003e\n\u003e ```js\n\u003e // Replace the default condition with a custom one to avoid retrying on 4xx errors:\n\u003e until: (response, error) =\u003e !!response \u0026\u0026 (response.ok || (response.status \u003e= 400 \u0026\u0026 response.status \u003c 500))\n\u003e ```\n\n```js\nimport wretch from 'wretch'\nimport { retry } from 'wretch/middlewares'\n\nwretch().middlewares([\n  retry({\n    /* Options - defaults below */\n    delayTimer: 500,\n    delayRamp: (delay, nbOfAttempts) =\u003e delay * nbOfAttempts,\n    maxAttempts: 10,\n    until: (response, error) =\u003e !!response \u0026\u0026 response.ok,\n    onRetry: undefined,\n    retryOnNetworkError: false,\n    resolveWithLatestResponse: false\n  })\n])\n\n// You can also return a Promise, which is useful if you want to inspect the body:\nwretch().middlewares([\n  retry({\n    until: response =\u003e\n      response?.clone().json().then(body =\u003e\n        body.field === 'something'\n      ) || false\n  })\n])\n```\n\n### [Dedupe 🔗](https://elbywan.github.io/wretch/api/types/middlewares_dedupe.DedupeMiddleware)\n\n**Prevents having multiple identical requests on the fly at the same time.**\n\n```js\nimport wretch from 'wretch'\nimport { dedupe } from 'wretch/middlewares'\n\nwretch().middlewares([\n  dedupe({\n    /* Options - defaults below */\n    skip: (url, opts) =\u003e opts.skipDedupe || opts.method !== 'GET',\n    key: (url, opts) =\u003e opts.method + '@' + url,\n    resolver: response =\u003e response.clone()\n  })\n])\n```\n\n### [Throttling Cache 🔗](https://elbywan.github.io/wretch/api/types/middlewares_throttlingCache.ThrottlingCacheMiddleware)\n\n**A throttling cache which stores and serves server responses for a certain amount of time.**\n\n```js\nimport wretch from 'wretch'\nimport { throttlingCache } from 'wretch/middlewares'\n\nwretch().middlewares([\n  throttlingCache({\n    /* Options - defaults below */\n    throttle: 1000,\n    skip: (url, opts) =\u003e opts.skipCache || opts.method !== 'GET',\n    key: (url, opts) =\u003e opts.method + '@' + url,\n    clear: (url, opts) =\u003e false,\n    invalidate: (url, opts) =\u003e null,\n    condition: response =\u003e response.ok,\n    flagResponseOnCacheHit: '__cached'\n  })\n])\n```\n\n### [Delay 🔗](https://elbywan.github.io/wretch/api/types/middlewares_delay.DelayMiddleware)\n\n**Delays the request by a specific amount of time.**\n\n```js\nimport wretch from 'wretch'\nimport { delay } from 'wretch/middlewares'\n\nwretch().middlewares([\n  delay(1000)\n])\n```\n\n## Writing a Middleware\n\nBasically a Middleware is a function having the following signature :\n\n```ts\n// A middleware accepts options and returns a configured version\ntype Middleware = (options?: { [key: string]: any }) =\u003e ConfiguredMiddleware;\n// A configured middleware (with options curried)\ntype ConfiguredMiddleware = (next: FetchLike) =\u003e FetchLike;\n// A \"fetch like\" function, accepting an url and fetch options and returning a response promise\ntype FetchLike = (\n  url: string,\n  opts: WretchOptions,\n) =\u003e Promise\u003cWretchResponse\u003e;\n```\n\n### Context\n\nIf you need to manipulate data within your middleware and expose it for later\nconsumption, a solution could be to pass a named property to the wretch options\n(_suggested name: `context`_).\n\nYour middleware can then take advantage of that by mutating the object\nreference.\n\n```js\nconst contextMiddleware = (next) =\u003e\n  (url, opts) =\u003e {\n    if (opts.context) {\n      // Mutate \"context\"\n      opts.context.property = \"anything\";\n    }\n    return next(url, opts);\n  };\n\n// Provide the reference to a \"context\" object\nconst context = {};\nconst res = await wretch(\"...\")\n  // Pass \"context\" by reference as an option\n  .options({ context })\n  .middlewares([contextMiddleware])\n  .get()\n  .res();\n\nconsole.log(context.property); // prints \"anything\"\n```\n\n### Advanced examples\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u0026nbsp;\u003cstrong\u003e👀 Show me the code\u003c/strong\u003e\u003c/summary\u003e\n  \u003cbr\u003e\n\n```javascript\n/* A simple delay middleware. */\nconst delayMiddleware = delay =\u003e next =\u003e (url, opts) =\u003e {\n  return new Promise(res =\u003e setTimeout(() =\u003e res(next(url, opts)), delay))\n}\n\n/* Returns the url and method without performing an actual request. */\nconst shortCircuitMiddleware = () =\u003e next =\u003e (url, opts) =\u003e {\n  // We create a new Response object to comply because wretch expects that from fetch.\n  const response = new Response()\n  response.text = () =\u003e Promise.resolve(opts.method + \"@\" + url)\n  response.json = () =\u003e Promise.resolve({ url, method: opts.method })\n  // Instead of calling next(), returning a Response Promise bypasses the rest of the chain.\n  return Promise.resolve(response)\n}\n\n/* Logs all requests passing through. */\nconst logMiddleware = () =\u003e next =\u003e (url, opts) =\u003e {\n  console.log(opts.method + \"@\" + url)\n  return next(url, opts)\n}\n\n/* A throttling cache. */\nconst cacheMiddleware = (throttle = 0) =\u003e {\n\n  const cache = new Map()\n  const inflight = new Map()\n  const throttling = new Set()\n\n  return next =\u003e (url, opts) =\u003e {\n    const key = opts.method + \"@\" + url\n\n    if(!opts.noCache \u0026\u0026 throttling.has(key)) {\n      // If the cache contains a previous response and we are throttling, serve it and bypass the chain.\n      if(cache.has(key))\n        return Promise.resolve(cache.get(key).clone())\n      // If the request in already in-flight, wait until it is resolved\n      else if(inflight.has(key)) {\n        return new Promise((resolve, reject) =\u003e {\n          inflight.get(key).push([resolve, reject])\n        })\n      }\n    }\n\n    // Init. the pending promises Map\n    if(!inflight.has(key))\n      inflight.set(key, [])\n\n    // If we are not throttling, activate the throttle for X milliseconds\n    if(throttle \u0026\u0026 !throttling.has(key)) {\n      throttling.add(key)\n      setTimeout(() =\u003e { throttling.delete(key) }, throttle)\n    }\n\n    // We call the next middleware in the chain.\n    return next(url, opts)\n      .then(_ =\u003e {\n        // Add a cloned response to the cache\n        cache.set(key, _.clone())\n        // Resolve pending promises\n        inflight.get(key).forEach((([resolve, reject]) =\u003e resolve(_.clone()))\n        // Remove the inflight pending promises\n        inflight.delete(key)\n        // Return the original response\n        return _\n      })\n      .catch(_ =\u003e {\n        // Reject pending promises on error\n        inflight.get(key).forEach(([resolve, reject]) =\u003e reject(_))\n        inflight.delete(key)\n        throw _\n      })\n  }\n}\n\n// To call a single middleware\nconst cache = cacheMiddleware(1000)\nwretch(\"...\").middlewares([cache]).get()\n\n// To chain middlewares\nwretch(\"...\").middlewares([\n  logMiddleware(),\n  delayMiddleware(1000),\n  shortCircuitMiddleware()\n}).get().text(_ =\u003e console.log(text))\n\n// To test the cache middleware more thoroughly\nconst wretchCache = wretch().middlewares([cacheMiddleware(1000)])\nconst printResource = (url, timeout = 0) =\u003e\n  setTimeout(_ =\u003e wretchCache.url(url).get().notFound(console.error).text(console.log), timeout)\n// The resource url, change it to an invalid route to check the error handling\nconst resourceUrl = \"/\"\n// Only two actual requests are made here even though there are 30 calls\nfor(let i = 0; i \u003c 10; i++) {\n  printResource(resourceUrl)\n  printResource(resourceUrl, 500)\n  printResource(resourceUrl, 1500)\n}\n```\n\n\u003c/details\u003e\n\n# Limitations\n\n## [Cloudflare Workers](https://workers.cloudflare.com/)\n\nIt seems like using `wretch` in a Cloudflare Worker environment is not possible out of the box, as the Cloudflare `Response` implementation does not implement the [`type`](https://developer.mozilla.org/en-US/docs/Web/API/Response/type) property and throws an error when trying to access it.\n\n#### Please check the issue [#159](https://github.com/elbywan/wretch/issues/159) for more information.\n\n### Workaround\n\nThe following middleware should fix the issue (thanks @jimmed 🙇):\n\n```js\nwretch().middlewares([\n  (next) =\u003e async (url, opts) =\u003e {\n    const response = await next(url, opts);\n    try {\n      Reflect.get(response, \"type\", response);\n    } catch (error) {\n      Object.defineProperty(response, \"type\", {\n        get: () =\u003e \"default\",\n      });\n    }\n    return response;\n  },\n])\n```\n\n## Headers Case Sensitivity\n\nThe [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object from the Fetch API uses the [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) class to store headers under the hood.\nThis class is case-insensitive, meaning that setting both will actually appends the value to the same key:\n\n```js\nconst headers = new Headers();\nheaders.append(\"Accept\", \"application/json\");\nheaders.append(\"accept\", \"application/json\");\nheaders.forEach((value, key) =\u003e console.log(key, value));\n// prints: accept application/json, application/json\n```\n\nWhen using `wretch`, please be mindful of this limitation and avoid setting the same header multiple times with a different case:\n\n```js\nwretch(url)\n  .headers({ \"content-type\": \"application/json\" })\n  // .json is a shortcut for .headers(\"Content-Type\": \"application/json\").post().json()\n  .json({ foo: \"bar\" })\n  // Wretch stores the headers inside a plain javascript object and will not deduplicate them.\n  // Later on when fetch builds the Headers object the content type header will be set twice\n  // and its value will be \"application/json, application/json\".\n  // Ultimately this is certainly not what you want.\n```\n\n#### Please check the issue [#80](https://github.com/elbywan/wretch/issues/80) for more information.\n\n### Workaround\n\nYou can use the following middleware to deduplicate headers (thanks @jimmed 🙇):\n\n```js\nexport const manipulateHeaders =\n  callback =\u003e next =\u003e (url, { headers, ...opts }) =\u003e {\n    const nextHeaders = callback(new Headers(headers))\n    return next(url, { ...opts, headers: nextHeaders })\n  }\n\nexport const dedupeHeaders = (dedupeHeaderLogic = {}) =\u003e {\n  const deduperMap = new Map(\n    Object.entries(dedupeHeaderLogic).map(([k, v]) =\u003e [k.toLowerCase(), v]),\n  )\n  const dedupe = key =\u003e\n    deduperMap.get(key.toLowerCase()) ?? (values =\u003e new Set(values))\n\n  return manipulateHeaders((headers) =\u003e {\n    Object.entries(headers.raw()).forEach(([key, values]) =\u003e {\n      const deduped = Array.from(dedupe(key)(values))\n      headers.delete(key)\n      deduped.forEach((value, index) =\u003e\n        headers[index ? 'append' : 'set'](key.toLowerCase(), value),\n      )\n    })\n    return headers\n  })\n}\n\n// By default, it will deduplicate identical values for a given header. This can be used as follows:\nwretch().middlewares([dedupeHeaders()])\n// If there is a specific header for which the defaults cause problems, then you can provide a callback to handle deduplication yourself:\nwretch().middlewares([\n  dedupeHeaders({\n    Accept: (values) =\u003e values.filter(v =\u003e v !== '*/*')\n  })\n])\n```\n\n# Migration from v1\n\n## Philosophy\n\nWretch has been **completely rewritten** with the following goals in mind:\n\n- reduce its size by making it modular\n- preserve the typescript type coverage\n- improve the API by removing several awkward choices\n\n## Compatibility\n\n`wretch@1` was transpiled to es5, `wretch@2` is now transpiled to es2018.\nAny \"modern\" browser and Node.js versions \u003e= 14 should parse the library without issues.\n\nIf you need compatibility with older browsers/nodejs versions then either stick with v1, use poyfills\nor configure `@babel` to make it transpile wretch.\n\n## Addons\n\nSome features that were part of `wretch` v1 are now split apart and must be imported through addons.\nIt is now needed to pass the Addon to the [`.addon`](#addonaddon-wretchaddon) method to register it.\n\nPlease refer to the [Addons](#addons) documentation.\n\n ```js\n/* Previously (wretch@1) */\nimport wretch from \"wretch\"\n\nwretch.formData({ hello: \"world\" }).query({ check: true })\n\n/* Now (wretch@2) */\nimport FormDataAddon from \"wretch/addons/formData\"\nimport QueryStringAddon from \"wretch/addons/queryString\"\nimport baseWretch from \"wretch\"\n\n// Add both addons\nconst wretch = baseWretch().addon(FormDataAddon).addon(QueryStringAddon)\n\n// Additional features are now available\nwretch.formData({ hello: \"world\" }).query({ check: true })\n```\n\n## Typescript\n\nTypes have been renamed and refactored, please update your imports accordingly and refer to the [typescript api documentation](https://elbywan.github.io/wretch/api).\n\n## API Changes\n\n### Replace / Mixin arguments\n\nSome functions used to have a `mixin = true` argument that could be used to merge the value, others a `replace = false` argument performing the opposite.\nIn v2 there are only `replace = false` arguments but the default behaviour should be preserved.\n\n```js\n/* Previously (wretch@1) */\nwretch.options({ credentials: \"same-origin\" }, false) // false: do not merge the value\nwretch.options({ credentials: \"same-origin\" }) // Default behaviour stays the same\n\n/* Now (wretch@2) */\nwretch.options({ credentials: \"same-origin\" }, true) // true: replace the existing value\nwretch.options({ credentials: \"same-origin\" }) // Default behaviour stays the same\n```\n\n### HTTP methods extra argument\n\nIn v1 it was possible to set fetch options while calling the http methods to end the request chain.\n\n```js\n/* Previously (wretch@1) */\nwretch(\"...\").get({ my: \"option\" })\n```\n\nThis was a rarely used feature and the extra argument now appends a string to the base url.\n\n```js\n/* Now (wretch@2) */\nwretch(\"https://base.com\").get(\"/resource/1\")\n```\n\n### Replay function\n\nThe `.replay` function has been renamed to [`.fetch`](https://elbywan.github.io/wretch/api/interfaces/index.Wretch#fetch).\n\n# License\n\nMIT\n\n","funding_links":["https://github.com/sponsors/elbywan"],"categories":["TypeScript","HTTP Client","API","JavaScript","API [🔝](#readme)","Built with TypeScript","Table of Contents","API Layer"],"sub_categories":["Runner","Web","运行器","运行器e2e测试","HTTP","Reactive Programming"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felbywan%2Fwretch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felbywan%2Fwretch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felbywan%2Fwretch/lists"}