{"id":13686091,"url":"https://github.com/tulios/mappersmith","last_synced_at":"2025-05-14T02:02:31.485Z","repository":{"id":24499496,"uuid":"27904771","full_name":"tulios/mappersmith","owner":"tulios","description":"is a lightweight rest client for node.js and the browser","archived":false,"fork":false,"pushed_at":"2025-02-20T19:32:58.000Z","size":369112,"stargazers_count":339,"open_issues_count":28,"forks_count":73,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-04-10T16:47:20.334Z","etag":null,"topics":["ajax","isomorphic","javascript","nodejs","promise"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/mappersmith","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/tulios.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"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}},"created_at":"2014-12-12T04:09:18.000Z","updated_at":"2025-03-29T07:40:31.000Z","dependencies_parsed_at":"2024-09-10T11:42:41.785Z","dependency_job_id":"abc195e8-96f4-4a64-83d5-e898d2bbd5f6","html_url":"https://github.com/tulios/mappersmith","commit_stats":{"total_commits":904,"total_committers":60,"mean_commits":"15.066666666666666","dds":0.6592920353982301,"last_synced_commit":"e6f633e8f57cf4eba2661ece47c0bf9d3cd9ade7"},"previous_names":[],"tags_count":120,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tulios%2Fmappersmith","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tulios%2Fmappersmith/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tulios%2Fmappersmith/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tulios%2Fmappersmith/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tulios","download_url":"https://codeload.github.com/tulios/mappersmith/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253497332,"owners_count":21917684,"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","isomorphic","javascript","nodejs","promise"],"created_at":"2024-08-02T14:01:04.849Z","updated_at":"2025-05-14T02:02:26.472Z","avatar_url":"https://github.com/tulios.png","language":"TypeScript","readme":"[![npm version](https://badge.fury.io/js/mappersmith.svg)](http://badge.fury.io/js/mappersmith)\n[![Node.js CI](https://github.com/tulios/mappersmith/actions/workflows/node.js.yml/badge.svg)](https://github.com/tulios/mappersmith/actions/workflows/node.js.yml)\n[![Windows Tests](https://img.shields.io/appveyor/ci/tulios/mappersmith.svg?label=Windows%20Tests)](https://ci.appveyor.com/project/tulios/mappersmith)\n# Mappersmith\n\n__Mappersmith__ is a lightweight rest client for node.js and the browser. It creates a client for your API, gathering all configurations into a single place, freeing your code from HTTP configurations.\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Usage](#usage)\n  - [Commonjs](#commonjs)\n  - [Configuring my resources](#resource-configuration)\n    - [Parameters](#parameters)\n    - [Default parameters](#default-parameters)\n    - [Body](#body)\n    - [Headers](#headers)\n    - [Basic Auth](#basic-auth)\n    - [Timeout](#timeout)\n    - [Abort Signal](#abort-signal)\n    - [Alternative host](#alternative-host)\n    - [Alternative path](#alternative-path)\n    - [Binary data](#binary-data)\n  - [Promises](#promises)\n  - [Response object](#response-object)\n  - [Middleware](#middleware)\n    - [Creating middleware](#creating-middleware)\n      - [Context (deprecated)](#context)\n      - [Optional arguments](#creating-middleware-optional-arguments)\n        - [mockRequest](#creating-middleware-optional-arguments-mock-request)\n        - [Abort](#creating-middleware-optional-arguments-abort)\n        - [Renew](#creating-middleware-optional-arguments-renew)\n        - [request](#creating-middleware-optional-arguments-request)\n    - [Configuring middleware](#configuring-middleware)\n      - [Resource level middleware](#resource-middleware)\n      - [Client level middleware](#client-middleware)\n      - [Global middleware](#global-middleware)\n    - [Built-in middleware](#built-in-middleware)\n      - [BasicAuth](#middleware-basic-auth)\n      - [CSRF](#middleware-csrf)\n      - [Duration](#middleware-duration)\n      - [EncodeJSON](#middleware-encode-json)\n      - [GlobalErrorHandler](#middleware-global-error-handler)\n      - [Log](#middleware-log)\n      - [Retry](#middleware-retry)\n      - [Timeout](#middleware-timeout)\n    - [Middleware legacy notes](#middleware-legacy-notes)\n  - [Testing Mappersmith](#testing-mappersmith)\n  - [Gateways](#gateways)\n  - [TypeScript](#typescript)\n- [Development](#development)\n\n## \u003ca name=\"installation\"\u003e\u003c/a\u003e Installation\n\n```sh\nnpm install mappersmith --save\n```\n\nor\n\n```sh\nyarn add mappersmith\n```\n\n#### Build from the source\n\nInstall the dependencies\n\n```sh\nyarn\n```\n\nBuild\n\n```sh\nyarn build\nyarn release # for minified version\n```\n\n## \u003ca name=\"usage\"\u003e\u003c/a\u003e Usage\n\nTo create a client for your API you will need to provide a simple manifest. If your API reside in the same domain as your app you can skip the `host` configuration. Each resource has a name and a list of methods with its definitions, like:\n\n```typescript\nimport forge, { configs } from \"mappersmith\"\nimport { Fetch } from \"mappersmith/gateway/fetch\"\n\nconfigs.gateway = Fetch;\n\nconst github = forge({\n  clientId: \"github\",\n  host: \"https://www.githubstatus.com\",\n  resources: {\n    Status: {\n      current: { path: \"/api/v2/status.json\" },\n      summary: { path: \"/api/v2/summary.json\" },\n      components: { path: \"/api/v2/components.json\" },\n    },\n  },\n});\n\ngithub.Status.current().then((response) =\u003e {\n  console.log(`summary`, response.data());\n});\n```\n\n## \u003ca name=\"commonjs\"\u003e\u003c/a\u003e Commonjs\n\nIf you are using _commonjs_, your `require` should look like:\n\n```javascript\nconst forge = require(\"mappersmith\").default;\nconst { configs } = require(\"mappersmith\");\nconst FetchGateway = require(\"mappersmith/gateway/fetch\").default;\n```\n\n## \u003ca name=\"resource-configuration\"\u003e\u003c/a\u003e Configuring my resources\n\nEach resource has a name and a list of methods with its definitions. A method definition can have host, path, method, headers, params, bodyAttr, headersAttr and authAttr. Example:\n\n```javascript\nconst client = forge({\n  resources: {\n    User: {\n      all: { path: '/users' },\n\n      // {id} is a dynamic segment and will be replaced by the parameter \"id\"\n      // when called\n      byId: { path: '/users/{id}' },\n\n      // {group} is also a dynamic segment but it has default value \"general\"\n      byGroup: { path: '/users/groups/{group}', params: { group: 'general' } },\n\n      // {market?} is an optional dynamic segment. If called without a value\n      // for the \"market\" parameter, {market?} will be removed from the path\n      // including any prefixing \"/\".\n      // This example: '/{market?}/users' =\u003e '/users'\n      count: { path: '/{market?}/users' } }\n    },\n    Blog: {\n      // The HTTP method can be configured through the `method` key, and a default\n      // header \"X-Special-Header\" has been configured for this resource\n      create: { method: 'post', path: '/blogs', headers: { 'X-Special-Header': 'value' } },\n\n      // There are no restrictions for dynamic segments and HTTP methods\n      addComment: { method: 'put', path: '/blogs/{id}/comment' },\n\n      // `queryParamAlias` will map parameter names to their alias when\n      // constructing the query string\n      bySubject: { path: '/blogs', queryParamAlias: { subjectId: 'subject_id' } },\n\n      // `path` is a function to map passed params to a custom path\n      byDate: { path: ({date}) =\u003e `${date.getYear()}/${date.getMonth()}/${date.getDate()}` }\n    }\n  }\n})\n```\n\n### \u003ca name=\"parameters\"\u003e\u003c/a\u003e Parameters\n\nIf your method doesn't require any parameter, you can just call it without them:\n\n```javascript\nclient.User\n  .all() // https://my.api.com/users\n  .then((response) =\u003e console.log(response.data()))\n  .catch((response) =\u003e console.error(response.data()))\n```\n\nEvery parameter that doesn't match a pattern `{parameter-name}` in path will be sent as part of the query string:\n\n```javascript\nclient.User.all({ active: true }) // https://my.api.com/users?active=true\n```\n\nWhen a method requires a parameters and the method is called without it, __Mappersmith__ will raise an error:\n\n```javascript\nclient.User.byId(/* missing id */)\n// throw '[Mappersmith] required parameter missing (id), \"/users/{id}\" cannot be resolved'\n```\n\nYou can optionally set `parameterEncoder: yourEncodingFunction` to change the default encoding function for parameters. This is useful when you are calling an endpoint which for example requires not encoded characters like `:` that are otherwise encoded by the default behaviour of the `encodeURIComponent` function ([external documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent?retiredLocale=it#description)).\n\n```javascript\nconst client = forge({\n  host: 'https://custom-host.com',\n  parameterEncoder: yourEncodingFunction,\n  resources: { ... }\n})\n```\n\n### \u003ca name=\"default-parameters\"\u003e\u003c/a\u003e Default Parameters\n\nIt is possible to configure default parameters for your resources, just use the key `params` in the definition. It will replace params in the URL or include query strings.\n\nIf we call `client.User.byGroup` without any params it will default `group` to \"general\"\n\n```javascript\nclient.User.byGroup() // https://my.api.com/users/groups/general\n```\n\nAnd, of course, we can override the defaults:\n\n```javascript\nclient.User.byGroup({ group: 'cool' }) // https://my.api.com/users/groups/cool\n```\n\n### \u003ca name=\"aliased-parameters\"\u003e\u003c/a\u003e Renaming query parameters\n\nSometimes the expected format of your query parameters doesn't match that of your codebase. For example, maybe you're using `camelCase` in your code but the API you are calling expects `snake_case`. In that case, set `queryParamAlias` in the definition to an object that describes a mapping between your input parameter and the desired output format.\n\nThis mapping will not be applied to params in the URL.\n\n```javascript\nclient.Blog.all({ subjectId: 10 }) // https://my.api.com/blogs?subject_id=10\n```\n\n### \u003ca name=\"body\"\u003e\u003c/a\u003e Body\n\nTo send values in the request body (usually for POST, PUT or PATCH methods) you will use the special parameter `body`:\n\n```javascript\nclient.Blog.create({\n  body: {\n    title: 'Title',\n    tags: ['party', 'launch']\n  }\n})\n```\n\nBy default, it will create a _urlencoded_ version of the object (`title=Title\u0026tags[]=party\u0026tags[]=launch`). If the body used is not an object it will use the original value. If `body` is not possible as a special parameter for your API you can configure it through the param `bodyAttr`:\n\n```javascript\n// ...\n{\n  create: { method: 'post', path: '/blogs', bodyAttr: 'payload' }\n}\n// ...\n\nclient.Blog.create({\n  payload: {\n    title: 'Title',\n    tags: ['party', 'launch']\n  }\n})\n```\n\n__NOTE__: It's possible to post body as JSON, check the [EncodeJsonMiddleware](#middleware-encode-json) below for more information\n__NOTE__: The `bodyAttr` param can be set at manifest level.\n\n### \u003ca name=\"headers\"\u003e\u003c/a\u003e Headers\n\nTo define headers in the method call use the parameter `headers`:\n\n```javascript\nclient.User.all({ headers: { Authorization: 'token 1d1435k' } })\n```\n\nIf `headers` is not possible as a special parameter for your API you can configure it through the param `headersAttr`:\n\n```javascript\n// ...\n{\n  all: { path: '/users', headersAttr: 'h' }\n}\n// ...\n\nclient.User.all({ h: { Authorization: 'token 1d1435k' } })\n```\n\n__NOTE__: The `headersAttr` param can be set at manifest level.\n\n### \u003ca name=\"basic-auth\"\u003e\u003c/a\u003e Basic auth\n\nTo define credentials for basic auth use the parameter `auth`:\n\n```javascript\nclient.User.all({ auth: { username: 'bob', password: 'bob' } })\n```\n\nThe available attributes are: `username` and `password`.\nThis will set an `Authorization` header. This can still be overridden by custom headers.\n\nIf `auth` is not possible as a special parameter for your API you can configure it through the param `authAttr`:\n\n```javascript\n// ...\n{\n  all: { path: '/users', authAttr: 'secret' }\n}\n// ...\n\nclient.User.all({ secret: { username: 'bob', password: 'bob' } })\n```\n\n__NOTE__: A default basic auth can be configured with the use of the [BasicAuthMiddleware](#middleware-basic-auth), check the middleware section below for more information.\n__NOTE__: The `authAttr` param can be set at manifest level.\n\n### \u003ca name=\"timeout\"\u003e\u003c/a\u003e Timeout\n\nTo define the number of milliseconds before the request times out use the parameter `timeout`:\n\n```javascript\nclient.User.all({ timeout: 1000 })\n```\n\nIf `timeout` is not possible as a special parameter for your API you can configure it through the param `timeoutAttr`:\n\n```javascript\n// ...\n{\n  all: { path: '/users', timeoutAttr: 'maxWait' }\n}\n// ...\n\nclient.User.all({ maxWait: 500 })\n```\n\n__NOTE__: A default timeout can be configured with the use of the [TimeoutMiddleware](#middleware-timeout), check the middleware section below for more information.\n__NOTE__: The `timeoutAttr` param can be set at manifest level.\n\n### \u003ca name=\"abort-signal\"\u003e\u003c/a\u003e Abort Signal\n\nThe [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) interface represents a signal object that allows you to communicate with an asynchronous operation (such as a fetch request) and abort it if required via an AbortController object. All gateway APIs (Fetch, HTTP and XHR) support this interface via the `signal` parameter:\n\n```javascript\nconst abortController = new AbortController()\nclient.User.all({ signal: abortController.signal })\n// abort!\nabortController.abort()\n```\n\nIf `signal` is not possible as a special parameter for your API you can configure it through the param `signalAttr`:\n\n```javascript\n// ...\n{\n  all: { path: '/users', signalAttr: 'abortSignal' }\n}\n// ...\n\nconst abortController = new AbortController()\nclient.User.all({ abortSignal: abortController.signal })\n// abort!\nabortController.abort()\n```\n\n__NOTE__: The `signalAttr` param can be set at manifest level.\n\n### \u003ca name=\"alternative-host\"\u003e\u003c/a\u003e Alternative host\n\nThere are some cases where a resource method resides in another host, in those cases you can use the `host` key to configure a new host:\n\n```javascript\n// ...\n{\n  all: { path: '/users', host: 'http://old-api.com' }\n}\n// ...\n\nclient.User.all() // http://old-api.com/users\n```\n\nIn case you need to overwrite the host for a specific call, you can do so through the param `host`:\n\n```javascript\n// ...\n{\n  all: { path: '/users', host: 'http://old-api.com' }\n}\n// ...\n\nclient.User.all({ host: 'http://very-old-api.com' }) // http://very-old-api.com/users\n```\n\nIf `host` is not possible as a special parameter for your API, you can configure it through the param `hostAttr`:\n\n```javascript\n// ...\n{\n  all: { path: '/users', hostAttr: 'baseUrl' }\n}\n// ...\n\nclient.User.all({ baseUrl: 'http://very-old-api.com' }) // http://very-old-api.com/users\n```\n\n**NOTE**: Since version `2.34.0` you need to also use `allowResourceHostOverride: true`, example:\n\n```javascript\nconst client = forge({\n  host: 'https://new-host.com',\n  allowResourceHostOverride: true,\n  resources: {\n    User: {\n      all: { path: '/users', host: 'https://old-host.com }\n    }\n  }\n})\n```\n\nWhenever using host overrides, be diligent about how you pass parameters to your resource methods. If you spread unverified attributes, you might open your server to SSR attacks.\n\n### \u003ca name=\"alternative-path\"\u003e\u003c/a\u003e Alternative path\n\nIn case you need to overwrite the path for a specific call, you can do so through the param `path`:\n\n```javascript\n// ...\n{\n  all: { path: '/users' }\n}\n// ...\n\nclient.User.all({ path: '/people' })\n```\n\nIf `path` is not possible as a special parameter for your API, you can configure it through the param `pathAttr`:\n\n```javascript\n// ...\n{\n  all: { path: '/users', pathAttr: '__path' }\n}\n// ...\n\nclient.User.all({ __path: '/people' })\n```\n\n### \u003ca name=\"binary-data\"\u003e\u003c/a\u003e Binary data\n\nIf the data being fetched is in binary form, such as a PDF, you may add the `binary` key, and set it to true. The response data will then be a [Buffer](https://nodejs.org/api/buffer.html) in NodeJS, and a [Blob](https://developer.mozilla.org/sv-SE/docs/Web/API/Blob) in the browser.\n\n```javascript\n\n// ...\n{\n  report: { path: '/report.pdf', binary: true }\n}\n// ...\n\n```\n\n## \u003ca name=\"promises\"\u003e\u003c/a\u003e Promises\n\n__Mappersmith__ does not apply any polyfills, it depends on a native Promise implementation to be supported. If your environment doesn't support Promises, please apply the polyfill first. One option can be [then/promises](https://github.com/then/promise)\n\nIn some cases it is not possible to use/assign the global `Promise` constant, for those cases you can define the promise implementation used by Mappersmith.\n\nFor example, using the project [rsvp.js](https://github.com/tildeio/rsvp.js/) (a tiny implementation of Promises/A+):\n\n```javascript\nimport RSVP from 'rsvp'\nimport { configs } from 'mappersmith'\n\nconfigs.Promise = RSVP.Promise\n```\n\nAll `Promise` references in Mappersmith use `configs.Promise`. The default value is the global Promise.\n\n## \u003ca name=\"response-object\"\u003e\u003c/a\u003e Response object\n\nMappersmith will provide an instance of its own `Response` object to the promises. This object has the methods:\n\n* `request()` - Returns the original [Request](https://github.com/tulios/mappersmith/blob/master/src/request.js)\n* `status()` - Returns the status number\n* `success()` - Returns true for status greater than 200 and lower than 400\n* `headers()` - Returns an object with all headers, keys in lower case\n* `header(name)` - Returns the value of the header\n* `data()` - Returns the response data, if `Content-Type` is `application/json` it parses the response and returns an object\n* `error()` - Returns the last error instance that caused the request to fail or `null`\n\n## \u003ca name=\"middleware\"\u003e\u003c/a\u003e Middleware\n\nThe behavior between your client and the API can be customized with middleware. A middleware is a function which returns an object with two methods: request and response.\n\n### \u003ca name=\"creating-middleware\"\u003e\u003c/a\u003e Creating middleware\n\nThe `prepareRequest` method receives a function which returns a `Promise` resolving the [Request](https://github.com/tulios/mappersmith/blob/master/src/request.js). This function must return a `Promise` resolving the request. The method `enhance` can be used to generate a new request based on the previous one.\n\n```javascript\nconst MyMiddleware = () =\u003e ({\n  prepareRequest(next) {\n    return next().then(request =\u003e request.enhance({\n      headers: { 'x-special-request': '-\u003e' }\n    }))\n  }\n})\n```\n\nIf you have multiple middleware it is possible to pass information from an earlier ran middleware to a later one via the request context:\n\n```javascript\nconst MyMiddlewareOne = () =\u003e ({\n  async prepareRequest(next) {\n    const request = await next().then(request =\u003e request.enhance({}, { message: 'hello from mw1' }))\n  }\n})\n\nconst MyMiddlewareTwo = () =\u003e ({\n  async prepareRequest(next) {\n    const request = await next()\n    const { message } = request.getContext()\n    // Logs: \"hello from mw1\"\n    console.log(message)\n    return request\n  }\n})\n```\n\nThe above example assumes you synthesized your middleware in this order when calling `forge`: `middleware: [MyMiddlewareOne, MyMiddlewareTwo]`\n\nThe `response` method receives a function which returns a `Promise` resolving the [Response](https://github.com/tulios/mappersmith/blob/master/src/response.js). This function must return a `Promise` resolving the Response. The method `enhance` can be used to generate a new response based on the previous one.\n\n```javascript\nconst MyMiddleware = () =\u003e ({\n  response(next) {\n    return next().then((response) =\u003e response.enhance({\n      headers: { 'x-special-response': '\u003c-' }\n    }))\n  }\n})\n```\n\n#### \u003ca name=\"context\"\u003e\u003c/a\u003e Context (deprecated)\n\n⚠️ `setContext` is not safe for concurrent use, and shouldn't be used!\n\nWhy is it not safe? Basically, the setContext function mutates a global state (see [here](https://github.com/tulios/mappersmith/blob/2.34.0/src/mappersmith.js#L114)), hence it is the last call to setContext that decides its global value. Which leads to a race condition when handling concurrent requests.\n\n#### \u003ca name=\"creating-middleware-optional-arguments\"\u003e\u003c/a\u003e Optional arguments\n\nIt can, optionally, receive `resourceName`, `resourceMethod`, [#context](`context`), `clientId` and `mockRequest`. Example:\n\n```javascript\nconst MyMiddleware = ({ resourceName, resourceMethod, context, clientId, mockRequest }) =\u003e ({\n  /* ... */\n})\n\nclient.User.all()\n// resourceName: 'User'\n// resourceMethod: 'all'\n// clientId: 'myClient'\n// context: {}\n// mockRequest: false\n```\n\n##### \u003ca name=\"creating-middleware-optional-arguments-mock-request\"\u003e\u003c/a\u003e mockRequest\n\nBefore mocked clients can assert whether or not their mock definition matches a request they have to execute their middleware on that request. This means that middleware might be executed multiple times for the same request. More specifically, the middleware will be executed once per mocked client that utilises the middleware until a mocked client with a matching definition is found. If you want to avoid middleware from being called multiple times you can use the optional \"mockRequest\" boolean flag. The value of this flag will be truthy whenever the middleware is being executed during the mock definition matching phase. Otherwise its value will be falsy. Example:\n\n```javascript\nconst MyMiddleware = ({ mockRequest }) =\u003e {\n  prepareRequest(next) {\n    if (mockRequest) {\n      ... // executed once for each mocked client that utilises the middleware\n    }\n    if (!mockRequest) {\n      ... // executed once for the matching mock definition\n    }\n    return next().then(request =\u003e request)\n  }\n}\n```\n\n##### \u003ca name=\"creating-middleware-optional-arguments-abort\"\u003e\u003c/a\u003e Abort\n\nThe `prepareRequest` phase can optionally receive a function called \"abort\". This function can be used to abort the middleware execution early-on and throw a custom error to the user. Example:\n\n```javascript\nconst MyMiddleware = () =\u003e {\n  prepareRequest(next, abort) {\n    return next().then(request =\u003e\n      request.header('x-special')\n        ? response\n        : abort(new Error('\"x-special\" must be set!'))\n    )\n  }\n}\n```\n\n##### \u003ca name=\"creating-middleware-optional-arguments-renew\"\u003e\u003c/a\u003e Renew\n\nThe `response` phase can optionally receive a function called \"renew\". This function can be used to rerun the middleware stack. This feature is useful in some scenarios, for example, automatically refreshing an expired access token. Example:\n\n```javascript\nconst AccessTokenMiddleware = () =\u003e {\n  // maybe this is stored elsewhere, here for simplicity\n  let accessToken = null\n\n  return () =\u003e ({\n    request(request) {\n      return Promise\n        .resolve(accessToken)\n        .then((token) =\u003e token || fetchAccessToken())\n        .then((token) =\u003e {\n          accessToken = token\n          return request.enhance({\n            headers: { 'Authorization': `Token ${token}` }\n          })\n        })\n    },\n    response(next, renew) {\n      return next().catch(response =\u003e {\n        if (response.status() === 401) { // token expired\n          accessToken = null\n          return renew()\n        }\n\n        return next()\n      })\n    }\n  })\n}\n```\n\nThen:\n\n```javascript\nconst AccessToken = AccessTokenMiddleware()\nconst client = forge({\n  // ...\n  middleware: [ AccessToken ],\n  // ...\n})\n```\n\n\"renew\" can only be invoked sometimes before it's considered an infinite loop, make sure your middleware can distinguish an error from a \"renew\". By default, mappersmith will allow 2 calls to \"renew\". This can be configured with `configs.maxMiddlewareStackExecutionAllowed`. It's advised to keep this number low. Example:\n\n```javascript\nimport { configs } from 'mappersmith'\nconfigs.maxMiddlewareStackExecutionAllowed = 3\n```\n\nIf an infinite loop is detected, mappersmith will throw an error.\n\n##### \u003ca name=\"creating-middleware-optional-arguments-request\"\u003e\u003c/a\u003e request\n\nThe `response` phase can optionally receive an argument called \"request\". This argument is the final request (after the whole middleware chain has prepared and all `prepareRequest` been executed). This is useful in some scenarios, for example when you want to get access to the `request` without invoking `next`:\n\n```javascript\nconst CircuitBreakerMiddleware = () =\u003e {\n  return () =\u003e ({\n    response(next, renew, request) {\n      // Creating the breaker required some information available only on `request`:\n      const breaker = createBreaker({ ..., timeout: request.timeout })\n      // Note: `next` is still wrapped:\n      return breaker.invoke(createExecutor(next))\n    }\n  })\n}\n```\n\n### \u003ca name=\"configuring-middleware\"\u003e\u003c/a\u003e Configuring middleware\n\nMiddleware scope can be Global, Client or on Resource level. The order will be applied in this order: Resource level applies first, then Client level, and finally Global level. The subsections below describes the differences and how to use them correctly.\n\n#### \u003ca name=\"resource-middleware\"\u003e\u003c/a\u003e Resource level middleware\n\nResource middleware are configured using the key `middleware` in the resource level of manifest, example:\n\n```javascript\nconst client = forge({\n  clientId: 'myClient',\n  resources: {\n    User: {\n      all: {\n        // only the `all` resource will include MyMiddleware:\n        middleware: [ MyMiddleware ],\n        path: '/users'\n      }\n    }\n  }\n})\n```\n\n#### \u003ca name=\"client-middleware\"\u003e\u003c/a\u003e Client level middleware\n\nClient middleware are configured using the key `middleware` in the root level of manifest, example:\n\n```javascript\nconst client = forge({\n  clientId: 'myClient',\n  // all resources in this client will include MyMiddleware:\n  middleware: [ MyMiddleware ],\n  resources: {\n    User: {\n      all: { path: '/users' }\n    }\n  }\n})\n```\n\n#### \u003ca name=\"global-middleware\"\u003e\u003c/a\u003e Global middleware\n\nGlobal middleware are configured on a config level, and all new clients will automatically\ninclude the defined middleware, example:\n\n```javascript\nimport { forge, configs } from 'mappersmith'\n\nconfigs.middleware = [MyMiddleware]\n// all clients defined from now on will include MyMiddleware\n```\n\n* Global middleware can be disabled for specific clients with the option `ignoreGlobalMiddleware`, e.g:\n\n```javascript\nforge({\n  ignoreGlobalMiddleware: true,\n  // + the usual configurations\n})\n```\n\n### \u003ca name=\"built-in-middleware\"\u003e\u003c/a\u003e Built-in middleware\n\n#### \u003ca name=\"middleware-basic-auth\"\u003e\u003c/a\u003e BasicAuth\n\nAutomatically configure your requests with basic auth\n\n```javascript\nimport { BasicAuthMiddleware } from 'mappersmith/middleware'\nconst BasicAuth = BasicAuthMiddleware({ username: 'bob', password: 'bob' })\n\nconst client = forge({\n  middleware: [ BasicAuth ],\n  /* ... */\n})\n\nclient.User.all()\n// =\u003e header: \"Authorization: Basic Ym9iOmJvYg==\"\n```\n\n** The default auth can be overridden with the explicit use of the auth parameter, example:\n\n```javascript\nclient.User.all({ auth: { username: 'bill', password: 'bill' } })\n// auth will be { username: 'bill', password: 'bill' } instead of { username: 'bob', password: 'bob' }\n```\n\n#### \u003ca name=\"middleware-csrf\"\u003e\u003c/a\u003e CSRF\n\nAutomatically configure your requests by adding a header with the value of a cookie - If it exists.\nThe name of the cookie (defaults to \"csrfToken\") and the header (defaults to \"x-csrf-token\") can be set as following;\n\n```javascript\nimport { CsrfMiddleware } from 'mappersmith/middleware'\n\nconst client = forge({\n  middleware: [ CsrfMiddleware('csrfToken', 'x-csrf-token') ],\n  /* ... */\n})\n\nclient.User.all()\n```\n\n#### \u003ca name=\"middleware-duration\"\u003e\u003c/a\u003e Duration\n\nAutomatically adds `X-Started-At`, `X-Ended-At` and `X-Duration` headers to the response.\n\n```javascript\nimport { DurationMiddleware } from 'mappersmith/middleware'\n\nconst client = forge({\n  middleware: [ DurationMiddleware ],\n  /* ... */\n})\n\nclient.User.all({ body: { name: 'bob' } })\n// =\u003e headers: \"X-Started-At=1492529128453;X-Ended-At=1492529128473;X-Duration=20\"\n```\n\n#### \u003ca name=\"middleware-encode-json\"\u003e\u003c/a\u003e EncodeJson\n\nAutomatically encode your objects into JSON\n\n```javascript\nimport { EncodeJsonMiddleware } from 'mappersmith/middleware'\n\nconst client = forge({\n  middleware: [ EncodeJsonMiddleware ],\n  /* ... */\n})\n\nclient.User.all({ body: { name: 'bob' } })\n// =\u003e body: {\"name\":\"bob\"}\n// =\u003e header: \"Content-Type=application/json;charset=utf-8\"\n```\n\n#### \u003ca name=\"middleware-global-error-handler\"\u003e\u003c/a\u003e GlobalErrorHandler\n\nProvides a catch-all function for all requests. If the catch-all function returns `true` it prevents the original promise to continue.\n\n```javascript\nimport { GlobalErrorHandlerMiddleware, setErrorHandler } from 'mappersmith/middleware'\n\nsetErrorHandler((response) =\u003e {\n  console.log('global error handler')\n  return response.status() === 500\n})\n\nconst client = forge({\n  middleware: [ GlobalErrorHandlerMiddleware ],\n  /* ... */\n})\n\nclient.User\n  .all()\n  .catch((response) =\u003e console.error('my error'))\n\n// If status != 500\n// output:\n//   -\u003e global error handler\n//   -\u003e my error\n\n// IF status == 500\n// output:\n//   -\u003e global error handler\n```\n\n#### \u003ca name=\"middleware-log\"\u003e\u003c/a\u003e Log\n\nLog all requests and responses. Might be useful in development mode.\n\n```javascript\nimport { LogMiddleware } from 'mappersmith/middleware'\n\nconst client = forge({\n  middleware: [ LogMiddleware ],\n  /* ... */\n})\n```\n\n#### \u003ca name=\"middleware-retry\"\u003e\u003c/a\u003e Retry\n\nThis middleware will automatically retry GET requests up to the configured amount of retries using a randomization function that grows exponentially. The retry count and the time used will be included as a header in the response. By default on requests with response statuses \u003e= 500 will be retried.\n\nIt's possible to configure the header names and parameters used in the calculation by providing a configuration object when creating the middleware.\n\nIf no configuration is passed when creating the middleware then the defaults will be used.\n\n```javascript\nimport { RetryMiddleware } from 'mappersmith/middleware'\n\nconst retryConfigs = {\n  headerRetryCount: 'X-Mappersmith-Retry-Count',\n  headerRetryTime: 'X-Mappersmith-Retry-Time',\n  maxRetryTimeInSecs: 5,\n  initialRetryTimeInSecs: 0.1,\n  factor: 0.2, // randomization factor\n  multiplier: 2, // exponential factor\n  retries: 5, // max retries\n  validateRetry: (response) =\u003e response.responseStatus \u003e= 500 // a function that returns true if the request should be retried\n}\n\nconst client = forge({\n  middleware: [ Retry(retryConfigs) ],\n  /* ... */\n})\n```\n\n#### \u003ca name=\"middleware-timeout\"\u003e\u003c/a\u003e Timeout\n\nAutomatically configure your requests with a default timeout\n\n```javascript\nimport { TimeoutMiddleware } from 'mappersmith/middleware'\nconst Timeout = TimeoutMiddleware(500)\n\nconst client = forge({\n  middleware: [ Timeout ],\n  /* ... */\n})\n\nclient.User.all()\n```\n\n** The default timeout can be overridden with the explicit use of the timeout parameter, example:\n\n```javascript\nclient.User.all({ timeout: 100 })\n// timeout will be 100 instead of 500\n```\n\n### \u003ca name=\"middleware-legacy-notes\"\u003e\u003c/a\u003e Middleware legacy notes\n\nThis section is only relevant for mappersmith versions older than but not including `2.27.0`, when the method `prepareRequest` did not exist. This section describes how to create a middleware using older versions.\n\nSince version `2.27.0` a new method was introduced: `prepareRequest`. This method aims to replace the `request` method in future versions of mappersmith, it has a similar signature as the `response` method and it is always async. All previous middleware are backward compatible, the default implementation of `prepareRequest` will call the `request` method if it exists.\n\nThe `request` method receives an instance of the [Request](https://github.com/tulios/mappersmith/blob/master/src/request.js) object and it must return a Request. The method `enhance` can be used to generate a new request based on the previous one.\n\nExample:\n\n```javascript\nconst MyMiddleware = () =\u003e ({\n  request(request) {\n    return request.enhance({\n      headers: { 'x-special-request': '-\u003e' }\n    })\n  },\n\n  response(next) {\n    return next().then((response) =\u003e response.enhance({\n      headers: { 'x-special-response': '\u003c-' }\n    }))\n  }\n})\n```\n\nThe request phase can be asynchronous, just return a promise resolving a request. Example:\n\n```javascript\nconst MyMiddleware = () =\u003e ({\n  request(request) {\n    return Promise.resolve(\n      request.enhance({\n        headers: { 'x-special-token': 'abc123' }\n      })\n    )\n  }\n})\n```\n\n## \u003ca name=\"testing-mappersmith\"\u003e\u003c/a\u003e Testing Mappersmith\n\nMappersmith plays nice with all test frameworks, the generated client is a plain javascript object and all the methods can be mocked without any problem. However, this experience can be greatly improved with the test library.\n\nThe test library has 4 utilities: `install`, `uninstall`, `mockClient` and `mockRequest`\n\n#### install and uninstall\n\nThey are used to setup the test library, __example using jasmine__:\n\n```javascript\nimport { install, uninstall } from 'mappersmith/test'\n\ndescribe('Feature', () =\u003e {\n  beforeEach(() =\u003e install())\n  afterEach(() =\u003e uninstall())\n})\n```\n\n#### mockClient\n\n`mockClient` offers a high level abstraction, it works directly on your client mocking the resources and their methods.\n\nIt accepts the methods:\n\n* `resource(resourceName)`, ex: `resource('Users')`\n* `method(resourceMethodName)`, ex: `method('byId')`\n* `with(resourceMethodArguments)`, ex: `with({ id: 1 })`\n* `status(statusNumber | statusHandler)`, ex: `status(204)` or `status((request, mock) =\u003e 200)`\n* `headers(responseHeaders)`, ex: `headers({ 'x-header': 'value' })`\n* `response(responseData | responseHandler)`, ex: `response({ user: { id: 1 } })` or `response((request, mock) =\u003e ({ user: { id: request.body().id } }))`\n* `assertObject()`\n* `assertObjectAsync()`\n\nExample using __jasmine__:\n\n```javascript\nimport { forge } from 'mappersmith'\nimport { install, uninstall, mockClient } from 'mappersmith/test'\n\ndescribe('Feature', () =\u003e {\n  beforeEach(() =\u003e install())\n  afterEach(() =\u003e uninstall())\n\n  it('works', (done) =\u003e {\n    const myManifest = {} // Let's assume I have my manifest here\n    const client = forge(myManifest)\n\n    mockClient(client)\n      .resource('User')\n      .method('all')\n      .response({ allUsers: [{id: 1}] })\n\n    // now if I call my resource method, it should return my mock response\n    client.User\n      .all()\n      .then((response) =\u003e expect(response.data()).toEqual({ allUsers: [{id: 1}] }))\n      .then(done)\n  })\n})\n```\n\nTo mock a failure just use the correct HTTP status, example:\n\n```javascript\n// ...\nmockClient(client)\n  .resource('User')\n  .method('byId')\n  .with({ id: 'ABC' })\n  .status(422)\n  .response({ error: 'invalid ID' })\n// ...\n```\n\nThe method `with` accepts the body and headers attributes, example:\n\n```javascript\n// ...\nmockClient(client)\n  .with({\n    id: 'abc',\n    headers: { 'x-special': 'value'},\n    body: { payload: 1 }\n  })\n  // ...\n```\n\nIt's possible to use a match function to assert params and body, example:\n\n```javascript\nimport { m } from 'mappersmith/test'\n\nmockClient(client)\n  .with({\n    id: 'abc',\n    name: m.stringContaining('john'),\n    headers: { 'x-special': 'value'},\n    body: m.stringMatching(/token=[^\u0026]+\u0026other=true$/)\n  })\n```\n\nThe assert object can be used to retrieve the requests that went through the created mock, example:\n\n```javascript\nconst mock = mockClient(client)\n  .resource('User')\n  .method('all')\n  .response({ allUsers: [{id: 1}] })\n  .assertObject()\n\nconsole.log(mock.mostRecentCall())\nconsole.log(mock.callsCount())\nconsole.log(mock.calls())\n```\n\nThe `mock` object is an instance of `MockAssert` and exposes three methods:\n  - _calls()_: returns a `Request` array;\n  - _mostRecentCall()_: returns the last Request made. Returns `null` if array is empty.\n  - _callsCount()_: returns the number of requests that were made through the mocked client;\n\n__Note__:\nThe assert object will also be returned in the `mockRequest` function call.\n\n\nIf you have a middleware with an async request phase use `assertObjectAsync` to await for the middleware execution, example:\n\n```javascript\nconst mock = await mockClient(client)\n  .resource('User')\n  .method('all')\n  .response({ allUsers: [{id: 1}] })\n  .assertObjectAsync()\n\nconsole.log(mock.mostRecentCall())\nconsole.log(mock.callsCount())\nconsole.log(mock.calls())\n```\n\n`response` and `status` can accept functions to generate response body or status. This can be useful when you want to return different responses for the same request being made several times.\n\n```javascript\nconst generateResponse = () =\u003e {\n  return (request, mock) =\u003e mock.callsCount() === 0\n    ? {}\n    : { user: { id: 1 } }\n}\n\nconst mock = mockClient(client)\n  .resource('User')\n  .method('create')\n  .response(generateResponse())\n```\n\n#### mockRequest\n\n`mockRequest` offers a low level abstraction, very useful for automations.\n\nIt accepts the params: method, url, body and response\n\nIt returns an assert object\n\nExample using __jasmine__:\n\n```javascript\nimport { forge } from 'mappersmith'\nimport { install, uninstall, mockRequest } from 'mappersmith/test'\n\ndescribe('Feature', () =\u003e {\n  beforeEach(() =\u003e install())\n  afterEach(() =\u003e uninstall())\n\n  it('works', (done) =\u003e {\n    mockRequest({\n      method: 'get',\n      url: 'https://my.api.com/users?someParam=true',\n      response: {\n        body: { allUsers: [{id: 1}] }\n      }\n    })\n\n    const myManifest = {} // Let's assume I have my manifest here\n    const client = forge(myManifest)\n\n    client.User\n      .all()\n      .then((response) =\u003e expect(response.data()).toEqual({ allUsers: [{id: 1}] }))\n      .then(done)\n  })\n})\n```\n\nA more complete example:\n\n```javascript\n// ...\nmockRequest({\n  method: 'post',\n  url: 'http://example.org/blogs',\n  body: 'param1=A\u0026param2=B', // request body\n  response: {\n    status: 503,\n    body: { error: true },\n    headers: { 'x-header': 'nope' }\n  }\n})\n// ...\n```\n\nIt's possible to use a match function to assert the body and the URL, example:\n\n```javascript\nimport { m } from 'mappersmith/test'\n\nmockRequest({\n  method: 'post',\n  url: m.stringMatching(/example\\.org/),\n  body: m.anything(),\n  response: {\n    body: { allUsers: [{id: 1}] }\n  }\n})\n```\n\nUsing the assert object:\n\n```javascript\nconst mock = mockRequest({\n  method: 'get',\n  url: 'https://my.api.com/users?someParam=true',\n  response: {\n    body: { allUsers: [{id: 1}] }\n  }\n})\n\nconsole.log(mock.mostRecentCall())\nconsole.log(mock.callsCount())\nconsole.log(mock.calls())\n```\n\n#### Match functions\n\n`mockClient` and `mockRequest` accept match functions, the available built-in match functions are:\n\n```javascript\nimport { m } from 'mappersmith/test'\n\nm.stringMatching(/something/) // accepts a regexp\nm.stringContaining('some-string') // accepts a string\nm.anything()\nm.uuid4()\n```\n\nA match function is a function which returns a boolean, example:\n\n```javascript\nmockClient(client)\n  .with({\n    id: 'abc',\n    headers: { 'x-special': 'value'},\n    body: (body) =\u003e body === 'something'\n  })\n```\n\n__Note__:\n`mockClient` only accepts match functions for __body__ and __params__\n`mockRequest` only accepts match functions for __body__ and __url__\n\n#### unusedMocks\n\n`unusedMocks` can be used to check if there are any unused mocks after each test. It\nwill return count of unused mocks. It can be either unused `mockRequest` or `mockClient`.\n\n```javascript\nimport { install, uninstall, unusedMocks } from 'mappersmith/test'\n\ndescribe('Feature', () =\u003e {\n  beforeEach(() =\u003e install())\n  afterEach(() =\u003e {\n    const unusedMocksCount = unusedMocks()\n    uninstall()\n    if (unusedMocksCount \u003e 0) {\n      throw new Error(`There are ${unusedMocksCount} unused mocks`) // fail the test\n    }\n  })\n})\n```\n\n## \u003ca name=\"gateways\"\u003e\u003c/a\u003e Gateways\n\nMappersmith has a pluggable transport layer and it includes by default three gateways: xhr, http and fetch. Mappersmith will pick the correct gateway based on the environment you are running (nodejs, service worker or the browser).\n\nYou can write your own gateway, take a look at [XHR](https://github.com/tulios/mappersmith/blob/master/src/gateway/xhr.js) for an example. To configure, import the `configs` object and assign the gateway option, like:\n\n```javascript\nimport { configs } from 'mappersmith'\nconfigs.gateway = MyGateway\n```\n\nIt's possible to globally configure your gateway through the option `gatewayConfigs`.\n\n### HTTP\n\nWhen running with node.js you can configure the `configure` callback to further customize the `http/https` module, example:\n\n```javascript\nimport fs from 'fs'\nimport https from 'https'\nimport { configs } from 'mappersmith'\n\nconst key = fs.readFileSync('/path/to/my-key.pem')\nconst cert =  fs.readFileSync('/path/to/my-cert.pem')\n\nconfigs.gatewayConfigs.HTTP = {\n  configure() {\n    return {\n      agent: new https.Agent({ key, cert })\n    }\n  }\n}\n```\n\nThe new configurations will be merged. `configure` also receives the `requestParams` as the first argument. Take a look [here](https://github.com/tulios/mappersmith/blob/master/src/mappersmith.js) for more options.\n\nThe HTTP gatewayConfigs also provides several callback functions that will be called when various events are emitted on the `request`, `socket`, and `response` EventEmitters. These callbacks can be used as a hook into the event cycle to execute any custom code.\nFor example, you may want to time how long each stage of the request or response takes.\nThese callback functions will receive the `requestParams` as the first argument.\n\nThe following callbacks are supported:\n* `onRequestWillStart` - This callback is not based on a event emitted by Node but is called just before the `request` method is called.\n* `onRequestSocketAssigned` - Called when the 'socket' event is emitted on the `request`\n* `onSocketLookup` - Called when the `lookup` event is emitted on the `socket`\n* `onSocketConnect` - Called when the `connect` event is emitted on the `socket`\n* `onSocketSecureConnect` - Called when the `secureConnect` event is emitted on the `socket`\n* `onResponseReadable` - Called when the `readable` event is emitted on the `response`\n* `onResponseEnd` - Called when the `end` event is emitted on the `response`\n\n```javascript\nlet startTime\n\nconfigs.gatewayConfigs.HTTP = {\n  onRequestWillStart() {\n    startTime = Date.now()\n  }\n  onResponseReadable() {\n    console.log('Time to first byte', Date.now() - startTime)\n  }\n}\n```\n\n### XHR\n\nWhen running in the browser you can configure `withCredentials` and `configure` to further customize the `XMLHttpRequest` object, example:\n\n```javascript\nimport { configs } from 'mappersmith'\nconfigs.gatewayConfigs.XHR = {\n  withCredentials: true,\n  configure(xhr) {\n    xhr.ontimeout = () =\u003e console.error('timeout!')\n  }\n}\n```\n\nTake a look [here](https://github.com/tulios/mappersmith/blob/master/src/mappersmith.js) for more options.\n\n### Fetch\n\n__Mappersmith__ does not apply any polyfills, it depends on a native `fetch` implementation to be supported. It is possible to assign the fetch implementation used by Mappersmith:\n\n```javascript\nimport { configs } from 'mappersmith'\nconfigs.fetch = fetchFunction\n```\n\nFetch is not used by default, you can configure it through `configs.gateway`.\n\n```javascript\nimport { FetchGateway } from 'mappersmith/gateway'\nimport { configs } from 'mappersmith'\n\nconfigs.gateway = FetchGateway\n\n// Extra configurations, if needed\nconfigs.gatewayConfigs.Fetch = {\n  credentials: 'same-origin'\n}\n```\n\nTake a look [here](https://github.com/tulios/mappersmith/blob/master/src/mappersmith.js) for more options.\n\n### \u003ca name=\"typescript\"\u003e\u003c/a\u003e TypeScript\n\n__Mappersmith__ also supports TypeScript (\u003e=3.5). In the following sections there are some common examples for using TypeScript with Mappersmith where it is not too obvious how typings are properly applied.\n\n#### Create a middleware with TypeScript\n\nTo create a middleware using TypeScript you just have to add the `Middleware` interface to your middleware object:\n\n```typescript\nimport type { Middleware } from 'mappersmith'\n\nconst MyMiddleware: Middleware = () =\u003e ({\n  prepareRequest(next) {\n    return next().then(request =\u003e request.enhance({\n      headers: { 'x-special-request': '-\u003e' }\n    }))\n  },\n\n  response(next) {\n    return next().then(response =\u003e response.enhance({\n      headers: { 'x-special-response': '\u003c-' }\n    }))\n  }\n})\n```\n\n#### Use `mockClient` with TypeScript\n\nTo use the `mockClient` with proper types you need to pass a typeof your client as generic to the `mockClient` function:\n\n```typescript\nimport { forge } from 'mappersmith'\nimport { mockClient } from 'mappersmith/test'\n\nconst github = forge({\n  clientId: 'github',\n  host: 'https://status.github.com',\n  resources: {\n    Status: {\n      current: { path: '/api/status.json' },\n      messages: { path: '/api/messages.json' },\n      lastMessage: { path: '/api/last-message.json' },\n    },\n  },\n})\n\nconst mock = mockClient\u003ctypeof github\u003e(github)\n  .resource('Status')\n  .method('current')\n  .with({ id: 'abc' })\n  .response({ allUsers: [] })\n  .assertObject()\n\nconsole.log(mock.mostRecentCall())\nconsole.log(mock.callsCount())\nconsole.log(mock.calls())\n```\n\n#### Use `mockRequest` with Typescript\n\n```typescript\nconst mock = mockRequest({\n  method: 'get',\n  url: 'https://status.github.com/api/status.json',\n  response: {\n    status: 503,\n    body: { error: true },\n  }\n})\n\nconsole.log(mock.mostRecentCall())\nconsole.log(mock.callsCount())\nconsole.log(mock.calls())\n```\n\n## \u003ca name=\"development\"\u003e\u003c/a\u003e Development\n\n### Node version\n\nThis project uses [ASDF](https://asdf-vm.com/) to manage the node version used via `.tool-versions`.\n\n### Running unit tests:\n\n```sh\nyarn test:browser\nyarn test:node\n```\n\n### Running integration tests:\n\n```sh\nyarn integration-server \u0026\nyarn test:browser:integration\nyarn test:node:integration\n```\n\n### Running all tests\n\n```sh\nyarn test\n```\n\n## Package and release\n\n### Package project only\n\nUseful for testing a branch against local projects. Run the build step, and yarn link to the `dist/` folder:\n\n```sh\nyarn publish:prepare\n```\n\nIn remote project:\n```sh\nyarn link ../mappersmith/dist\n```\n\n### Release\n\n1. Create a release branch, e.g. `git checkout -b release/2.43.0`\n2. Update package version and generate an updated `CHANGELOG.md`:\n\n```sh\nyarn changeset version\nyarn copy:version:src\n```\n\n3. Merge the PR.\n4. From `master`: pull the latest changes, and build the `dist/` folder which will be published to npm:\n\n```sh\nyarn publish:prepare\n```\n\n5. Verify the release works. If you are using `npm pack` to create a local tarball, delete this file after the verification has been done.\n6. Finally, publish the contents of `dist/` folder to npm:\n\n```sh\ncd dist/\nrm *.tgz # do not accidentally publish any tarball\nnpm publish\n```\n\n7. Tag the release and push the tags.\n\n```sh\ngit tag 2.43.0\ngit push --tags\n```\n\n## Linting\n\nThis project uses prettier and eslint, it is recommended to install extensions in your editor to format on save.\n\n## Contributors\n\nCheck it out!\n\nhttps://github.com/tulios/mappersmith/graphs/contributors\n\n## License\n\nSee [LICENSE](https://github.com/tulios/mappersmith/blob/master/LICENSE) for more details.\n","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftulios%2Fmappersmith","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftulios%2Fmappersmith","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftulios%2Fmappersmith/lists"}