{"_id":"thwack","_rev":"46-963e194fdd3f1fc0490d10ad0f2cacf1","name":"thwack","dist-tags":{"latest":"0.6.2","next":"0.7.0-beta.1"},"versions":{"0.0.0":{"name":"thwack","version":"0.0.0","description":"","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"keywords":[],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://donavon.com/"},"license":"MIT","_id":"thwack@0.0.0","_nodeVersion":"10.19.0","_npmVersion":"6.13.4","dist":{"integrity":"sha512-gZEZfoUnFvUITJxWRonx0H6p0fQ6TfBO3ncwWQAQbhEyRgMzkSNPuq2H7OfsNn4iBm4sng9ZSCQh13pxZFapBQ==","shasum":"1e1537f263e6783c24ae1d8ceba176a9d290e5f3","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.0.0.tgz","fileCount":1,"unpackedSize":276,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJej7j3CRA9TVsSAnZWagAAtwoP/2NiVZzzeXGDW9u2C52q\nkaKb3xtEcuCQ4+46XOORZ/fAmp6oNAXU/5Rk8n8rZWEXaMVx5KToVvuaK1/M\ng1HF9wK1edifVNXBfFs2tOv+Y6te3M/LVbQEjitrVLbDQd02N0+/DWCRZ7SR\ng3Oc0SPOb9XrM78HYjBWB0MtqEuwg6SEVwcn3wClZLN4z7YASQwyHanDDfaH\nDKb2WpovNlq+oRbsEF+pKJZRcnTrdyttoUceNR+ZApYeWHKnQRHgYl2v96sB\nAZ3cTbOlIhI1L9GYSmLUELA4K6ypxklCuHfOZW0VesIVusux4OwgaQTE0IE8\nSTVcnXFPqpswTdAhEGt2RmY+dFod1LSx5QyJyXo0ptKtCngRTWS+c07g11Oe\ni4K/2ASAqyaUk+VWmnWnMvFQbf9IqpQJwtFvlhp1D4SVrTcXonFGG+eR2qUl\nwShZN0ylfkESk3L89bK0qaSLE5cCu7qm2ubmuqf6uBR/gweQoZXt9AbB7G3q\nVwlWhYsnuAeh2Q7iYdKKuIQJNdm5hSHKXSIpcQ+yTGpgzl0vHGoDqlMZE3Q1\nqhWDdHz6OzeVJFLx0NjxhTgOfIj1guniUfjfIJmmgxj3Wpx0lsarsIfIv6km\nacmylKh9rdCkS2yv41aU7nfHSAJX+eze2F2t6ReaLe1IpOeSs+G/mrWPtcBB\nP0ua\r\n=9BQ0\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQD8BGRK2P99Tjn7VVnWx5EPU8zPxruKCbsmxpG8jRnZ7wIgMuBK47HHFcEqgF0kRlTp9pmHO/IfBfxOb3lve5/JM64="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.0.0_1586477303035_0.9961556664501794"},"_hasShrinkwrap":false},"0.1.0-alpha.0":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.1.0-alpha.0","description":"A tiny modern fetch replacement","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle --name thwack -o dist/thwack.js --sourcemap false --target web","dev":"microbundle watch -o dist/thwack.js --sourcemap false --compress false"},"keywords":["utils","lib","fetch","axios","xmlhttprequest"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"7b99d5fd96449be9aa753fc655b1e982d32a3c5e","readme":"# What the heck in Twack!? 💥\n\n## TL;DR:\n\n- It's a modern version of Axios (HTTP data fetching).\n- Small — less than 1k gzipped\n- Smarter\n- Familiar — Uses a familiar interface\n\nmore later...\n\n> It's in alpha, so read the source to get started or see the tests! You can also ask me a question [on Twitter](https://twitter.com/donavon).\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.1.0-alpha.0","_nodeVersion":"10.20.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-qD2aWj6OAVJTV9UDW98cOSfunYH73mm/qnm7e2nhLJ4t/90KIvvaodUydgFsLpnuqLb7IERK8WMNq0nFM+T/eQ==","shasum":"efd54f8b9aeaccd68cd9eb5373da5955fa21dce7","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.1.0-alpha.0.tgz","fileCount":7,"unpackedSize":12338,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJekLUyCRA9TVsSAnZWagAAHrEP/0Y4dQMPP65FpziLAitC\nS/YGF3Q7nofnPAV2/axLag03u+CzNFx5cG/nxUjzsT09kacukb3eXBBtXYXD\nS2WcmHMkOYneG/QrH5DIvXr12LVMisbm10UtKNO61lcih2IJuYpkke4nbTgL\nJfTEaZStkoE8aqNuRvTBekhJyy5IYTz8Z9sUJVUBHQHbU5XXuV2/0kvjIJdg\nIePdXED+n7aQqAPjggRA+5m+s4WPwggFBhyCbw/CkUwaWxZVoCOHrYgyQAoe\na4ey7eoEYWHtp26R7osrYrxsxnnZN8aas8rVFG0zj20KChYI0fFsn7C8AWea\nf6JP2mxUpl/+GYL3t6skWWYqIxEXML4UQc6BN2RNCHQP9uO1sFAbOpxTw8tO\n7gI7ofY9jQ/+RtZcv+gMev0Prswarh+3V90rCwP5M19vK2RryL/KjVuE+fNK\nH9inTrg8WnG7ji6KSsHR3Z3aWmqVzXgyzLeibTfKZydkzuorSuorQUlUK+FW\ntYpXeeNqBmhoFLAsvrRLQFg1lzUfwSg5/upa58MstZQDLnvpqvi6cIvyb8Ll\nkDB+gHmxgTzyQQnKaiy9Q7ggafSn2fhfBd8vSeNDk5q37xmjWB+FU+19DmkX\nLUyskW1l9AxWRgMwMne5obPWzue0CCF9LIdozpB4c3byrOQfC3yPEsc65VgF\nL/ys\r\n=w6sC\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCVenTwpi0q0gS+QqNOhXHzeICZo5l2S3xm5CSpYm9S0wIgb4n8gSgcL3/5UtJMZ6wNQmcuKQA08ZE7ICvcR6RbA0E="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.1.0-alpha.0_1586541873996_0.9970167034574113"},"_hasShrinkwrap":false},"0.1.0-alpha.1":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.1.0-alpha.1","description":"A tiny modern fetch replacement","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"7f5f278aa22be8fa74aa14538bf8654ee21412d2","readme":"# What the heck is Twack!? 💥\n\n## TL;DR:\n\n- It's a modern version of Axios (HTTP data fetching).\n- Small — less than 1k gzipped\n- Smarter\n- Familiar — Uses a familiar interface\n\nMore later...\n\n> It's in alpha, so read the source to get started or see the tests! You can also ask me a question [on Twitter](https://twitter.com/donavon).\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.1.0-alpha.1","_nodeVersion":"10.20.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-2wUYgxdoMkykp1XpwkJ4LOZ2rS7poafUeXZ+US8Z6PGO3vvSzGYcqdSEvSysdtscZNlOLN/pLH2/xuuCLoAFew==","shasum":"9903075ac834560acf19de06c2492d69498ee398","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.1.0-alpha.1.tgz","fileCount":10,"unpackedSize":34323,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJekM/LCRA9TVsSAnZWagAAxfkP/ihybujcVPEcM+Jvyu2E\nMXG3Be05KuyrRzXyLVQNYA/RLG7B83zFcToZTcG5iIg8BNnjptpouNN8pCky\nKWmF63KOJPGshnbl3vyxd6jS7SYxEyGw9oVTlYXVAK5Ytqw+0+IjxK87++Yt\nu702whU8TDE6RxMN+3BJZpgVRCEEIyVrTUVL2IpEbyyvjIIgrp83AMFIeCce\nQKxHfQzz/+B7enh5esdKlXRThd0swOtdwJ5AgaFeBw+OHLrbWoMT81Z1Zk0n\nOQ+SLdresrCX0p7Rbg1U+WEOK9cmc+Rp3Yf60IcRAMaDNhppqobaV4vbUBx0\n40oX+IODwpUD/LUjnVPwcKbPnUtygxUdJRlqjXG70vyJEHgP/4vhVN/9Oall\nePCUBssQlV724a0YsqqQcL/yAsHDea73BjrN5SSuQvXteyedjYMIdDfGWMtj\nGEEX+91gozsC8EKRW3eRL5mmesspYvY4YYYPaUl70WF69V+fUC6BBiiFaRq4\nHAa1lAJRnXW8laAvNc51/vJ3KTagXKJHSwy/AEyfa76VH+s5pHixmbPRu9pQ\nI1YbNltINIByEW9NVkfacqABJZQNDDUSZnu/AZzdxWECanlgzsKDvx/zliN5\ndvpFZ7KYvf9v9eyAzW+dQ2Fq8cOmiM5lB1JJ5oJHG0wk2FUdPsFqWBQIujNd\nd3Ot\r\n=j98J\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQC741/6buJ1BizU7y9CDvuW4Zg2Md6LL6ZoeON9+48g3wIhAK6hpYCoZClu+PPddxZq0UFBpcNwecN+OBb98f1B4xk2"}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.1.0-alpha.1_1586548682935_0.6449645653189873"},"_hasShrinkwrap":false},"0.1.0-alpha.2":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.1.0-alpha.2","description":"A tiny modern fetch replacement","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"bf2fc6f02c6fb9d5f13ecd9a520e1dddfe33925b","readme":"# What the heck is Twack!? 💥\n\n## TL;DR:\n\n- It's a modern version of Axios (HTTP data fetching).\n- Small — less than 1k gzipped\n- Smarter\n- Familiar — Uses a familiar interface\n\nMore later...\n\n> It's in alpha, so read the source to get started or see the tests! You can also ask me a question [on Twitter](https://twitter.com/donavon).\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.1.0-alpha.2","_nodeVersion":"10.20.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-izE2GNeieFqPAPNKv02g4LjUQrTaQmEUr9LWAjEmNKLUlue5VraithshdFY37omdDFY/DbGHFsyzrhIWOR5CiA==","shasum":"7e72aa12e2338185e28598d8e9c38a4f93b43ed1","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.1.0-alpha.2.tgz","fileCount":10,"unpackedSize":34608,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJekPnlCRA9TVsSAnZWagAAA/cP/3iUWDZq6Umea5L89CLz\ncss2mb6kCrA9iZWVaVijbnQsQpf74OOWXZs3EVXJ+cFFvyjgT6/OcPiS9IlQ\n8WV0/jBKsDmIjq4tnxoRWhXgyicsMRAKAJ/BOH6EpQr4Da8XFuxzu4xT5RXL\nx+EpDrerf3iCHhpkob4SsfoiCV0XPBJE4CKiLYrBR4ejw63Iw96R78IZNdtC\nTjWlUISLOYRjemuh96ZpYomMju6pNUeVtoGdEvzemslnO5RWwxRTi8l4xGXB\naEej7778kPOLCK7yvvev92ttEoLhqbW3dbcNb3v9R5UK2WfaiyRSrRuBvQKt\nQabGvVm0dBpGxS9ACnY59hEml0isYL1US9tyJYwLS1xjlnNHUpzAJRqfKNBL\nrDWjrFVso+aHgKcLQt9lshpuC2WL6RyR7dvrIU97OKLbvHQLeYfIL2ECZ2jM\njUEwxifrGq5AMsy2c03SVrggFWn6bcchlaCNkMqkj/Np9VHNnVuoXDP7xjeC\nH2OVQD/bE1/ozlNSYCbYNGivgwbTEkfEIB+sf6EWrxCu7CgAwot6hlgbxDcc\nAiqsRoXlM/F6662OaLgGjTIJtBf3w09HG8xn3pNz73iqSUaJh63KeBYTnss/\nnuqUjScFgyAH6w4k1W9ZX9lqdlV308vh4KH+TY/KBFoXj1CtuF2KJ4ccJ+gg\nXeLN\r\n=MTk5\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCID4UVLzxNC4jEfuP4Lqvc8Pq4N3l1ZSSRAeHJHDDCOCGAiB85R/Cs6NVQgHLkHQwPiHvQ7DYwQopo/dVjlO9ZD+/Pg=="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.1.0-alpha.2_1586559461241_0.19419912491451718"},"_hasShrinkwrap":false},"0.1.0-alpha.3":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.1.0-alpha.3","description":"A tiny modern fetch replacement","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"1d75d509f4fae7aea675c2ec1a386b3bd966ba48","readme":"# What the heck is Twack!? 💥\n\n## TL;DR:\n\n- It's a modern version of Axios (HTTP data fetching).\n- Small — less than 1k gzipped\n- Smarter\n- Familiar — Uses a familiar interface\n\nMore later...\n\n> It's in alpha, so read the source to get started or see the tests! You can also ask me a question [on Twitter](https://twitter.com/donavon).\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.1.0-alpha.3","_nodeVersion":"10.20.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-47vt8UFOUTNG7DhxNXY58JqTmYXIGNrxttftvKWY0yqVEF4Kalo7hAJNSuoUcg4LEfvCdusLBT7O3iu+j2tJPQ==","shasum":"8babefce5189b78cb677b0268b1fa07017002352","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.1.0-alpha.3.tgz","fileCount":10,"unpackedSize":34597,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJekPr1CRA9TVsSAnZWagAAeLIP/0bmi+AH3K4tzXY08YZq\n7u63k4MS4abxy0ABF31kjpxNECqnDPU/b7U/Zbxo3ZEbD8P+vCa1lKI0QvWm\nRpo7dIgGTcn9aHucnFSP8zHVYpzeAYJAmz3N7+khUHR8MzdAzuOufPhoCrM7\naN3/qYR5+TzDBnFPlDeN3RxCJ80E+IvFvJ+2z8LhGhGsNbl7xK9oFT39az0r\nnObSnWcd9wnTrSH+bX6Mp7OeNNt24GtruFVOzRENOonqYsvzYVB7Ok1yZTiN\nm+hVbXtf+/YvHNLN6ezRkwfx7R4aJheCop78wH5qFwVOlA+/Um8r5eIeen+D\n3HYh7I8iVR7iPkIXdvzpjUShU85n34kvf3MJ8eQwbWUjXBrBzu9UP2NZ1wCK\nMBCLe4I5QQAa/nmt8LY6wEAVKqVAHFNlwxscloH8mLIkpjQioH+fUuWhSDmn\n0yA94dyaIwuVUuT0cLXIFuczc27XPXvNvoDLIygKQHGX84X+ZE5C68eNWFGU\nJegLZPquPayvOI0Y9gfP4ktDZedKJGfEEha80jx4DcikjxKf2VChwn38zZWx\n2jAMt6Sj495Bb84ukgZ+iI10jO79YDmF8KfPDe28+7WO0CtlryUXgwkfG/IG\nONDp099Tvp9nV7vt1QBxe6iiwEpZxVLvuIOKX+HGcuwdV9dFheywXr+l6CX8\nC2ft\r\n=Olza\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIEpEu2tdOxnXTQR7r2tloNHEwfKpV1f+5FCB7DT/YxbsAiEA4zdOgzQ56igUMuMLfQC4ezhFWNsHhzyCsxJU9N8Vgk0="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.1.0-alpha.3_1586559732850_0.8510703903469672"},"_hasShrinkwrap":false},"0.1.0-alpha.4":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.1.0-alpha.4","description":"A tiny modern fetch replacement","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"readme":"# What the heck is Twack!? 💥\n\n## TL;DR:\n\n- It's a modern version of Axios (HTTP data fetching).\n- Small — less than 1k gzipped\n- Smarter\n- Familiar — Uses a familiar interface\n\nMore later...\n\n> It's in alpha, so read the source to get started or see the tests! You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n- `thwack(url: string [,config: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.request(config: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,config: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.delete(url: string [,config: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.head(url: string [,config: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.post(url: string, data:any [,config: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.put(url: string, data:any [,config: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.patch(url: string, data:any [,config: ThwackOptions]): Promise<ThwackResponse>`\n","readmeFilename":"README.md","gitHead":"30c892b5a3e58d0c4671b3844f67c62b47a58aa6","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.1.0-alpha.4","_nodeVersion":"10.19.0","_npmVersion":"6.13.4","dist":{"integrity":"sha512-HxlhSrqxPt1+MJGmo1ZekmKQ+ftAYxLuf0Q6Ej8D0pQurJ+m6wBgClrMlpkQpSapk1zpab7BiXuaRLMWTZqFzg==","shasum":"82fb3b31d0692ef5b63af9c28777bd91cc3ebfb6","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.1.0-alpha.4.tgz","fileCount":10,"unpackedSize":35818,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJekc5NCRA9TVsSAnZWagAAxu4P+QHkJo3IUul97JODycD2\nY7x87Y5HPER2rYMKin49Lr7RsiVKJ+JxMUqtj+F8+rEUBMowRwgct9vYgOvo\nJ7bwRvCieqipKbcUIhw4CaIYQw6bNkJNP7ty8DXKG9/L0cgNr7sfFy0rPCGa\nb/92jYxOJ94tCmLC3e7+p/FCL5BFUz9SVOdjeRHeUUxA0rW2CSI9mFFKu673\nwQIS2xlRpGM5hZzqKNRDYs1DJL3sY4XJ50pLKKQx3e7BTDiEM4pHap5stjOw\nxLjAeTK7MqOGlYEELbQBemdXvuCAgCosC6KMCCMJ7MnDsjyN6bRCDUww0RHw\nKGvOmcfR+TFdORaA9U2EYEHab42Ia9rXMhCQTptF/kMU9FFkoJElML1jvIyH\n7N7W7zebvCbILe47rlzZ3eCuIrN8UdhGhn7rYVHLoItPCiPSoDgoRNSkNahp\nhuOC8BPDaYtfP7qGTnVLYlpU99DokDEIWRkzGI7hFLda+jXnoOFpxI3h8y58\nWx2IvNFKnTzeSa0ailV6TXngft+Qes8mxyqffWac5zxe7Rc+QFDXeX2C8phr\nL5cCg0UEU+kJoAgliB34QH6sY/HqP3gJ57u9MllSDIJMU9RMpJAfKiozUcxx\nH0VAOwel8fskhpmnNSZmVeivXf7p4u/2Swex58q7DaYNwKl1NBnZJtRd39Wk\ncOPP\r\n=dgvm\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIAF1u25mz3kfgzScu5USj6osMxYZfOo8BYo0yC+vZQ3lAiBxjhjupsYFrm8ccAJTZ28S/24h5esu7WTCyExQLA1SLA=="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.1.0-alpha.4_1586613837275_0.26609930515763214"},"_hasShrinkwrap":false},"0.1.0-alpha.5":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.1.0-alpha.5","description":"A tiny fetch replacement for modern web applications.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"ed859c246817dff14d99dd1e4167259290c1f4ef","readme":"# What the heck is Twack!? 💥\n\n## TL;DR:\n\n- It's a modern version of Axios (HTTP data fetching).\n- Small — less than 1k gzipped\n- Smarter\n- Familiar — Uses a familiar interface\n\nMore later...\n\n> It's in alpha, so read the source to get started or see the tests! You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n- `thwack(url: string [,config: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.request(config: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,config: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.delete(url: string [,config: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.head(url: string [,config: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.post(url: string, data:any [,config: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.put(url: string, data:any [,config: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.patch(url: string, data:any [,config: ThwackOptions]): Promise<ThwackResponse>`\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.1.0-alpha.5","_nodeVersion":"10.20.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-I4tBB6o24lb7ivNOHquW2/xG15ietAPynJAsc/GaKQjQp9xlvUWJeX7g7yxHgUBwNQhIxLn4V5dvIK5YOPNIIA==","shasum":"08b9848aec4726a8fd22e2df4cda1640e4a90927","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.1.0-alpha.5.tgz","fileCount":10,"unpackedSize":41022,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJekgxNCRA9TVsSAnZWagAAbCYP/jLb+3LwO8bvVP/h/S7W\n8hAtRjTILn0I1j7oStLLrTrqrX15qqkvyMVg0XhnP2aRfkrDJTqQyWEV5LjS\nodLtmkElHLb38MYHPx++dx+6Muwg+QKqAGFwrwAXr6bAwdiylBnEaGTr4YDi\nF2ffNvfgWLOcCTzZ2Ihyx6zdhaUuZhXt9odUP+OvOW4KOlSgjkpSRjX5lNjR\n72Io5Hn/pMQf8c9/kthDb94xaGCnAb2DcylZt0QjMHnYcQYZiE4mc4yjIN/A\n02F60Tp3Fb/90Nygi/KPLUEx7gFM6SvfQOTJWw8NYOjR94bLXOGGDsXntlDG\nUFGnVr4Uc2REZjyT7icY5q4CQ1uA4IyjZtFE/J5Ar56HEzz8knh+BMacD41h\nR6v5LY8RwBs4SsmSRkJ83xitAaPtpMbSws6BKhY03R/5BeEIuuX5AYiynaCC\nNh8zGBZTpSfylr2FDbyaBs7kQFz1kHwxxj/1MpUJCHtZSN+c3pcO8jPQjdK5\nK+te3tMts4RQNVG13ae5AIES6/l/yWUBdq3SQ0nx8skShO0GF0OvBSRr2ItE\n9npFM2Ph/v7wVD0yhgSJjffScSby/bUKIgoiuO6ZHZfpiuyE06Y2zZ6B7UQU\nhUU0491drN0nvLKJcPs+9l6rKNlA9RURMwR2rnVcamszZf5Trta1NxVqGiZS\n+wYZ\r\n=OCBv\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIFn8z/W8uzYCF5DS3nWYbmLVxKhMs6BXrNfMw1JdVPxGAiEAwa8z8ie5Qbvq/g2j2eNBcyLU7tV5YX+78/1+kQweP1A="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.1.0-alpha.5_1586629708791_0.2505611843615445"},"_hasShrinkwrap":false},"0.1.0-alpha.6":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.1.0-alpha.6","description":"A tiny fetch replacement for modern web applications.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"e8bb5e5ecaedd76d4191f69e446be05a827885d7","readme":"# 💥 What the heck is Twack!? 💥\n\n## TL;DR:\n\n- It's a modern HTTP data fetching\n- Small — ~1k gzipped\n- Smarter — (say something here)\n- Familiar — Uses an Axios-like interface\n\nThis README is a work in progress. More later...\n\n> It's in alpha, so read the source to get started or see the tests! You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n## Methods\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n## Options\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseUrl`\n\nDefines a base URL that will be used to build a fully qualified url from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseUrl: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you spwecify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await thwack.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseUrl` or the `url`, they will be replaced with the value of the maything key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseUrl: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parmameters (i.e. `?key=value`).\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack with automatically determine the fetch method based on `content-type` and the `responseParserMap` table (se below).\n\n|     Content-Type      | `responseType` |                 `fetch` method                 |\n| :-------------------: | :------------: | :--------------------------------------------: |\n|  `application/json`   |     `json`     |               `response.json()`                |\n| `multipart/form-data` |   `formdata`   |             `response.formData()`              |\n|                       |    `stream`    | passes back `response.body` without processing |\n|                       |     `blob`     |               `response.blob()`                |\n|                       | `arraybuffer`  |            `response.arrayBuffer()`            |\n|         `*.*`         |     `text`     |               `response.text()`                |\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to setup a mapping between content types and parser types.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `.default` key.\n\n```json\n{\n  \"application/json\": \"json\",         // ex: { \"foo\": 123, \"bar\": 456 }\n  \"multipart/form-data\": \"formdata\",  // ex: foo=123&bar=456\n  \".default\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can ovwerride the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could create an instance of Thwack (using `thwack.create()`) and share it throughout your entire application. Here we set the `baseUrl` to our API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\n// api.js\nimport thwack from 'thwack';\n\nexport default thwack.create({\n  responseParserMap: { 'image/*': 'blob' },\n});\n```\n\nThen `import` the `api.js` file to use these options in other parts of your application. Any URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nimport api from './api';\n\nconst getBlobUrl = async (url) => {\n  const blob = (await api.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.1.0-alpha.6","_nodeVersion":"10.20.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-cEGGh4z00xjas1ZXMwhf2p5knhLusrCFFRKgHS4PY1KMFAE+gI7OUuEnsdKAuvRyQ2ebvcQ5Y43YH/P+gNKqYA==","shasum":"c3dcb842f36a111ab9978d56051cb9d14c2520a2","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.1.0-alpha.6.tgz","fileCount":10,"unpackedSize":53693,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJekslyCRA9TVsSAnZWagAAPVAP/j+MoRSbBo+ak3PVd+tc\njzITGV7Lja/rMhK+U2aT8a4YrpQdrn8KLVBM9YH8mGmY/jXXBA0yldq/liKy\nqHLmBy8Z2JpWkL9u/biFIOWGC94lT52oBjjVCrko+sXkxejDu6zk+ZcpTzGz\nEXPJz4em/1EjURljFZVyjPWYlsiiu99Hdj+i7/Vzib1OCwhr3kDTaS1wltKI\npxmtGJyz9ecfJZhhf/7n7Hq63+EvPUuC3BI8+WW0mjEsmMVpms/H3NY9hL5g\nwbQwR7CLyoyp7SgWuFceWWJkdNBu86/fhuUVdKCm5ntN/SYBVJ/InlsQEAnF\nInHqosl/s+syNevE2bwoO5VjKSCKaeFcfUjEgulUR7m9zBdQu0j5HM6LlobZ\n5nxuRO36neVux+jEDkdOmRAdtt03KeZ4AaroASKLQjjZ+MDph+TZ4F1O2e1S\n/xFTLhTnJx1kRT14U5Qlk4rlzlIY/8GQDiNRvM9C5eWtVJyZPUPISkb0ob6A\nok3JS9O+cUp9Sfpnac6YVo/UN6DLuPkZl0+xe+qCIm2oDdmPw1HuhQzDM4Dk\napgj4FsWT687KAxDun+rVK5OM1tdOYg0Z02KZD3H401UXIvH2CR7BbvgzY/s\nwg9u3r704Wzbiho7iv9YqR6/iDtqd69eUltZdCMj5i6BMBA/TGGZsajBxp74\nzsZl\r\n=RDgs\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDeEGRXnNKyHon5puelOibc+WQSUr6/vGXzSTSmJQ+jSAIgfaOEVGe1YW25hJPasf7Smkb6bIs7ZdO7JUVPNdBZSTQ="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.1.0-alpha.6_1586678130431_0.14843944097767747"},"_hasShrinkwrap":false},"0.1.0-alpha.7":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.1.0-alpha.7","description":"A tiny fetch replacement for modern web applications.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"a3629dd4c4bf7d6d5c797b6ad6364f8264231c55","readme":"# 💥 What the heck is Twack!? 💥\n\n## TL;DR:\n\n- It's a modern HTTP data fetching\n- Small — ~1k gzipped\n- Smarter — (say something here)\n- Familiar — Uses an Axios-like interface\n\nThis README is a work in progress. More later...\n\n> It's in alpha, so read the source to get started or see the tests! You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n## Methods\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>`\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n## Options\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseUrl`\n\nDefines a base URL that will be used to build a fully qualified url from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseUrl: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you spwecify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await thwack.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseUrl` or the `url`, they will be replaced with the value of the maything key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseUrl: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parmameters (i.e. `?key=value`).\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack with automatically determine the fetch method based on `content-type` and the `responseParserMap` table (se below).\n\n|     Content-Type      | `responseType` |                 `fetch` method                 |\n| :-------------------: | :------------: | :--------------------------------------------: |\n|  `application/json`   |     `json`     |               `response.json()`                |\n| `multipart/form-data` |   `formdata`   |             `response.formData()`              |\n|                       |    `stream`    | passes back `response.body` without processing |\n|                       |     `blob`     |               `response.blob()`                |\n|                       | `arraybuffer`  |            `response.arrayBuffer()`            |\n|         `*.*`         |     `text`     |               `response.text()`                |\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to setup a mapping between content types and parser types.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `.default` key.\n\n```json\n{\n  \"application/json\": \"json\",         // ex: { \"foo\": 123, \"bar\": 456 }\n  \"multipart/form-data\": \"formdata\",  // ex: foo=123&bar=456\n  \".default\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can ovwerride the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could create an instance of Thwack (using `thwack.create()`) and share it throughout your entire application. Here we set the `baseUrl` to our API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\n// api.js\nimport thwack from 'thwack';\n\nexport default thwack.create({\n  responseParserMap: { 'image/*': 'blob' },\n});\n```\n\nThen `import` the `api.js` file to use these options in other parts of your application. Any URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nimport api from './api';\n\nconst getBlobUrl = async (url) => {\n  const blob = (await api.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n## How to\n\n### How to log every API call\n\nMake a file module (here called `api.js`). IN the file create a Thwack instance, setting any `options` that you need throughout your app.\n\nThen, add an `addEventListener('request', callback)` and export the instance.\n\n```js\nimport thwack from 'thwack';\n\nconst api = thwack.create({\n  baseURL: 'https://example.com/api/',\n});\n\napi.addEventListener('request', (options) => {\n  console.log('hitting URL', api.getUri(options));\n});\n\nexport default api;\n```\n\nThen, whenever you want to fetch in your app, import the Thwack instance from `api.js`.\n\n```js\n```\n\nBecause you setup an `eventListener` in `api.js`, your `callback` function every time that any place in the app\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.1.0-alpha.7","_nodeVersion":"10.20.1","_npmVersion":"6.14.4","dist":{"integrity":"sha512-DWpdYF1wZ+uBsH/KTxMRs+7ml7o+32L3z3de0QSQb2e3euT72CyqYaiMHtdKT18kcQl3A8mW6iw1FahUBJDtCA==","shasum":"21a1727ce370ec566841494caadc0c1d43db1acb","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.1.0-alpha.7.tgz","fileCount":10,"unpackedSize":65257,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJelQ3tCRA9TVsSAnZWagAAd/wP/0KJJKLd010yv5FEX9mw\nWMimJwvISrxiV2CZNd0MxY7qf+GaI9+AAL67GElGgw6eHiLIXcYXfMhLREiJ\nJQPnOA3oUtmo8YEsQCrl0pNH8ir81Yo32lBmxeqEPjrNZsRbVAb/+pEk3Dlh\nNreFtL3+g/ELK1h1tVMUVqQBZZUrWeoZzgcFr1+4D8eQt3v9DE3SuusinBaP\nF0EwNj7ChnF8gp1DGtAXNbPn4uSXDC5hQxY18ROqj4soa1NDi8xxjm+2ZR6j\n29nG/F8odeqgoUJFwBcDf7zB8dqJe22aVIJXon74dpGSV50tIxNMJ9umEepC\nN8+K1GjWYcYtgKaF67aTvWfP+k7Bz8nHzSXg/c/wWT4qu2FNzf7O1VwmEtq5\nltLnse5YlNWxHBKHHbnb7UZBlximqfQk3JmydaWJoEhXLc80+trLa+b4E9cr\nPwa9S3dv5cLX/fdVWBIrxvdtwe8NumBHGnzdkjk2pBBW/OC2Pwk+/3oikmIK\npzZ1iauzFjOVpvfw5XfXJd7bErVkgVEILqmt3WKpIoszwNBOOgjgX/9eH292\n94giW+ct6s5Bnz+PdY6gghl3csvje3/gnDd85t/U9sHQFIJPl+rSxtD8RMK8\n23N23TQ2SkTur3pHmRxl9DbS5equjMwWxeYLTsr4MqvFwdX8T8od3FpbHAbg\nRWNJ\r\n=S1iU\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDhqP5PotNbDJHU3YBajY9ihxjDCxkNT/DCoRcRcUWwLQIhAJXz/ZAnkfh7VbAViW5xgxkgtw+IGXoGdl9Ode/3n8vD"}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.1.0-alpha.7_1586826733330_0.49494775604340724"},"_hasShrinkwrap":false},"0.1.0-alpha.8":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.1.0-alpha.8","description":"A tiny, modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"e5ee6ccd9e16e7decedde1c12ea8f9b49c5b6d24","readme":"<p align=\"center\">\n    <img alt=\"Thwack logo\" src=\"https://repository-images.githubusercontent.com/254701245/b63c3200-7e16-11ea-872e-0f25e8045568\" width=\"600\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny, modern data fetching solution\n</h1>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/thwack\"><img src=\"https://img.shields.io/npm/v/thwack.svg?style=flat-square\"></a>\n  <a href=\"https://travis-ci.com/donavon/thwack\"><img src=\"https://img.shields.io/travis/donavon/thwack/master.svg?style=flat-square\"></a>\n</p>\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nWhat the heck is Twack!? TL;DR:\n</h2>\n\n- 💻 Modern - Thwack is an HTTP data fetching solution build for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n\n> This README is a work in progress. Thwack is in alpha, so read the source to get started or see the tests! You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was along time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differenced — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nMethods\n</h2>\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance`\n  Creates a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string`\n\n### Event listeners\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: Function<ThwackEvent>): void`\n\n- `thwack.removeEventListener(type: string, callback: Function<ThwackEvent>): void`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await thwack.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack with automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*.*`         |     `text`     |                    `response.text()`                     |\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to setup a mapping between content types and parser types.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `.default` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \".default\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can ovwerride the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could create an instance of Thwack (using `thwack.create()`) and share it throughout your entire application. Here we set the `baseURL` to our API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\n// api.js\nimport thwack from 'thwack';\n\nexport default thwack.create({\n  responseParserMap: { 'image/*': 'blob' },\n});\n```\n\nThen `import` the `api.js` file to use these options in other parts of your application. Any URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nimport api from './api';\n\nconst getBlobUrl = async (url) => {\n  const blob = (await api.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promist resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range hwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA `key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch`.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwach.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n## message\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parents's default options pass down through the instances. This can come in handy for setting up options in the parent that can effect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nHow to\n</h2>\n\n### Log every request\n\nMake a file module (here called `api.js`). IN the file create a Thwack instance, setting any `options` that you need throughout your app.\n\nThen, add an `addEventListener('request', callback)` and export the instance.\n\n```js\nimport thwack from 'thwack';\n\nconst api = thwack.create({\n  baseURL: 'https://example.com/api/',\n});\n\napi.addEventListener('request', (event) => {\n  console.log('hitting URL', api.getUri(event.options));\n});\n\nexport default api;\n```\n\nThen, whenever you want to fetch data in your app, import the Thwack instance from `api.js` instead of directly from Thwack.\n\nBecause you setup an event listener in `api.js`, your `callback` function executes every time that your app makes a request.\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\nWe will, again, use the approach where we create an `api.js` module. Within this module we will add the following event listener.\n\n```js\napi.addEventListener('request', (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n\n    // the caller's request will be resolved to this `ThwackResponse`\n    event.promise = Promise.resolve({\n      status: 200,\n      ok: true\n      headers: {\n        'content-type': 'application/json',\n      },\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      }\n    });\n\n    // tells Thwack to return `event.promise` instead of handling the event itself\n    event.preventDefault();\n    // stop other listeners (if any) from processing\n    event.stopPropagation();\n  }\n});\n```\n\n### Load an Image as a Blog\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRght now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint o a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\napi.addEventListener('request', (event) => {\n\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = api.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  consy newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nLicense\n</h2>\n\n[MIT](LICENSE)\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.1.0-alpha.8","_nodeVersion":"10.20.1","_npmVersion":"6.14.4","dist":{"integrity":"sha512-Dru/M0Ug12YfYM21Z7+HJFbqBxKvk0NTbD9m5a3b5bbMTbg6BGA8PNx4jlPhGmCVaSPIJ8AvvovY0ZWuIVlw6A==","shasum":"df19a4c0958e2799e6b37c04a5d08eeff72f9e81","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.1.0-alpha.8.tgz","fileCount":10,"unpackedSize":84024,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJelfBeCRA9TVsSAnZWagAAN4UP/iOtADhXapCrqpcmTuZc\nCVOzNRv6BKgD+9B4XQEgI7SerIEN9jmI2JTkUkrng7u2/7iIgzR4l84X5+hc\nt5e3IVxWzU9xQwvfrFcQ/lOORAmm+4mJctfbUVekmKYoXnbg4qUp2SVPnspa\nnVpdvMcpEVgMP7BSYh6xwa/fWyg/YQjro90l6wY5FYmW87nWQQJuRCFRxAzh\na/WC0TpzXoHlZ4qjo5Kc4bMuuLPEgoullfCm1ErDfZIyY2MkqQqineah2Xt9\niMnysc/xbkNxN2zJG/ApUOxxygEfUd0PQak+ebwN3LUH8qXwOE7NVB9y3z5t\nmxymnC/vZWxY9qv78XCjocMwWzYdAt8TWApyXlr7QpnoaiyBhjY76lNL9mww\nVyaifYNW5jBe94ZTueIkyjVtVLjnR0haV91zOmfEk3X00N1Hn6AGZfjcz8fL\nmDi8UvlNRfc2o7ADa2hGZ4XRTbTWeVRSIJCcUCZG4nwtFbE4830S8mIyiSkl\n4MpEO7/4x+XA1mn2qfFvK0U3ec1EVcBQwCEfr2mAGLXZxe8a70ApcrnNP14X\nZCgroHRN8S4b0Kins7sDPXGlFGDlSbAlfrKvSyC97IFnGrjdUYWZeJ7URTtf\n1j6EbOGNIHjXvNx3lrsYYdDdye61uubVV+hzEJPQMINyuiAgpQ2P3eg1MWUq\nMSdL\r\n=f09G\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIE0tUhsba0Lcnto/Lqu/7IKuA0vj87A2v2E3oEqgz6daAiEA1oV/OdNNNNj3NUzViZlUprSDewTU+BJ6u9D7Gku0ZXE="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.1.0-alpha.8_1586884702198_0.09136002270283772"},"_hasShrinkwrap":false},"0.1.0-alpha.9":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.1.0-alpha.9","description":"A tiny, modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"7048f32b8684a5d9376ceeb4c0664b9d5e138c69","readme":"<p align=\"center\">\n    <img alt=\"Thwack logo\" src=\"https://repository-images.githubusercontent.com/254701245/b63c3200-7e16-11ea-872e-0f25e8045568\" width=\"600\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny, modern data fetching solution\n</h1>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/thwack\"><img src=\"https://img.shields.io/npm/v/thwack.svg?style=flat-square\"></a>\n  <a href=\"https://travis-ci.com/donavon/thwack\"><img src=\"https://img.shields.io/travis/donavon/thwack/master.svg?style=flat-square\"></a>\n</p>\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nWhat the heck is Twack!? TL;DR:\n</h2>\n\n- 💻 Modern - Thwack is an HTTP data fetching solution build for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n\n> This README is a work in progress. Thwack is in alpha, so read the source to get started or see the tests! You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was along time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differenced — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nMethods\n</h2>\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance`\n  Creates a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string`\n\n### Event listeners\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: Function<ThwackEvent>): void`\n\n- `thwack.removeEventListener(type: string, callback: Function<ThwackEvent>): void`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await thwack.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack with automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*.*`         |     `text`     |                    `response.text()`                     |\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to setup a mapping between content types and parser types.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `.default` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \".default\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can ovwerride the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could create an instance of Thwack (using `thwack.create()`) and share it throughout your entire application. Here we set the `baseURL` to our API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\n// api.js\nimport thwack from 'thwack';\n\nexport default thwack.create({\n  responseParserMap: { 'image/*': 'blob' },\n});\n```\n\nThen `import` the `api.js` file to use these options in other parts of your application. Any URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nimport api from './api';\n\nconst getBlobUrl = async (url) => {\n  const blob = (await api.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promist resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range hwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA `key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch`.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parents's default options pass down through the instances. This can come in handy for setting up options in the parent that can effect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n### The `response` event\n\n> [not implimented yet]\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nHow to\n</h2>\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\n```js\nthwack.addEventListener('request', (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n\n    // the caller's request will be resolved to this `ThwackResponse`\n    event.promise = Promise.resolve({\n      status: 200,\n      ok: true\n      headers: {\n        'content-type': 'application/json',\n      },\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      }\n    });\n\n    // tells Thwack to return `event.promise` instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n  }\n});\n```\n\n### Load an Image as a Blog\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRght now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint o a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  consy newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nLicense\n</h2>\n\n[MIT](LICENSE)\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.1.0-alpha.9","_nodeVersion":"10.20.1","_npmVersion":"6.14.4","dist":{"integrity":"sha512-NsUNWg6ilk9QPj7w18PFupANOybUnfpyi3Vp6f3nOFaW7GIRqk25zgWzMwx6lTm7f5Pd30Zq3UiHYtBcqDOaOg==","shasum":"7649cdc9129804d44b7458f2de52b4d2da28df40","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.1.0-alpha.9.tgz","fileCount":10,"unpackedSize":84160,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeljf6CRA9TVsSAnZWagAAxucP/jmvpCficvyBN9FRX+9b\nj8rWznuvCiBVB7chK/KsfsIrwP3Ncc0FwQY5PJjjqklSLbBa+aGAL/MNivvw\ndKE2DR7qKPKytVT2pncEBgmHggSBpbU37VekclYNiRgVXl585UT5cT8Koio+\nQxgKGazDTtV2/iwB25FY4WI57z78+7vXhmOU8qZkX+eSURV94uW/kyoL+Ye/\nOCjlqGdqc4Xit8vdiURebdLy0Z1lHYN4nBrNvSZ877C0vU+0WQ3PfeJuzmuP\n5xEQaLH4d2L59RTuI1TzGhoO+fbjLmj6DgD+6Hqq7cvUFCMXLrkV9ejfYUY2\ni9kaqErJIhBUaQDHOLWDbx431VOFlcm8PC5LLFVC9f6Xww2c+5qQH3Uq4OBy\nvb2Azk57YhJO4vr7EAh929mWxo7M5tLLWLvs01sElGIpBNrfp8AtjASokOe3\njK1YeO42GMTwlyZa3WGpRPqyqVeMO60aEnGDMxdPPLH0nEQ6XXqNrd5XUUH5\n66bVq3yKy/pGqIhZXIGsW/JVAV5R5GhZu9DX55r8uLlg8qug9wdfTrmxPVJH\nmEKYqU7Ghea64DKlxP0OxEp2PeO8Y730fJw6nBRrPreR+PWFKuJxJSFubU4x\n/dmzl9aGMJg+5s81fSsN7u7URX/SqrVec4PA4di2MIEUorJ3wtJi1Q4O1j+5\n9gNX\r\n=TEvl\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDZgQgI6rvTK/ReYJYzmdFrb/YWC4Y/u7321EK3ClGx9gIgOK3yJ+Ow0oI/roSHKj7JHPPAruE1BVJIwe1Y+ll67rU="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.1.0-alpha.9_1586903033762_0.6917558278975462"},"_hasShrinkwrap":false},"0.1.0-alpha.10":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.1.0-alpha.10","description":"A tiny, modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"4fbaf8d8433fdb6b35a278a989adfecffeccb23b","readme":"<p align=\"center\">\n    <img alt=\"Thwack logo\" src=\"https://repository-images.githubusercontent.com/254701245/b63c3200-7e16-11ea-872e-0f25e8045568\" width=\"600\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny, modern data fetching solution\n</h1>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/thwack\"><img src=\"https://img.shields.io/npm/v/thwack.svg?style=flat-square\"></a>\n  <a href=\"https://travis-ci.com/donavon/thwack\"><img src=\"https://img.shields.io/travis/donavon/thwack/master.svg?style=flat-square\"></a>\n</p>\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nWhat the heck is Twack!? TL;DR:\n</h2>\n\n- 💻 Modern - Thwack is an HTTP data fetching solution build for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n\n> This README is a work in progress. Thwack is in alpha, so read the source to get started or see the tests! You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was along time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differenced — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nMethods\n</h2>\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance`\n  Creates a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string`\n\n### Event listeners\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: Function<ThwackEvent>): void`\n\n- `thwack.removeEventListener(type: string, callback: Function<ThwackEvent>): void`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await thwack.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack with automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*.*`         |     `text`     |                    `response.text()`                     |\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to setup a mapping between content types and parser types.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `.default` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \".default\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can ovwerride the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could create an instance of Thwack (using `thwack.create()`) and share it throughout your entire application. Here we set the `baseURL` to our API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\n// api.js\nimport thwack from 'thwack';\n\nexport default thwack.create({\n  responseParserMap: { 'image/*': 'blob' },\n});\n```\n\nThen `import` the `api.js` file to use these options in other parts of your application. Any URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nimport api from './api';\n\nconst getBlobUrl = async (url) => {\n  const blob = (await api.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promist resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range hwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA `key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch`.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parents's default options pass down through the instances. This can come in handy for setting up options in the parent that can effect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n### The `response` event\n\n> [not implimented yet]\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nHow to\n</h2>\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\n```js\nthwack.addEventListener('request', (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n\n    // the caller's request will be resolved to this `ThwackResponse`\n    event.promise = Promise.resolve({\n      status: 200,\n      ok: true\n      headers: {\n        'content-type': 'application/json',\n      },\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      }\n    });\n\n    // tells Thwack to return `event.promise` instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n  }\n});\n```\n\n### Load an Image as a Blob\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRght now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint o a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  consy newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nLicense\n</h2>\n\n[MIT](LICENSE)\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.1.0-alpha.10","_nodeVersion":"10.20.1","_npmVersion":"6.14.4","dist":{"integrity":"sha512-geF5v9C4H1xPyi/31Ay/VNqP1X+W45WTwOUz42moA+TvvZTUlWIAH4xutoVNvSi23s/AWH5bl2znlovuSXDiow==","shasum":"9820476a0dd65d44efb219d6711c17ac1f89dceb","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.1.0-alpha.10.tgz","fileCount":11,"unpackedSize":89186,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJelwjCCRA9TVsSAnZWagAAF3sQAI8IQtvppLm8XRbQS0Ar\nSl46QT8L3kVawphe94KU9AYDRBAKCAhIwy/uS3YRHr58Z66IW2dznu0cjIhc\nuj6pL+CWyUfPQH9SvG14PlCjVAlA9VzPjGnw+KbRTHNgy3mVyr+G2jRa8gSf\nHjIU/Ah8Z/3LSTtJQMaHUQkOBxh61SeYcwL8k+nJ7BKkmzhegiJY3PS32MTv\nJ0YLGu4oXPzNVnTmylU0FSN470kzfuZh/XpQmMXOP8/D0D8Htp+tGIkBU8fn\nTrJHxMTb0JbnS9HP7W68pBGt+RKjwJZgTvM9C0G/6PDdVTJvMds/qF9tUxLa\nenhizt6o7lKLupIykTyU/oFanl9yVc32pIrBjSeRriw/No6ObAb70FyPV4Sp\nLtp8ikTGM2kZi+ZdNf4mstMAhs+P+LEJtki9DXDyDvJkUQ+8sy5WCbN3NRP/\nlLNZQmNrSh9venP3sWQTnd6VMGztkc9Z7gHNK27fOz8C4OQbGB4eQl5COLaW\njbJ1QtIKcUyfmTa0emNa2KDCRdjjeTVnD+1FwMwMkHogj0EIA4GilWiTv3jh\nVVkgcNrkiLRGxXllkKupgIxqquYYj8T+NtpPXa3kxaw0eOezHdPBxOs4I1Km\nQNbBW1g8NapUBOB6AJriv9S3TcrWM9jfWiYTGAk86J2SfZhJJ8OLo9/oQjSD\nAkvX\r\n=ftTh\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIBYCC566mvoBiEfPru0CcXQT9IEQokRFhOO23aHuVdv5AiEAi/zC3uHpUu+TuJN+CBfZmHXwkC+2FyuFKQmnlKwFH6M="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.1.0-alpha.10_1586956481737_0.07540610072873477"},"_hasShrinkwrap":false},"0.1.0-beta.1":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.1.0-beta.1","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"aa8b05f0563d2e5dcce3cd4125292d7bc1881b49","readme":"<p align=\"center\">\n    <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/thwack\"><img src=\"https://img.shields.io/npm/v/thwack.svg?style=flat-square\"></a>\n  <a href=\"https://travis-ci.com/donavon/thwack\"><img src=\"https://img.shields.io/travis/donavon/thwack/master.svg?style=flat-square\"></a>\n</p>\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nTL;DR\n</h2>\nThwack is:\n\n- 💻 Modern - Thwack is an HTTP data fetching solution build for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was along time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differenced — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nMethods\n</h2>\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n### Event listeners\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string,callback: (event:ThwackEvent) => void ): void;`\n\n- `thwack.removeEventListener(type: string,callback: (event:ThwackEvent) => void ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await thwack.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack with automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*.*`         |     `text`     |                    `response.text()`                     |\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to setup a mapping between content types and parser types.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `.default` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \".default\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can ovwerride the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could create an instance of Thwack (using `thwack.create()`) and share it throughout your entire application. Here we set the `baseURL` to our API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\n// api.js\nimport thwack from 'thwack';\n\nexport default thwack.create({\n  responseParserMap: { 'image/*': 'blob' },\n});\n```\n\nThen `import` the `api.js` file to use these options in other parts of your application. Any URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nimport api from './api';\n\nconst getBlobUrl = async (url) => {\n  const blob = (await api.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promist resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range hwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with parent instances (if any) as well as with defaults.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch`.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parents's default options pass down through the instances. This can come in handy for setting up options in the parent that can effect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n### The `response` event\n\nThe event is fored _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nHow to\n</h2>\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\n```js\nthwack.addEventListener('request', (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n\n    // the caller's request will be resolved to this `ThwackResponse`\n    event.promise = Promise.resolve({\n      status: 200,\n      ok: true\n      headers: {\n        'content-type': 'application/json',\n      },\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      }\n    });\n\n    // tells Thwack to return `event.promise` instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n  }\n});\n```\n\n### Load an Image as a Blob\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRght now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint o a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  consy newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nLicense\n</h2>\n\n[MIT](LICENSE)\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.1.0-beta.1","_nodeVersion":"10.20.1","_npmVersion":"6.14.4","dist":{"integrity":"sha512-G6jCD1kXEckWX8hmGh+DdcVWpw9QdiIXCs/y5F1u9FKOOBJavqNhzdRHjmSe7brUsJ80TMpvFAnjfcWTqte/uA==","shasum":"b644e8281d721a7e2d247e1cf87a74888511d869","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.1.0-beta.1.tgz","fileCount":10,"unpackedSize":89877,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJelziXCRA9TVsSAnZWagAA/v0P/27mOZy8nhbLiaPwYKYj\nDUNGbbWkArBvS3GRRITXtkdypn3jYmj/zpVSdkML4uvFWWlRjeBpMoKp7d2x\nnK3czmEjohtmgcpKSev4Rrv2tvqzDvtnW5bgIDOEt/gP/xxifASn+dgyvALG\n+2Gwa12s/fKYBB4sSgDxSjSUv0KpinTtXIBiFxtcoM6LE5irWegzc6RswMLk\nntC+szBmZ2XsCHFcqZ0xl1SCYphEhTv/qGsl1AoHTNI7P1AvKPZ7Tpz0NgMq\n2tCtyXS0sOA60Lt6rCdSK49J0HGTXVOMLNv7uc+670hTGTNGDD0caFzmh2fe\nW7Mp5aCQC67VpjQ/6cMhX9IURMPe50Ece365l/paS5yOnuZ2ssqocEw1engm\nqhtViEHTDjfgYrE3Zlzn0urVh0hy/4k6FtFqtLVkIfNHcLTXKvqXC2xoP3wD\nTKmGZmcxRm50Vv8BYrnMHmD5mRUcTN9q0r0LIqor/B7jvCLslNVrgG/7oJap\nZVZDfSbtFw1OPMOxhDOXD1HXbJI2Kzok1GEBQ3n4LNedkEoXes8plNP8MZH7\nH5LwKmqNb87+e5aJrfovLKynnRml0U3KGFV3hSR2oaLvPGk3uavDvpRTIGTv\nOKHrZy83XYd6PC6PdzCd+Ezc+13mrVGtN9wulA6gvVHMQzSmIBJDMxZoEFDY\nBcD3\r\n=neQJ\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIFCKwshbZB8o4euT9trTExczjBxtogQovVuR92eSOrSXAiBUdZebrArWqAv7yUUQuPu5QGQoGvas6xg8ncbprB2oRw=="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.1.0-beta.1_1586968727171_0.6591746032638861"},"_hasShrinkwrap":false},"0.1.0":{"name":"thwack","publishConfig":{"access":"public","tag":"latest"},"version":"0.1.0","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"5035ba6730a24fb04381c1ba77b806011f190ca4","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.1.0","_nodeVersion":"10.20.1","_npmVersion":"6.14.4","dist":{"integrity":"sha512-BWz6SHj44WctEZbW9uWkEwSUQCSitGRr8yPbuZijBMsNnmWDWFxsHSC8j/zfXCF04ZEUuSXsTzeFJC9f0424nA==","shasum":"276a052e53e246f64b386efb4da7316dffd21d4a","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.1.0.tgz","fileCount":10,"unpackedSize":89884,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJel2rGCRA9TVsSAnZWagAA5hgQAKNefcwBND8kFA44+PLr\nL3/DQn6sGphU+9GZaAgfA0forWi4LPOx2lARPs5fu97BVncDcBCqoHpE/V6q\nORrNmIRDRYkqUO/0z0XW9aNKGbq+JM430cEoNh5o2PKglNGS6f3Idfhv80B+\n31fEHc37lO5nn0fC9/gfCJqQNSJeyIDo7EtNjKubQCUZGedKhZGHVBn7w5K0\nrVpbQRyz/irY0LLDUcHhKpv8wfxfB4OTeiASMZXwj2O2TB3fAhIHJPZrVcUN\n5vfdVpUVSoHlEerOaL3vzl6jvD7ZOi9YIDtHoc40tQFLbAyGKs7fueu+nCmw\nPXea3s3oPheX0Mo9betq5WfjQWbNnshpGBm++5ANrXJfY7mXtFlhove5cqq5\niukPg0rt+CutAjyI4FEUlgczrwA9efcETDxOv16mk6LAsioBQAuPZRnwFhsu\nc6v/Wegqwd+y2ilAvU2E6QVG9jzZnf1QR+R4ZFlwyqAuVDxp+6VUIyXwL1zQ\ninsat2qMrmJZb7UM1dvnZRlXmdF/GZUbNPjRlIbYD1BNc6ReA/qM2aExNPVT\nH5xaa6m99YwyNI+5jMk5qbTO4x9RAxMKKhmQKXdlPkXB5oeyu+gi/fMUHY0e\nuhyJ1AMi/C7qBKzmdAxdAFGwOx1e1vozGXb0fr+5mixvm/JnH4Q3F+WDcQpV\noZOW\r\n=eaND\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQC2C6752hc66E8thRdvMQeO7SmzYq+oCSi1cGN/Em13GAIgM6jPPu/xnl8nj5o+wopU5yPvx1t1wyf6jjf1uNvfCgM="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.1.0_1586981573849_0.12684247859195064"},"_hasShrinkwrap":false},"0.1.1-beta.1":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.1.1-beta.1","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-jest":"^25.3.0","core-js":"^3.6.5","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack) [![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern - Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nMethods\n</h2>\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n### Event listeners\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*.*`         |     `text`     |                    `response.text()`                     |\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between content types and parser types.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `.default` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \".default\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could create an instance of Thwack (using `thwack.create()`) and share it throughout your entire application. Here we set the `baseURL` to our API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\n// api.js\nimport thwack from 'thwack';\n\nexport default thwack.create({\n  responseParserMap: { 'image/*': 'blob' },\n});\n```\n\nThen `import` the `api.js` file to use these options in other parts of your application. Any URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nimport api from './api';\n\nconst getBlobUrl = async (url) => {\n  const blob = (await api.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promise resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range Thwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with parent instances (if any) as well as with defaults.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch`.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parents's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nHow to\n</h2>\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options:\n\n```js\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal })\n  .then(handleResponse)\n  .catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n\n    // tells Thwack to return `event.promise` instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above,\n    // the caller's request will be resolved to this `ThwackResponse`\n    return {\n      status: 200,\n      ok: true\n      headers: {\n        'content-type': 'application/json',\n      },\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      }\n    };\n  }\n});\n```\n\n### Load an Image as a Blob\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRght now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint o a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nLicense\n</h2>\n\n[MIT](LICENSE)\n\n## Contributors ✨\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","gitHead":"d405f8f51badc17fc5539f7b5233ff0d6467aa0b","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.1.1-beta.1","_nodeVersion":"10.19.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-Q8jYg8tJ2o9HXXaaYnHveuItqbPq96Ixz+vApY0cJGFVUvc7EbAhkhAaGajuxRxWG76cxbiCfPgcICkXlx+reg==","shasum":"486dc0ce517fe831f75c198b6c41680eb279f3fa","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.1.1-beta.1.tgz","fileCount":10,"unpackedSize":93807,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJemLBgCRA9TVsSAnZWagAAsloP/jwSqYD7vaU+1752mdlo\n+rpDcAoQn0zmGLxnAsqH6LCixNYpA2Lo5rxUNcYdAdzdzwMduu1pHxCL8lWp\naBOwF/teakphHWbtM3vTyae+bViIY8mlsTu7+wB6+nfw8rg0xViXqex9QswN\nw2DXvNqNNJc//46iQUz1dH0YPZmfJSZ+/8eDeYLLA7DTmS3c6CAqwU384cAy\n+JvkF/HRZOVAKs+4EBU481p3iqxcUcukM4hoLMFKMrnkWUaw4xGY8ToeV6tb\ns3hy1tXJ0QG+B/KoaZ1soG3qONUAH5luhT4tVsOK8fslG6y270CsWHZhTMo/\n+p+kh6lgmoW4HpOYDWnjAc5yUVbTptiBPvusZSI2fI5i33AOwLzKoRSKbRVW\n6LCDbF2pwtn4eaE8XkKFonmFytIrVwKD+936mdwsc57CsOb1foQyn0+VvSAO\ns2WRIZPaKHDQvpA8oUp4CF2O4Tv5bE2kPN4xQkPEEdAUWjrzPd3HlmnOGDsJ\n3rRgkxwroEGt2JS/PcmzrFuztrG++lCHn4judOQNY39s/WHo0aZfov/dUlo7\ne7CBmKCYJSKNbzBAqPfsXDYmOn0QPpJM9ilTKdP81snCxJCeseGRlq43Bvqp\ntSL0399iXRbh6aq7gLgiECmZpae4cRaVWWIVDkvrdvGu2jEQrJW7Fx64qJSE\nkLpm\r\n=allL\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDxi97QcwFgSKHHXDQ9xPEe5wrjALVRNY7LUTY5w+rWJQIgP+DFQOUtZRBp7LNApmQDsPG1aXyPzEuSoXGVsKjpUek="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.1.1-beta.1_1587064927587_0.6427140843110619"},"_hasShrinkwrap":false},"0.1.1-beta.2":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.1.1-beta.2","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-jest":"^25.3.0","core-js":"^3.6.5","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack) [![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern - Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nMethods\n</h2>\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n### Event listeners\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*.*`         |     `text`     |                    `response.text()`                     |\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between content types and parser types.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `.default` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \".default\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could create an instance of Thwack (using `thwack.create()`) and share it throughout your entire application. Here we set the `baseURL` to our API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\n// api.js\nimport thwack from 'thwack';\n\nexport default thwack.create({\n  responseParserMap: { 'image/*': 'blob' },\n});\n```\n\nThen `import` the `api.js` file to use these options in other parts of your application. Any URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nimport api from './api';\n\nconst getBlobUrl = async (url) => {\n  const blob = (await api.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promise resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range Thwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with parent instances (if any) as well as with defaults.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch`.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parents's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nHow to\n</h2>\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options:\n\n```js\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal })\n  .then(handleResponse)\n  .catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n\n    // tells Thwack to return `event.promise` instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above,\n    // the caller's request will be resolved to this `ThwackResponse`\n    return {\n      status: 200,\n      ok: true\n      headers: {\n        'content-type': 'application/json',\n      },\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      }\n    };\n  }\n});\n```\n\n### Load an Image as a Blob\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRght now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint o a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79184401-077dfe80-7de2-11ea-859e-ceaaf1364077.png\" width=\"20\">\nLicense\n</h2>\n\n[MIT](LICENSE)\n\n## Contributors ✨\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","gitHead":"694429499653db87077ebd0321f9dcc5064f5da5","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.1.1-beta.2","_nodeVersion":"10.19.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-v0Q8UGy/M5xitIeXCcg0Yu8GZDetfZNAW2YOu3GayUUrO5ND1AZI+lcSIQ5aHGov5WJ+Vfb0rryP4sZ6CZKD4Q==","shasum":"68cea8e3748a0800499be8ab7387e2fac583738d","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.1.1-beta.2.tgz","fileCount":10,"unpackedSize":93825,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJemM1lCRA9TVsSAnZWagAAo2gP/RUGnX2qEDQ6S27FVve8\neuvG6sCld8rmAuRPnvW0G6sV/GVs6McuxQ0ipLur/07DxErj1CYLL2aVTeEj\nWELva0BwGQ4ah3Sy64GnGmBI2EgbzsgPzsRV4CQnU+u2YgMeO+2q1M65ee+b\nbOz+9vbdcwCiBlcWeSniBYTZ3IjxM88t7Aibni1CNn1264V20VDcbm6OufsL\nFKFQrTGoFghVyqOPRCFiuoYired8mpPiY4I4VYDQ1mclRVJtaFJqZBIni7J2\nkR56sdT4NKq0f1RYFLL8MW2oeaQPkNCZt7ImTmIFPKZ5TQqT05T4UqYdLRTC\nKll4gx1W9+BgQ6k916G4C+6kIOligu+nivhn2FbP8nka9lHofqJOQK2Vk3r9\nNMZIAFlaXqm62WT/6A2kRsfnRqd9/y1MucyHHA2zvJKFDXhXR3GuCLygIm1R\nbVF8wTqSicyQPh93/6BwcAWhySySE16NPheCqfSFrnNyjpyNcAquh2EFHcCJ\nZys36KkklqFhOYPQ49i34s35PwW8yNBBE0Q5cAO3US5xOgQSM8zs64xnq6g8\nBc2TE96lPMTqDtH0qAdvYPn3QmFn/1h8WklayC/fUehEa8oHSElZFy2M+1tZ\npdlU5HWd1mxkEKS9l/FE+YxzraBBeyZ5gYPRf7U4tNQhYzkUkpaE7V/J9cSq\nTXgg\r\n=uaQE\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIBa9CDTzeCBmwvA53dTGy/+rUtgKs89oGOLJdtdNmjfTAiBbz41TIINZMNLL6acfveIl4yiI47TWh/y7CanIZi/u6w=="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.1.1-beta.2_1587072357469_0.6561645023872786"},"_hasShrinkwrap":false},"0.2.0-beta.1":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.2.0-beta.1","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.3.0","core-js":"^3.6.5","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js","!**/__test_utils__/**"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack)\n[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern - Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n- 🅰️ Typed - Easier inclusion for TypeScript projects\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nMethods\n</h2>\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n### Event listeners\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*.*`         |     `text`     |                    `response.text()`                     |\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between content types and parser types.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `.default` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \".default\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could create an instance of Thwack (using `thwack.create()`) and share it throughout your entire application. Here we set the `baseURL` to our API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\n// api.js\nimport thwack from 'thwack';\n\nexport default thwack.create({\n  responseParserMap: { 'image/*': 'blob' },\n});\n```\n\nThen `import` the `api.js` file to use these options in other parts of your application. Any URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nimport api from './api';\n\nconst getBlobUrl = async (url) => {\n  const blob = (await api.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promise resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range Thwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with parent instances (if any) as well as with defaults.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch`.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parent's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nHow to\n</h2>\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options:\n\n```js\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n\n    // tells Thwack to return `event.promise` instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above,\n    // the caller's request will be resolved to this `ThwackResponse`\n    return {\n      status: 200,\n      ok: true\n      headers: {\n        'content-type': 'application/json',\n      },\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      }\n    };\n  }\n});\n```\n\n### Load an Image as a Blob\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRght now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint o a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nLicense\n</h2>\n\nLicensed under [MIT](LICENSE)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nContributors ✨\n</h2>\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://kojikanao.netlify.com/\"><img src=\"https://avatars0.githubusercontent.com/u/474225?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Koji</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/tomByrer\"><img src=\"https://avatars2.githubusercontent.com/u/1308419?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tom Byrer</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=tomByrer\" title=\"Documentation\">📖</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","gitHead":"f0bdc3c51779c5837a0a807035983cbd2900ebc7","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.2.0-beta.1","_nodeVersion":"10.19.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-QA8LqviKPF+KaarsU2qg6aFFJGFqfa+pdrnMeZmDbWtyFcu/ue8R/ZTDB0Fs1cCrJF4R75in8z7BpST3JosGYQ==","shasum":"c5383733da27fc8982ae73c81d775dd1c9f5e178","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.2.0-beta.1.tgz","fileCount":10,"unpackedSize":122920,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJencCYCRA9TVsSAnZWagAA/gIP/ic0axRMAT96JSXe9oJQ\nczC1E4+mWx8diO89nGs3KxJ7PXeJL0IW1U+zl2pOER95qH+kHAYbasp8m3HF\nlFXzCYqqjD3rKjztYZyoLEybHWxb+f0IwB9uw9cberAw3KJiK268qedwRj7J\nZPlI0+UWLKwG91UBgxyQz7nmsuMyEdUGMcJg/ylgM4plgaNmEvzEuEYioO/i\nqHASJUXNPpZcLQnqf9M9Q+q9NGlAbrlrkdcK+l1x5z8KYPmfgsnLdnei3Gc0\nvJZKf5tZFGXFOLLbhwDJMoFPYVjB3jo8y6kE5gl3tZC8dO8xsraVaS8I9KdR\n8jARmBDIuOXC/cpa8lciUWiwjR4Bi3+Lb5V1FNH7vC2HLDV3N1R2oariD7Xm\npBlFrIXzQb1U9RktUer0Nq/9DLZd+IoJRfDN/FLBbaMCOyOF28nmyqXcEHbJ\npGLDXvZxY5vPxX2s7SZgKlM8WMBYNFSiGi0OdVghICJQvoOladinstcEluAW\n1aqeIgGvfiQDtk4Cdu3HqhcsTWr4KL+Y/MbL2yj54Vd57G6bzvR6zDt6WCng\nn6/bp5dnU3eCMUDFkEQ61GX0A+RdPvKUhPbq6TESDzg1wxs1835qKH8GQJfD\ntfTE3da6lp2ZtcdIA7Fkiq8HU2M9ljMxRUHrA1fvNMa+rgJCAQlyPvTH0IN6\np42S\r\n=jhxt\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIGRAVKyZUsx6Wutj99QGB4olLaAGF7piLZj+o1j+6ZrCAiBTLqOfPonqdHdLyS1EMEt7ad/fQSmb/G7xFNnLIhtScQ=="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.2.0-beta.1_1587396759703_0.5552257319029388"},"_hasShrinkwrap":false},"0.2.0-beta.2":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.2.0-beta.2","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js","!**/__test_utils__/**"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack)\n[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern - Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n- 🅰️ Typed - Easier inclusion for TypeScript projects\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nMethods\n</h2>\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n### Event listeners\n\nThwack supports the following event types: `request`, `response`, `data`, and `error`.\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `maxDepth`\n\nThe maximum level of recursive requests that can be made before Thwack throws an error. This is used to prevent a event callback from causing a recursive loop if it issues another `request` without proper guards in place. Default = 5.\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*.*`         |     `text`     |                    `response.text()`                     |\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between content types and parser types.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `.default` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \".default\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could create an instance of Thwack (using `thwack.create()`) and share it throughout your entire application. Here we set the `baseURL` to our API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\n// api.js\nimport thwack from 'thwack';\n\nexport default thwack.create({\n  responseParserMap: { 'image/*': 'blob' },\n});\n```\n\nThen `import` the `api.js` file to use these options in other parts of your application. Any URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nimport api from './api';\n\nconst getBlobUrl = async (url) => {\n  const blob = (await api.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promise resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range Thwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with parent instances (if any) as well as with defaults.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch`.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parent's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n### The `data` event\n\nThe event is fired after the body is streamed and parsed. It is fired only if the fetch returned a 2xx status code. Listeners will receive a `ThwackDataEvent` object with a `thwackResponse` key set to the response.\n\n### The `error` event\n\nThe event is fired after the body is streamed and parsed. It is fired if the fetch returned a non-2xx status code. Listeners will receive a `ThwackErrorEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nHow to\n</h2>\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options:\n\n```js\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n\n    // tells Thwack to return `event.promise` instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above,\n    // the caller's request will be resolved to this `ThwackResponse`\n    return new thwack.ThwackResponse({\n      status: 200,\n      ok: true\n      headers: {\n        'content-type': 'application/json',\n      },\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      }\n    });\n  }\n});\n```\n\n### Load an Image as a Blob\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRght now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint o a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nLicense\n</h2>\n\nLicensed under [MIT](LICENSE)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79573303-6f577200-808c-11ea-83e0-e69d937bf0c4.png\" width=\"22\">\nContributors ✨\n</h2>\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://kojikanao.netlify.com/\"><img src=\"https://avatars0.githubusercontent.com/u/474225?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Koji</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/tomByrer\"><img src=\"https://avatars2.githubusercontent.com/u/1308419?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tom Byrer</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=tomByrer\" title=\"Documentation\">📖</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","gitHead":"6a86175a052ea10468d46a61526b674db449d785","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.2.0-beta.2","_nodeVersion":"10.19.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-Jgz/U6YUJN/qkYgeRN9yGR6BxSHp/gw2PJr+cCHcDVJNi9okzXNxwxG/An5Zq5EH1kVvXYOtr2Kq8EJgK9E7ew==","shasum":"a26a2a9f52166ad6d16472a34d512e2b028ab089","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.2.0-beta.2.tgz","fileCount":12,"unpackedSize":124266,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJendA0CRA9TVsSAnZWagAAq7sQAIzjM4o+H5+T9aKgJTq7\nMT/jwXE73LxrXzNJhdqmgkDCdku8LkAg1fUoPGWmws9JUg6oSSXiyVgd91Lv\nauB5DDxzbvaLC1un73qDxEq5f/kzeDN7P72kvGz9rBi6Ur3eTonXKrH17OdX\n7OuDlqvEThOs0M66DMnYtRxpE0QP/f9VBVCxSh3C6ES8ZjDjSUpd2ZxpBvyX\nRgeGoUPrHfA8yIG2ZxSuCYVmnVOrIG7nUfah1xOHYmlpwyMXGQUfPHLOM7X8\nZtQGMtsXkpNP+BPCslj9X8sGCjKMup+A3eh0xmTS8UI2Y//v6lV/1+5YzHyx\nObPGpdVAJGAeykVZBCDnPKLLQqd9U5oKug4yFOnWtYI6+1fLIJujFPdWf7Xi\nPpKu6c2V8CeiSjeetdLNGvAwrGTun5wRPKGY/7bdr3PBvE5tYEwmEP8vx37Q\nIvgQJOlJReyTXDEN9SpNkKxxsgtM/88QlAlzDNpZrws5YZSHph+feYGuxLpw\nmvP605DEfymidXrnIj/nCEdD1gfoml5L//GGyFaGxHVtoVg5V/+6cdNbK6B9\nfzznQlRKiT47dOVk4/QRuQUHdaDYFBQ/v54cn7CQyQ5rmomj9RDtXhnnkw35\ncpGtL+fut7jV/lPSXNrOSFGB1k5ZggowijKS73qFW7QERUjdZNJrS65cm3Xk\n7XQA\r\n=ZjhK\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIDWdfGz52AQPvDsSbKy2s/YVZPgJAvJ1CxiBB0pd5By7AiEAjshKj54xaKvtAl4prZj2RTg6GPDdtzwVnN4HxDrAAHs="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.2.0-beta.2_1587400755621_0.43733677060669085"},"_hasShrinkwrap":false},"0.2.0-beta.3":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.2.0-beta.3","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js","!**/__test_utils__/**"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack)\n[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern — Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n- 🅰️ Typed — Easier inclusion for TypeScript projects\n- ✨ Support for IE11 and NodeJS\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nMethods\n</h2>\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n### Event listeners\n\nThwack supports the following event types: `request`, `response`, `data`, and `error`.\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `maxDepth`\n\nThe maximum level of recursive requests that can be made before Thwack throws an error. This is used to prevent a event callback from causing a recursive loop if it issues another `request` without proper guards in place. Default = 5.\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*.*`         |     `text`     |                    `response.text()`                     |\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between content types and parser types.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `.default` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \".default\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could create an instance of Thwack (using `thwack.create()`) and share it throughout your entire application. Here we set the `baseURL` to our API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\n// api.js\nimport thwack from 'thwack';\n\nexport default thwack.create({\n  responseParserMap: { 'image/*': 'blob' },\n});\n```\n\nThen `import` the `api.js` file to use these options in other parts of your application. Any URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nimport api from './api';\n\nconst getBlobUrl = async (url) => {\n  const blob = (await api.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promise resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range Thwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with parent instances (if any) as well as with defaults.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch`.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parent's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n### The `data` event\n\nThe event is fired after the body is streamed and parsed. It is fired only if the fetch returned a 2xx status code. Listeners will receive a `ThwackDataEvent` object with a `thwackResponse` key set to the response.\n\n### The `error` event\n\nThe event is fired after the body is streamed and parsed. It is fired if the fetch returned a non-2xx status code. Listeners will receive a `ThwackErrorEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nIE11\n</h2>\n\nIf your app requires Internet Explorer 11 support, you can still use Thwack, but it will require polyfills for `Array#flat`, `Array#entries`, and `Object#fromEntries`. You can either provide these polyfills yourself, or use the following convenience import instead.\n\n```js\nimport thwack from 'thwack/ie';\n```\n\nNote that this will work for any browser, not just IE11, but it will increase your bundle size slightly.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nNodeJS\n</h2>\n\nThwack will work on NodeJS, but requires a polyfill for `window.fetch`. Luckilly, there is a wonderful polyfill called [`node-fetch`](https://github.com/node-fetch/node-fetch) that you can use.\n\nIf you are using NodeJS verson 10, you will also need a polyfill for `Array#flat` and `Object#fromEntries`. NodeJS version 11+ has these methods and does not require a polyfill.\n\nYou can either provide these polyfills yourself, or use the one of the following convenience imports instead. If you are running NodeJS 11+, use:\n\n```js\nimport thwack from 'thwack/node'; // NodeJS version 11+\n```\n\nIf you are running on NodeJS 10, use:\n\n```js\nimport thwack from 'thwack/node10'; // NodeJS version 10\n```\n\n> Note: The `responseType` of `blob` is not supported on NodeJS.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nHow to\n</h2>\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options:\n\n```js\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n\n    // tells Thwack to return `event.promise` instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above,\n    // the caller's request will be resolved to this `ThwackResponse`\n    return new thwack.ThwackResponse({\n      status: 200,\n      ok: true\n      headers: {\n        'content-type': 'application/json',\n      },\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      }\n    });\n  }\n});\n```\n\n### Load an Image as a Blob\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRght now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint o a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nLicense\n</h2>\n\nLicensed under [MIT](LICENSE)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nContributors ✨\n</h2>\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://kojikanao.netlify.com/\"><img src=\"https://avatars0.githubusercontent.com/u/474225?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Koji</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/tomByrer\"><img src=\"https://avatars2.githubusercontent.com/u/1308419?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tom Byrer</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=tomByrer\" title=\"Documentation\">📖</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","gitHead":"e13c33cf1f733c4eeb51223ac50e3711e93a629d","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.2.0-beta.3","_nodeVersion":"10.19.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-ql1ypc0QcNc+HUIoevk2VBCXhzYkFHRgPXvkzMFx3AiMny3FNuALnlay7mySBcAcPIMEmejN1r0GFHl783QCYQ==","shasum":"8322cea634e44bc265bf1486ce83d53282303b88","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.2.0-beta.3.tgz","fileCount":13,"unpackedSize":125859,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJendpgCRA9TVsSAnZWagAAUw4QAJOGxrFpBVoxvOodrGYo\nlaAi3LgaiRhAfHaGbssPDI8wVwJzcmHZm+UMwrs8K631AP46z4Jaga1VKs+H\ngkgSEzDM9eVNrhqoM21KPZLzt2WvBQRouYtRA+zBVTF6UCsWKm8ZPAp230nk\n56AvDT3SjYF3XG0WZMw/KB/t8eS9ZWdICvftpKv6MOizVMPENstIs5LXxCA/\nK9tcAC9N6CpEU3UiVP7C0IuBXa2NAUOCGWVvL0QqCtYBP1/9DPxgG3cfXKcr\n40sXOk7ddN3bDrUVzoqFdD7p6urkNC7bysScVAOUhW1boEziuXpFVSlXjL+w\nRwo3beQN35rqxtxmsErmQyaTmFafNaq+xAM3pONMwXpmHlfwxzLdY4UFZZHa\npwLfSTxAvurFSRPSkv8ZKFofDhwd3rgQ22GSHQxb2PuepvypnW87RyMPXMZK\nKYFoCAIWygjoZml4u7gjO4IV0AweP6Vnv1jpSGUQxdB0aO7oQTIh9fxGJXwY\npOJd96pFUXMNG2cpu1yr5WjxpG4xeZVybBzPSyg8oXzh5nDmA35KN4zfANU4\nmKj9kS2CKUSz+qPwq1U+nZqO+mHc8I/JvvCQNqqmj2DeXk99gBEY9p0JciW2\nQP9EZJtV5BRFBh+kljciifhVn14SVYJFlPxfREeZErSlG1hTrV7HZWKwPaoI\nIIV/\r\n=mOJO\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIF+Gg9Q5bVz/hSOMxSC4AonheynzXX8lGaidzGYiKqiiAiEAvwDOOxEQkSLabXHjftFaOYvYCtdl5FNCeyF5i2TOU5E="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.2.0-beta.3_1587403360484_0.7848332948969485"},"_hasShrinkwrap":false},"0.2.0-beta.4":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.2.0-beta.4","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js","!**/__test_utils__/**"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"282b5d3a3915d7e837672c8650a2c9abf3b642c0","readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack)\n[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern — Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n- 🅰️ Typed — Easier inclusion for TypeScript projects\n- ✨ Support for IE11 and NodeJS\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nMethods\n</h2>\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n### Event listeners\n\nThwack supports the following event types: `request`, `response`, `data`, and `error`.\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `maxDepth`\n\nThe maximum level of recursive requests that can be made before Thwack throws an error. This is used to prevent a event callback from causing a recursive loop if it issues another `request` without proper guards in place. Default = 5.\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*.*`         |     `text`     |                    `response.text()`                     |\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between content types and parser types.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `.default` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \".default\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could create an instance of Thwack (using `thwack.create()`) and share it throughout your entire application. Here we set the `baseURL` to our API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\n// api.js\nimport thwack from 'thwack';\n\nexport default thwack.create({\n  responseParserMap: { 'image/*': 'blob' },\n});\n```\n\nThen `import` the `api.js` file to use these options in other parts of your application. Any URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nimport api from './api';\n\nconst getBlobUrl = async (url) => {\n  const blob = (await api.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promise resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range Thwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with parent instances (if any) as well as with defaults.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch`.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parent's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n### The `data` event\n\nThe event is fired after the body is streamed and parsed. It is fired only if the fetch returned a 2xx status code. Listeners will receive a `ThwackDataEvent` object with a `thwackResponse` key set to the response.\n\n### The `error` event\n\nThe event is fired after the body is streamed and parsed. It is fired if the fetch returned a non-2xx status code. Listeners will receive a `ThwackErrorEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nIE11\n</h2>\n\nIf your app requires Internet Explorer 11 support, you can still use Thwack, but it will require polyfills for `Array#flat`, `Array#entries`, and `Object#fromEntries`. You can either provide these polyfills yourself, or use the following convenience import instead.\n\n```js\nimport thwack from 'thwack/ie';\n```\n\nNote that this will work for any browser, not just IE11, but it will increase your bundle size slightly.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nNodeJS\n</h2>\n\nThwack will work on NodeJS, but requires a polyfill for `window.fetch`. Luckilly, there is a wonderful polyfill called [`node-fetch`](https://github.com/node-fetch/node-fetch) that you can use.\n\nIf you are using NodeJS verson 10, you will also need a polyfill for `Array#flat` and `Object#fromEntries`. NodeJS version 11+ has these methods and does not require a polyfill.\n\nYou can either provide these polyfills yourself, or use the one of the following convenience imports instead. If you are running NodeJS 11+, use:\n\n```js\nimport thwack from 'thwack/node'; // NodeJS version 11+\n```\n\nIf you are running on NodeJS 10, use:\n\n```js\nimport thwack from 'thwack/node10'; // NodeJS version 10\n```\n\n> Note: The `responseType` of `blob` is not supported on NodeJS.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nHow to\n</h2>\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options:\n\n```js\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n    // tells Thwack to use the returned value instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above, the caller's request\n    // will be resolved to this `ThwackResponse` (defaults to status of 200)\n    return new thwack.ThwackResponse({\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      },\n    });\n  }\n});\n```\n\n### Load an Image as a Blob\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRght now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint o a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nLicense\n</h2>\n\nLicensed under [MIT](LICENSE)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nContributors ✨\n</h2>\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://kojikanao.netlify.com/\"><img src=\"https://avatars0.githubusercontent.com/u/474225?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Koji</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/tomByrer\"><img src=\"https://avatars2.githubusercontent.com/u/1308419?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tom Byrer</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=tomByrer\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://iansutherland.ca/\"><img src=\"https://avatars2.githubusercontent.com/u/433725?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ian Sutherland</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=iansu\" title=\"Code\">💻</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.2.0-beta.4","_nodeVersion":"10.20.1","_npmVersion":"6.14.4","dist":{"integrity":"sha512-3VskKnD6rMJjUTPGxraYSvErDHTzZjI05gzXchAQ2DsAFj5RPOgDTdqKpKlKPROwusIa2FRA1ihYiSn9Z7MsCw==","shasum":"88843e474d2167bde5d21f9ed11dfc90a27af790","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.2.0-beta.4.tgz","fileCount":13,"unpackedSize":126155,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJenfdzCRA9TVsSAnZWagAAUfoQAJlflb7EDEXDcRQu3Zrd\no0K05k3Z0hG4BwhvHAkUO4l6arvOmOhauK5DGhQ/ZtN99NIF3vGBaN8IvbR1\nsSplhVVVx2SE6eimemh0uh3gYdYmuPt0jfmlV40K2IrHQPu6Tot9EFZREU9k\nsQFP4nBVbqW96BiJWbA8PYqFacOYuh/+6AU+1lYjVH+tj0QqAkNkPoD0wFiV\nFslGUKlNQKqs8yRwMqP1BmKq9IbeVgKokaqhfq5VqBma+ZEG+hunxB60s2+v\n5FqDpOhbX+ruoj/BeT3SQkOUNCMW1I62FebLN16JVBbGfKNZ5vjR5O2bPkfm\nd4BPRraDnO0lxGe6co7uU0pa2WSW44ncdYr6HtIseW10Z8nd8LPP2cdROo3m\nGPlWInV/B6AU/kARXU0r4JI9n2vNmT43iBAVOsWUhG982AZmGgsSQHFmwDrW\nNRU/syuwRhBiyT3qxMKlU1QoF1Cu3WPj7f9t+AOKbjvkSB/zpGgdaw257+qH\nYF9FQEPXCqfBpxZ/KDeAgCnOfiwYDT26e+p+I+ZaCSd5SxAmjs3Q+3KdlrIu\nNf3FZP8vlHDQnVLBuiA55zvA5gYFglkvLG3ivhtCpYn01w16GNdNKodC/QI9\n2aYmLEpSIK1ZvQULJqbddj1/8M8z/C7CzsRRJg0JG0ZjnyshrJMxZb6M6L5G\neGpR\r\n=/eG6\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCGwb0tD1TXHhM9uCTbZoXW/AP22/SFWc8JRy+DnZ3CEgIgH0ulLdcIc1yhtCsXFrXk9aIHboxg5eynZSDka2BpNCA="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.2.0-beta.4_1587410802917_0.8253498468889184"},"_hasShrinkwrap":false},"0.2.0":{"name":"thwack","publishConfig":{"access":"public","tag":"latest"},"version":"0.2.0","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js","!**/__test_utils__/**"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"617dac022b81620cdf968b9453c9fb3920989b4b","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.2.0","_nodeVersion":"10.19.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-/jBxnkz2Fp5aEkTsvT2MqhmYYJOgnYP3XsvKli7nsbetC6D6LuVftxVE02+KXaPJ9XifxPEdfFzgJWSvdpxG2Q==","shasum":"1279318a3887d9f8aa18037d4414d267f8330dbe","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.2.0.tgz","fileCount":13,"unpackedSize":128612,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJenvCMCRA9TVsSAnZWagAAsZEP/A+C1xYQiyOBId2xfzKZ\nwhGaAA6XPrqhx0uwSy3fZnhFSGsFhiXXL2FfiPPv6bRFJsIu9++poq8pHW/I\nPd9e3DfopzCTs4Mx/wrZXnHW3G5hl4m0tUOm14izOJE7f+tRDQxHGmfkp9bX\n6p9yafFQ4p/iAonemSQrukgg6dAifDpEzroimulnippjnbQfqWjQMBs1C5pJ\ntsJUrGr6bKNl2j9qUMI8vs7g9cK4ECAlxCgd1D9vgHtEvbXmXKMpzxTNFyc9\nCFm+KyvYmqE5nBPtDCfvYv36I3jkHvdo1XKbj4Kcb9wEBBBtbTTywiZPXYUo\nOeYFbPuakZec4OVCD4usRC4fHIM5u3tNdrrqec9hPVOndPFIni/YANMjOBKc\nuSE03Bjm/krPCP+Pbg5U0taZ93fMi6eKrJPmzY4It2sc6o307lH0AEDeFcYs\nbp6GU6eM8GpbsXD16/Y58OE0ex2B0kiWvYHGifvnbVc7n3khDY6vva5kdn4/\n59zrtMo/UKjLt3tb+bCwsF8o/EHUZlMu9lXVYQB33JVaj98pk3eLkZq4T8/1\nQg/geETBnGATEQWgMjdotoxEXiCWNLx5V5OLUZo2snplcqHME6YmMGRiZAdi\nlj8A6Hrf6+XatuTdy6tDSQhvbdz0a+85WQGlAKHoGb/fp+hk94u9KXRWPVVm\nMOkh\r\n=ZZZT\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQC2xHuQHQi1aIWJj74u3eo5j7OCQ/OduWVrlXBiHCDwJwIhAIWwY9fnzIqxE69Y9Qb9k5EXSi+pXy9pRPT/JqD4NIFZ"}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.2.0_1587474572305_0.41325110952145483"},"_hasShrinkwrap":false},"0.2.1":{"name":"thwack","publishConfig":{"access":"public","tag":"latest"},"version":"0.2.1","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js","!**/__test_utils__/**"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"2d2a73743fc58d1292381bc3eeeaa4aeb6596923","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.2.1","_nodeVersion":"10.19.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-bI49qvY5DlfSBh6fly1SVBmZkLsJDbLKZQqQkoU5TU73TgOHIw1mqgP1UGlfoxJjEKg00hSmMutwIK6XPex4SA==","shasum":"1b2ba35bc5ca9ee082d5adbc80bd781fbc1eaf6d","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.2.1.tgz","fileCount":12,"unpackedSize":127852,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJenvVeCRA9TVsSAnZWagAApPEP/0dg2RJiP+BkI9rCnIFU\nQ+70p+UPzzcR1dnm+pC67X98QN6Asq6FtuH8e6vFs4nsFyIMPb4D8Sl3SpgN\nZ9BQIdGZaty/zivv23ShnsoUTE+wHt8CYvLy5+Kk+99aPDYmALuIj7nDncV7\nODM1YIHNl3EVFl1fOu03i3eVvRqbnyFgOq8KhfVfZCspjWEhWURAM09lAjM7\nwKHNmu7ei6XapCy6faCaWVZOUSUnVCJdBXHUPsBROxD7nhdwfSp5bEqNVWzp\ng4XEqihVBiCiHoj1K9YWF624jbfx05FVvmW7EZnVMGGU4lf9ZgrTkcnQvhyh\nfb5awmyCdpx0T0OJrw//cvPRpLMqVVp4n+XsxzeY+vYZpLkzNrTZFrZQKAF7\nTp1WOJv1d7BQaaTAfbyc4i46XZrNo2dEDbbt6WJLLO2XaOvBaS7LGrVDolrV\nosFmcdAx2JsF2GLqrWfq6E+RNzlEHnHm2P2fTgrFrP2cyjeVd+Fc0fq2q4Co\nsztlhQw7EtkvLgfRVQZ5C1Hf3jUJoeIdo7/yvSt43st3LkXuWKpYmeXBLGeu\nc+vPnp7q96LQe8wbc79E6cyqvYWWMhmMJTnRHQU2ONCsy3vswuEUT6/lFYtJ\n3d5qDB4M1+oaSufOanBTjTozaGqQJvBkJydEKPylal5iEKQx1MU2uHm4/c/M\nBxM+\r\n=JIuM\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDZj6LsD0pItYm/5Qai/RpX5ONZxE+08WMDQV0WvXXAvQIgWWNZPaPSN08I3gB6SGyTCZFNYFZkbPE6CibfiGeDYtE="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.2.1_1587475806226_0.391023993458685"},"_hasShrinkwrap":false},"0.2.2":{"name":"thwack","publishConfig":{"access":"public","tag":"latest"},"version":"0.2.2","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js","!**/__test_utils__/**"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"cf7480bf708254fbbc32325ca6cd0b73099ee751","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.2.2","_nodeVersion":"10.20.1","_npmVersion":"6.14.4","dist":{"integrity":"sha512-m6r8L7HHc0FYOn8xRS0EBMT/J2ryBQNi7vLgnGLx4xZS46fUidyIa3467boedCdum8owOi5ukzYPMQDr9uQfjw==","shasum":"41793d31cb9275b21a9b5587b690fcbf3544120f","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.2.2.tgz","fileCount":12,"unpackedSize":127844,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJen2gUCRA9TVsSAnZWagAAjvwP/jGLVSX6bZ0AeobGywqb\nBBXMw8vk3ta5AXKT5sSEKnoCQqwNlsoMep6KYXCw0HcQyRBi89RNjCjjpj3N\nalQ6AHtTQFCBdftTq85gqt+jEh2Hw9mh0WrQ+LbjbQCkeykgpQU3ICxLbS6M\nxMIZgHRXgu2FNaZwHlukOW3C92Sy6e79x4tuPOrs1teDJPPSOrykTGTV1bC6\nmU7Ld1r4TkEOXNnB9fVS5VMl0RI3/qKmM1oCwBPT876/Xc/+rwh4MAeVMsUE\nuSQ13D1N+Gkxn1tVU8gX5GBcYFjJHSoKCpE7U1Mc6Rc5eu6zcnCdO/1lNycK\nnz0u4LJrK4rWsbWYsZjQIGALOBRvse6Ie0C+/EI7FO8neY3KDbVYEXjX2wy+\nXvUZIC1aLL3Yv4ebNwNEnJRy5CZXOmOJ1bKwyFLsr+CKaDIbhzT1wg+/EpPs\nOOvc+77gIWqbMQEMJyARfV6+9p5Z9rijK8m8pzbSC+vH5j1ieJef9bTBYm4E\nebHDT3fWnckeblwVYItCkCDuKt8x2j6nvJPxyVMgMYyoBPca3QZifEH3wEPf\nDqA1AsywgviLlxn0aXAE+jk3aog3P0/L8zkfjUF7rOcaio1UO7zc1cyr2kUe\n4IVlpyjtglteia7dJQYKmenld/SFHWGS0GyV20LCp2m30zrrhZcx81gU/5B7\nxpAO\r\n=++9i\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQC7Z7C8OOWZNlesrnJq5KDRzMSWj5zsOmquHQaqayqOwQIhAMGWsOwPWPMVggr3a4eJP7xp60u2rnYkmPATOjZNNqNc"}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.2.2_1587505171902_0.14981042505342446"},"_hasShrinkwrap":false},"0.2.3":{"name":"thwack","publishConfig":{"access":"public","tag":"latest"},"version":"0.2.3","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js","!**/__test_utils__/**"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"9c27fbf403752635050b3bcf78050fab7b4f37df","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.2.3","_nodeVersion":"10.20.1","_npmVersion":"6.14.4","dist":{"integrity":"sha512-rDyThHi6z0mBkSDafLfx1e7Cx0MbSJEdtORw6jgkHKxo5ucfKqEpbTfAy9BholAZSDhB3YwCnnUB9mx2UzTmsw==","shasum":"cd5a67c613bea9c60be915373cf84b2fd3053274","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.2.3.tgz","fileCount":12,"unpackedSize":126599,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJen86GCRA9TVsSAnZWagAAHRwQAJxyy+gXs+vp3gKyVvW8\n9WI18lXTCJFmrBuRNg68HJQR2ADNd69SxOuVuJoMR9m2b4PDuU78ruCtzsp9\nOGY8f8wP1Vie9Y08DDt0M5DA2gzH3bk8WcWgcwfL9p206FEV5eHxEuScng+j\n82fQO8UGDZJqqWc+DV3QcXHRjJR/WNjuCeFxAaYKXxSy5bPPgzi0HuQK5G1U\n/v3xEol05TxeDYjLCGnatTVIROuiIsAjCB8oWCyI/uwAdJidv+PoQEUFsxoe\nGitpshUcUnDuTRIoXqDaX4owAhuRkG4t4rWBsX4rf/78SRFGJOxC9JYAn0XD\n16O4Emw+anOKee1DVtCchpQf8OWQjjRLAnaCxJSzUtVY55eaNYJZIWBSxk0Y\nPCFnjbH9S6KoaIvkHWc82VYys98YnMg+z6L9MVLiFFr8Jw97UvcL9KFJpMvL\nOdZseEFTgLBjxCWbOoevKrHBGlaLrkRlxNDBIb6sjnZmN5+AyNbmrUvQVfuX\nlA0elCj+N588lTE7t3MyYYMDe3tZzCroeMoZ1uyJ06LWe0OaBmwDS1KHRDK4\n3RK0Z5N6AiJefPj2laFCH8QjYThXmWQT63T+G+JzuV+EVNRfy6Tdw9KoxWIO\nYcpd4ECkbZQ3/zTrBuR3N8gWn155GW8hOoF8z9Og7JNkpBFFNKEiLrTmpSO2\nL0Tb\r\n=Zkhz\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIHlIsIhsLrAd7wky6lZkFbO7qO5+KfTTPZ8Qw5st3LcNAiAkyJ+5EKSxRPmJrqEnieYL7oI+pvfmK92wdkHZEZVIuQ=="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.2.3_1587531397650_0.3561001035508946"},"_hasShrinkwrap":false},"0.2.4":{"name":"thwack","publishConfig":{"access":"public","tag":"latest"},"version":"0.2.4","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js","!**/__test_utils__/**"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"d18b260aab5a34c1127258704105eb941d0cf086","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.2.4","_nodeVersion":"10.20.1","_npmVersion":"6.14.4","dist":{"integrity":"sha512-rzmN0uj/UyyGuVA6VIMYsyPxI8Zf+pMo3fScEPRpP+MvRkvIP3GIklyt9YcrRzSCOHeMSww91FLmX7vM3ua29g==","shasum":"cd14fef13556bdd5a2f71c279d78a46e256ddb53","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.2.4.tgz","fileCount":12,"unpackedSize":126744,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeoFdcCRA9TVsSAnZWagAAtdoP/3I9h4msFS1yGj2LzRd7\nkq5Fh4i9n56e1otVww7e3Zm9QcUMO72Fqyrz011Xak7GtfamgqmKnBmM88R+\nFvEHCp9Y8+lG3GGPbao0M29DaERcbixqAL+3pqV/VdK0j4C+7fUAkeKMzGHX\n0zl/7fcxDNpOoBs3d8kqFm8s7ibWVvv7ezrwfN7Y14pGE8nLttO3BdTwcwaC\nLVGRrgLuUNxYQ7EQk/bQ6F6fN7vpefEV0Zptd36yfjtBhTyFsuHNVtjyYwDs\nAd1ueMZfWpe/dwqW3xFFusKdXLNHF62JUNKdbsk9p+oEILcvBUkLpCfKLzXN\nCiK7Jp5Mm8Ddr1LIgE7zMcN7KCgFPaKFQRWNpKdnQhwrww7iRQoPbN9NWr0D\nzBIVjqpohDczA7oh6obqfLqetMVcOcKXs/99Kd2AL+mAa7dRJAqgFXxpr8mG\nleGrAVxZHgAcdSmC/TqdFbMgwvW99mJeVEsTm/1bI++2SgO+jLQso0T1z/yw\nW2lwHgaesn5rHhDVORKDVBCeweZ58PwnRGE3hU0K8giih2QSYX+bVIbOP1Yr\nN4AL4NfORBRci1VOxRRgzVvGoSA5Wj2UfYzV2lWnty3v4lOP9EQcI+6wmfBc\nv6qBFvaSQhY9T5TdAlYaFX32WuCy/jdg+gq339A+rBcXEfbAsaepsUG1HQOk\nLvjB\r\n=8Qyq\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCArQR7FqNUCnP/4nAa/XxClX0gVm72Va46idOdukr0RQIgAriJ8OAvHHTAuEX6/vbZK/u2XrkeiSpe3ozgBQ9o/7M="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.2.4_1587566427857_0.13539569776959026"},"_hasShrinkwrap":false},"0.2.5-beta.1":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.2.5-beta.1","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js","!**/__test_utils__/**"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"d18b260aab5a34c1127258704105eb941d0cf086","readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack)\n[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern — Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n- 🅰️ Typed — Easier inclusion for TypeScript projects\n- ✨ Support for NodeJS 10 and 12\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nMethods\n</h2>\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n### Event listeners\n\nThwack supports the following event types: `request`, `response`, `data`, and `error`.\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `defaults`\n\nThis allows you to read/set the default options for this instance and, in effect, any child instances.\n\nExample:\n\n```js\nthwack.defaults.baseURL = 'https://example.com/api';\n```\n\nFor an instance, `defaults` is the same object passed to `create`. For example, the following will output \"https://example.com/api\".\n\n```js\nconst instance = thwack.create({\n  baseURL: 'https://example.com/api',\n});\nconsole.log(instance.defaults.baseURL);\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `maxDepth`\n\nThe maximum level of recursive requests that can be made before Thwack throws an error. This is used to prevent a event callback from causing a recursive loop if it issues another `request` without proper guards in place. Default = 5.\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*.*`         |     `text`     |                    `response.text()`                     |\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between content types and parser types.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `*/*` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \"*/*\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could create an instance of Thwack (using `thwack.create()`) and share it throughout your entire application. Here we set the `baseURL` to our API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\n// api.js\nimport thwack from 'thwack';\n\nexport default thwack.create({\n  responseParserMap: { 'image/*': 'blob' },\n});\n```\n\nThen `import` the `api.js` file to use these options in other parts of your application. Any URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nimport api from './api';\n\nconst getBlobUrl = async (url) => {\n  const blob = (await api.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promise resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range Thwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with parent instances (if any) as well as with defaults.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch`.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parent's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\nHere is the event flow for all events. AS you can see, it is possible for your code to get into an endless loop, should your callback blindly issue a `request()` without checking to see if it's already done so, so take caution.\n\n![thwack events](https://user-images.githubusercontent.com/887639/79867660-aee3ce00-83ac-11ea-94fd-4078c1a36244.png)\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n### The `data` event\n\nThe event is fired after the body is streamed and parsed. It is fired only if the fetch returned a 2xx status code. Listeners will receive a `ThwackDataEvent` object with a `thwackResponse` key set to the response.\n\n### The `error` event\n\nThe event is fired after the body is streamed and parsed. It is fired if the fetch returned a non-2xx status code. Listeners will receive a `ThwackErrorEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nNodeJS\n</h2>\n\nThwack will work on NodeJS, but requires a polyfill for `window.fetch`. Luckily, there is a wonderful polyfill called [`node-fetch`](https://github.com/node-fetch/node-fetch) that you can use.\n\nIf you are using NodeJS version 10, you will also need a polyfill for `Array#flat` and `Object#fromEntries`. NodeJS version 11+ has these methods and does not require a polyfill.\n\nYou can either provide these polyfills yourself, or use one of the following convenience imports instead. If you are running NodeJS 11+, use:\n\n```js\nimport thwack from 'thwack/node'; // NodeJS version 11+\n```\n\nIf you are running on NodeJS 10, use:\n\n```js\nimport thwack from 'thwack/node10'; // NodeJS version 10\n```\n\n> Note: The `responseType` of `blob` is not supported on NodeJS.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nHow to\n</h2>\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options:\n\n```js\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\nThe `status` in the `ThwackResponse` defaults to 200, so unless you need to mock a non-OK response, you only need to return `data`.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n    // tells Thwack to use the returned value instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above, the caller's request\n    // will be resolved to this `ThwackResponse` (defaults to status of 200)\n    return new thwack.ThwackResponse({\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      },\n    });\n  }\n});\n```\n\n### Convert DTO to Model\n\nOften it is desirable to convert a DTO (Data Transfer Object) into something easier to consume by the client. In this example below, we convert a complex DTO into `firstName`, `lastName`, `avatar`, and `email`. Other data elements that are returned from the API call are ignored.\n\nYou can see an example of DTO conversion, logging, and returning fake data in this sample app.\n\n<div>\n<img src=\"https://user-images.githubusercontent.com/887639/79865700-94f4bc00-83a9-11ea-9de6-9f204d5857c8.png\" width=\"500\" height=\"526\" alt=\"Mickey Mouse sample app\">\n</div>\n\nYou can [view the source code](https://codesandbox.io/s/sharp-chatelet-mje3w?file=/src/App.js) on CodeSandbox.\n\n### Load an Image as a Blob\n\nIn this example, we have a React Hook that loads an image as a Blob URL. It caches the URL to Blob URL mapping in session storage. Once loaded, any refresh of the page will instantaneously load the image from Blob URL.\n\n```js\nconst useBlobUrl = (imageUrl) => {\n  const [objectURL, setObjectURL] = useState('');\n\n  useEffect(() => {\n    let url = sessionStorage.getItem(imageUrl);\n\n    async function fetchData() {\n      if (!url) {\n        const { data } = await thwack.get(imageUrl, {\n          responseType: 'blob',\n        });\n        url = URL.createObjectURL(data);\n        sessionStorage.setItem(imageUrl, url);\n      }\n      setObjectURL(url);\n    }\n\n    fetchData();\n  }, [imageUrl]);\n\n  return objectURL;\n};\n```\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRight now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint o a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nLicense\n</h2>\n\nLicensed under [MIT](LICENSE)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nContributors ✨\n</h2>\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://kojikanao.netlify.com/\"><img src=\"https://avatars0.githubusercontent.com/u/474225?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Koji</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/tomByrer\"><img src=\"https://avatars2.githubusercontent.com/u/1308419?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tom Byrer</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=tomByrer\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://iansutherland.ca/\"><img src=\"https://avatars2.githubusercontent.com/u/433725?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ian Sutherland</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=iansu\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https:///www.blakeyoder.com\"><img src=\"https://avatars0.githubusercontent.com/u/5393338?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Blake Yoder</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=blakeyoder\" title=\"Code\">💻</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.2.5-beta.1","_nodeVersion":"10.19.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-wZSUhL/auFvEi+u3oRCflqszWMy9XG9bHwtqsNkA10QW44miQ1lb1Kry++llNj3DpyUaXhlQ6vh4HX9UrPnEZA==","shasum":"2be7b5cd64ec45d99659309a0dd7eb23a4b9f811","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.2.5-beta.1.tgz","fileCount":12,"unpackedSize":127501,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeoGcpCRA9TVsSAnZWagAAkMAQAJUIHOreDadPn2CH0Y9v\nZfL6W96tdrjFalnAun4KE3N4sDnb67x8qOzO3eH53tp+We1ZLGrwSjTX4WnC\nIfQRpM7naujvWWQquytGHRvZCohk8Bx+O2PnFuHHd3tOCMeFr6cxgnsQiPvg\n9VXksICgyxSM3JrZi47M9nluU0csMct50pMU1/0WNZT77NV89La+NIUjzwDb\nCIl7USz5/CGrxW4ZwbytDTpJR5i3PyyNxjb5/8upZ0VlnLh+S7+Yaj30eBia\nIgeyxpv5WE2/AY+hywMlSdYml3cZLfzWlzpRXomEF1Q/+Zl53R+RjABjFEwc\nKv8hbNWVXaaJVeVJSojg0A23eVaiG0LZXbgay87mVgHMlhkIv7wlancGCoze\nd4KIuoym6x/M48SXbkSjj24mLgGa/aNWQrg+IE+JZWu2xYLH1wdZ8brcfsAl\nB9BxeS2dKaPajGqoPzYgn7WVquOQmN793TkIO1F4lsZIwmRQrIoLa84gigBK\n9KdHS/iRRlj6w4wFTf7HruQEePCEfOVf3JJJqA9OIxNE/DukoGozourmeBp3\njaqQWA+GXIMvadX/3TEqjqTVHOsunMwgMeFh3Jq2FUY31JmhaGgZGn7jOoph\nD9tb1J5EKFWLmdeiuNh3wTUN72c6f+Sijh8mS2BBegO4rKn/P7Xp3rF3MOuO\nebNh\r\n=NX3S\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIH30Ou0cvo42ZsPg6pT8PUcrTpfzRF0CHhe5FWElLimTAiAcugucECVZqz9HkowsYdnVbiD5iihLZ+LgKuNMOv4YHw=="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.2.5-beta.1_1587570472837_0.2927414434031408"},"_hasShrinkwrap":false},"0.2.5-beta.2":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.2.5-beta.2","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js","!**/__test_utils__/**"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"d18b260aab5a34c1127258704105eb941d0cf086","readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack)\n[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern — Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n- 🅰️ Typed — Easier inclusion for TypeScript projects\n- ✨ Support for NodeJS 10 and 12\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nMethods\n</h2>\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n### Event listeners\n\nThwack supports the following event types: `request`, `response`, `data`, and `error`.\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `defaults`\n\nThis allows you to read/set the default options for this instance and, in effect, any child instances.\n\nExample:\n\n```js\nthwack.defaults.baseURL = 'https://example.com/api';\n```\n\nFor an instance, `defaults` is the same object passed to `create`. For example, the following will output \"https://example.com/api\".\n\n```js\nconst instance = thwack.create({\n  baseURL: 'https://example.com/api',\n});\nconsole.log(instance.defaults.baseURL);\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `maxDepth`\n\nThe maximum level of recursive requests that can be made before Thwack throws an error. This is used to prevent a event callback from causing a recursive loop if it issues another `request` without proper guards in place. Default = 5.\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*.*`         |     `text`     |                    `response.text()`                     |\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between content types and parser types.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `*/*` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \"*/*\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could create an instance of Thwack (using `thwack.create()`) and share it throughout your entire application. Here we set the `baseURL` to our API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\n// api.js\nimport thwack from 'thwack';\n\nexport default thwack.create({\n  responseParserMap: { 'image/*': 'blob' },\n});\n```\n\nThen `import` the `api.js` file to use these options in other parts of your application. Any URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nimport api from './api';\n\nconst getBlobUrl = async (url) => {\n  const blob = (await api.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promise resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range Thwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with parent instances (if any) as well as with defaults.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch`.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parent's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\nHere is the event flow for all events. AS you can see, it is possible for your code to get into an endless loop, should your callback blindly issue a `request()` without checking to see if it's already done so, so take caution.\n\n![thwack events](https://user-images.githubusercontent.com/887639/79867660-aee3ce00-83ac-11ea-94fd-4078c1a36244.png)\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n### The `data` event\n\nThe event is fired after the body is streamed and parsed. It is fired only if the fetch returned a 2xx status code. Listeners will receive a `ThwackDataEvent` object with a `thwackResponse` key set to the response.\n\n### The `error` event\n\nThe event is fired after the body is streamed and parsed. It is fired if the fetch returned a non-2xx status code. Listeners will receive a `ThwackErrorEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nNodeJS\n</h2>\n\nThwack will work on NodeJS, but requires a polyfill for `window.fetch`. Luckily, there is a wonderful polyfill called [`node-fetch`](https://github.com/node-fetch/node-fetch) that you can use.\n\nIf you are using NodeJS version 10, you will also need a polyfill for `Array#flat` and `Object#fromEntries`. NodeJS version 11+ has these methods and does not require a polyfill.\n\nYou can either provide these polyfills yourself, or use one of the following convenience imports instead. If you are running NodeJS 11+, use:\n\n```js\nimport thwack from 'thwack/node'; // NodeJS version 11+\n```\n\nIf you are running on NodeJS 10, use:\n\n```js\nimport thwack from 'thwack/node10'; // NodeJS version 10\n```\n\n> Note: The `responseType` of `blob` is not supported on NodeJS.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nHow to\n</h2>\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options:\n\n```js\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\nThe `status` in the `ThwackResponse` defaults to 200, so unless you need to mock a non-OK response, you only need to return `data`.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n    // tells Thwack to use the returned value instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above, the caller's request\n    // will be resolved to this `ThwackResponse` (defaults to status of 200)\n    return new thwack.ThwackResponse({\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      },\n    });\n  }\n});\n```\n\n### Convert DTO to Model\n\nOften it is desirable to convert a DTO (Data Transfer Object) into something easier to consume by the client. In this example below, we convert a complex DTO into `firstName`, `lastName`, `avatar`, and `email`. Other data elements that are returned from the API call are ignored.\n\nYou can see an example of DTO conversion, logging, and returning fake data in this sample app.\n\n<div>\n<img src=\"https://user-images.githubusercontent.com/887639/79865700-94f4bc00-83a9-11ea-9de6-9f204d5857c8.png\" width=\"500\" height=\"526\" alt=\"Mickey Mouse sample app\">\n</div>\n\nYou can [view the source code](https://codesandbox.io/s/sharp-chatelet-mje3w?file=/src/App.js) on CodeSandbox.\n\n### Load an Image as a Blob\n\nIn this example, we have a React Hook that loads an image as a Blob URL. It caches the URL to Blob URL mapping in session storage. Once loaded, any refresh of the page will instantaneously load the image from Blob URL.\n\n```js\nconst useBlobUrl = (imageUrl) => {\n  const [objectURL, setObjectURL] = useState('');\n\n  useEffect(() => {\n    let url = sessionStorage.getItem(imageUrl);\n\n    async function fetchData() {\n      if (!url) {\n        const { data } = await thwack.get(imageUrl, {\n          responseType: 'blob',\n        });\n        url = URL.createObjectURL(data);\n        sessionStorage.setItem(imageUrl, url);\n      }\n      setObjectURL(url);\n    }\n\n    fetchData();\n  }, [imageUrl]);\n\n  return objectURL;\n};\n```\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRight now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint o a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nLicense\n</h2>\n\nLicensed under [MIT](LICENSE)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nContributors ✨\n</h2>\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://kojikanao.netlify.com/\"><img src=\"https://avatars0.githubusercontent.com/u/474225?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Koji</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/tomByrer\"><img src=\"https://avatars2.githubusercontent.com/u/1308419?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tom Byrer</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=tomByrer\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://iansutherland.ca/\"><img src=\"https://avatars2.githubusercontent.com/u/433725?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ian Sutherland</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=iansu\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https:///www.blakeyoder.com\"><img src=\"https://avatars0.githubusercontent.com/u/5393338?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Blake Yoder</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=blakeyoder\" title=\"Code\">💻</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.2.5-beta.2","_nodeVersion":"10.19.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-aJy19JtccSPPrM/nQrAK8MLBvjsSXwGYsN3wSIaazF7yMEQJWhxssWhXUjU9rtQwCpyIfM1GdllShA6mFXJ+sw==","shasum":"ebdd7ccaa32e701b0c727b75f42fc259529cc425","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.2.5-beta.2.tgz","fileCount":12,"unpackedSize":127312,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeoG6lCRA9TVsSAnZWagAAZ3oP/jyVaoFnPI800GhWDwFT\ntL1Vhr4Gsnh2YdQsRYaPp9iVFUeEQCVEAv3XtbLAsW2gaFGJhGbVZ47zp8tW\nFAUXaGyXL3vOm4X8dx4gy17xSjzZfgcfYX7Swql7SVnagAxBrTHz4Erfly6Y\nzGHAWSsB4okZimjHScHlZxpz9uBln2hC/XSIEJcTBNna7rZAXIVBHMQ295Rp\n/QdoJTywsoCIZeN4iJXLH4YsZ+0HoWrjv3gAhGAG/2lrttOdx32CFTU3tjn0\nDbTm7ftl2ApjE0wAmpcHltogFMVvus2giQeH6jhh35/8hRRdCEC1NAlakFGf\nB88rlLgKTRzjKCmlvHvatmNZW1Wk9fMwrdQxiZPNR3sQVDSrfVvQ8gnk1vsv\n9MZesXOq4FzFFEAuLM82Z/xOu0QXoKLXMoh/3Rv1zCiDdyI3pJxNay19VhRD\nIt7gSP6Pm0JGsqVHC9HMLdylZ9Wh9DLeFFqx5t0D/XrIbobjBb/5GibX/hOt\nIHO+jfLmiRo4SqotltoRjgSlPnzvEWB3xAAZR593jk1ybtjK1snnIipTbndu\nRl9PLSLyOmg0+Z3MEsxz2TamJJksIARaeKbEZZAj9q1O28Zyx6amgpXAdGxJ\nC0RUVx665r7vcFhH+WdrJ+K9RHsV/aETrt7TWqzFdkJj4z3XK613SBg27fS7\nNenX\r\n=EyLx\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIAtPeNRjTg1SANa+kOMyLTKiOhQL+fC5/d8em/hmUyhnAiBVwYhlSMtQ6Hd5CMWJ95xX2Qi+BVBvFhOJLX4Yl4cISA=="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.2.5-beta.2_1587572388116_0.25223704368167144"},"_hasShrinkwrap":false},"0.2.5-beta.3":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.2.5-beta.3","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js","!**/__test_utils__/**"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"d18b260aab5a34c1127258704105eb941d0cf086","readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack)\n[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern — Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n- 🅰️ Typed — Easier inclusion for TypeScript projects\n- ✨ Support for NodeJS 10 and 12\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nMethods\n</h2>\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n### Event listeners\n\nThwack supports the following event types: `request`, `response`, `data`, and `error`.\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `defaults`\n\nThis allows you to read/set the default options for this instance and, in effect, any child instances.\n\nExample:\n\n```js\nthwack.defaults.baseURL = 'https://example.com/api';\n```\n\nFor an instance, `defaults` is the same object passed to `create`. For example, the following will output \"https://example.com/api\".\n\n```js\nconst instance = thwack.create({\n  baseURL: 'https://example.com/api',\n});\nconsole.log(instance.defaults.baseURL);\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `maxDepth`\n\nThe maximum level of recursive requests that can be made before Thwack throws an error. This is used to prevent a event callback from causing a recursive loop if it issues another `request` without proper guards in place. Default = 5.\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*.*`         |     `text`     |                    `response.text()`                     |\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between content types and parser types.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `*/*` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \"*/*\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could create an instance of Thwack (using `thwack.create()`) and share it throughout your entire application. Here we set the `baseURL` to our API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\n// api.js\nimport thwack from 'thwack';\n\nexport default thwack.create({\n  responseParserMap: { 'image/*': 'blob' },\n});\n```\n\nThen `import` the `api.js` file to use these options in other parts of your application. Any URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nimport api from './api';\n\nconst getBlobUrl = async (url) => {\n  const blob = (await api.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promise resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range Thwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with parent instances (if any) as well as with defaults.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch`.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parent's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\nHere is the event flow for all events. AS you can see, it is possible for your code to get into an endless loop, should your callback blindly issue a `request()` without checking to see if it's already done so, so take caution.\n\n![thwack events](https://user-images.githubusercontent.com/887639/79867660-aee3ce00-83ac-11ea-94fd-4078c1a36244.png)\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n### The `data` event\n\nThe event is fired after the body is streamed and parsed. It is fired only if the fetch returned a 2xx status code. Listeners will receive a `ThwackDataEvent` object with a `thwackResponse` key set to the response.\n\n### The `error` event\n\nThe event is fired after the body is streamed and parsed. It is fired if the fetch returned a non-2xx status code. Listeners will receive a `ThwackErrorEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nNodeJS\n</h2>\n\nThwack will work on NodeJS, but requires a polyfill for `window.fetch`. Luckily, there is a wonderful polyfill called [`node-fetch`](https://github.com/node-fetch/node-fetch) that you can use.\n\nIf you are using NodeJS version 10, you will also need a polyfill for `Array#flat` and `Object#fromEntries`. NodeJS version 11+ has these methods and does not require a polyfill.\n\nYou can either provide these polyfills yourself, or use one of the following convenience imports instead. If you are running NodeJS 11+, use:\n\n```js\nimport thwack from 'thwack/node'; // NodeJS version 11+\n```\n\nIf you are running on NodeJS 10, use:\n\n```js\nimport thwack from 'thwack/node10'; // NodeJS version 10\n```\n\n> Note: The `responseType` of `blob` is not supported on NodeJS.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nHow to\n</h2>\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options:\n\n```js\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\nThe `status` in the `ThwackResponse` defaults to 200, so unless you need to mock a non-OK response, you only need to return `data`.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n    // tells Thwack to use the returned value instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above, the caller's request\n    // will be resolved to this `ThwackResponse` (defaults to status of 200)\n    return new thwack.ThwackResponse({\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      },\n    });\n  }\n});\n```\n\n### Convert DTO to Model\n\nOften it is desirable to convert a DTO (Data Transfer Object) into something easier to consume by the client. In this example below, we convert a complex DTO into `firstName`, `lastName`, `avatar`, and `email`. Other data elements that are returned from the API call are ignored.\n\nYou can see an example of DTO conversion, logging, and returning fake data in this sample app.\n\n<div>\n<img src=\"https://user-images.githubusercontent.com/887639/79865700-94f4bc00-83a9-11ea-9de6-9f204d5857c8.png\" width=\"500\" height=\"526\" alt=\"Mickey Mouse sample app\">\n</div>\n\nYou can [view the source code](https://codesandbox.io/s/sharp-chatelet-mje3w?file=/src/App.js) on CodeSandbox.\n\n### Load an Image as a Blob\n\nIn this example, we have a React Hook that loads an image as a Blob URL. It caches the URL to Blob URL mapping in session storage. Once loaded, any refresh of the page will instantaneously load the image from Blob URL.\n\n```js\nconst useBlobUrl = (imageUrl) => {\n  const [objectURL, setObjectURL] = useState('');\n\n  useEffect(() => {\n    let url = sessionStorage.getItem(imageUrl);\n\n    async function fetchData() {\n      if (!url) {\n        const { data } = await thwack.get(imageUrl, {\n          responseType: 'blob',\n        });\n        url = URL.createObjectURL(data);\n        sessionStorage.setItem(imageUrl, url);\n      }\n      setObjectURL(url);\n    }\n\n    fetchData();\n  }, [imageUrl]);\n\n  return objectURL;\n};\n```\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRight now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint o a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nLicense\n</h2>\n\nLicensed under [MIT](LICENSE)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nContributors ✨\n</h2>\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://kojikanao.netlify.com/\"><img src=\"https://avatars0.githubusercontent.com/u/474225?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Koji</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/tomByrer\"><img src=\"https://avatars2.githubusercontent.com/u/1308419?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tom Byrer</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=tomByrer\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://iansutherland.ca/\"><img src=\"https://avatars2.githubusercontent.com/u/433725?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ian Sutherland</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=iansu\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https:///www.blakeyoder.com\"><img src=\"https://avatars0.githubusercontent.com/u/5393338?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Blake Yoder</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=blakeyoder\" title=\"Code\">💻</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.2.5-beta.3","_nodeVersion":"10.19.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-G5Vyn+fh7/bTPOYe1XYRTvXSXF+v8gyso+xxUa7KzfLte8Gy5BDXA3Cya8Fd1x2qFguVqExh8dxWpMh3FNDpKQ==","shasum":"110d5917e16d06906abf7f9795fdc1c6522194d0","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.2.5-beta.3.tgz","fileCount":12,"unpackedSize":127725,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeoH15CRA9TVsSAnZWagAALu0P/RLoVAuwdPBMojfVnQEx\nf/WOaB9vpxiM5TY6qy4AQezlmf3t3sI6h92LMqpgYXRZDhVxIgRqCYuPgx1o\nuxWSI3y/gL8tNHoMhzF9UsHtx9kZEvXLpNCvObL5R3z1C27lczVtHOkE4zl1\nWclBDP/fVtyKuXTUrgoCBrTDzYY2808iWa+QRqL259RAKxxyAtX2MoJPQkhP\nniWOobjfgpFQVPBbjJDkdGk7vAvkuvHIMwcsUuqWcfYiR3cu3rPPOeg+WP9t\nrgJF4u3AaiQzOMlYTXUKsXzB/5P82EyP6cdYF02PWy/BTTC7kTjN2yI7bWYx\nYUWhiHjxZ8CiSlP3qljqAjzW35H1kc6VQ23TZxYKw33uN1x4r09NXMZTU3QT\nDwusVvTt0HPz/pkCN8+Q2J69iJPfmsdyPtYzLxjo7GYN6ZFjZvBaYY3PPZAy\nTBDotlQhtDTvBJztc71XakX6cYKIcZobvgFz6Nx0NdhzlN8SA2sNDtgVYdmx\nDxKmtKyrJg3LT4KDRgDkD5aaUvLgWXkCdEcaco0veX7b+CGUnxnL7XihzTqQ\nYEk+BS9uA8Ws0UHpprUdncCi6q4rt21IeuLBv5fHZF7wj+BDCXX4e6RMqlrp\nJteYUQMJEKsTPbl7oE6kKR5VxS0GV2eehHrvjqNQplGF78dSDNKGiir02hyD\n3nlP\r\n=Ke2I\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIDnF0FV8bM5xG+vXkPSHVVtV2x3AYO7sGsXPkrbCMkWVAiBPLeZkrt/2pPqJhaWHZ3e3TJjTYY+sqKIYrLHUkfycBw=="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.2.5-beta.3_1587576184644_0.7867427348595484"},"_hasShrinkwrap":false},"0.3.0":{"name":"thwack","publishConfig":{"access":"public","tag":"latest"},"version":"0.3.0","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js","!**/__test_utils__/**"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"3c87b480da2c5ab301945f2ff865552e71513593","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.3.0","_nodeVersion":"10.20.1","_npmVersion":"6.14.4","dist":{"integrity":"sha512-VcV2HzP6WA76IA4YFV0z0fyRolbgaMJMaDj1srGYZLxXP+D4bhmeF+D/6X9dKwdDM0B21BGXR+qWDOPXfaN7WA==","shasum":"f076c1b8fea6c6c3400d7c7a642278785ef87cc9","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.3.0.tgz","fileCount":12,"unpackedSize":126349,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeoIXCCRA9TVsSAnZWagAAzsAP/15CAgkWGHKnB5nmtRRL\nKCEScfqgeppssHkz94qKE4D0z+GIwSRiM59o3xAECV5NN9qmSeo82kbOu4/X\nEgWeqUtkjsE6S6UZXs+vuiQP7y0+vOgVD7gN7F+D05QpHQ3W19XvHlIYOpCw\nNWlA6wKT3Jl2f1hbUhMTLLmhwlKUGNG+y7myxXTIgGYV9elgDMnAG3pSExoN\nbc5TphS0von7U3soONqprEGPlZ/tXIAyG76xirOW1rDqUP+VFI9qRZPn7udv\npqXNbePrqJVuRG3Qex0ABFTy+dT8ecysC7IeiMHuwYkBpQOEfNvfxoFDd9UC\n5trfJKac0HG/4o1wXa2k7TYZEKsQTQ94j4U8tl1dOl5aoEsKd31I4tedtBM5\ndC0G/zwitwgq1A4xbRy1Iydk1K/8H3PFYVduzddfU42nPTiOdsIyYLf6dENt\nR2tE8yszSR4tewGCSaWjnyF+/DyBlkd+watIrEIrL2FW6Bt3RbFNCniC9b8Y\nu/LJTemqxmK4jOVBe2Clz86h+QcYiL0ZR8zqsLPK47xpcC8poAan8KXOCI5X\n6udRqRyd0/K+dFn97KCr4VC8COz9YrD86R4ofFW43f+vuJCm/N6cYymzE44Y\n6njAR4QTALsu98A5jXuf3huXxePrMvbgIfXbqI/ZsZNRRnv2vcaaouHWgxsh\ngTxR\r\n=NWk9\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIHKm88aZ9Oq8G70nduHNa1KJJQgfQPBKECStVc/rYJk9AiEA2wIvRkiTB2sBg7lSBHT0BBgDEVlxwX1vhzhDgwl407U="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.3.0_1587578306286_0.060770074718468736"},"_hasShrinkwrap":false},"0.3.1":{"name":"thwack","publishConfig":{"access":"public","tag":"latest"},"version":"0.3.1","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js","!**/__test_utils__/**"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"4bc1896941e9abec8f44613302188e22e53ab1b9","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.3.1","_nodeVersion":"10.20.1","_npmVersion":"6.14.4","dist":{"integrity":"sha512-soaMHKnOpT+U5f3fuQdnyxE1wuBb52u6y/Iw7EDGh32Eev3LsOyqf+KOIJCs4++80GHhDd7pRRyz8DLuI5NtDA==","shasum":"ed4f007b5d6a49157c1234ffc5c33294265f2592","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.3.1.tgz","fileCount":14,"unpackedSize":126423,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeoLDECRA9TVsSAnZWagAA/EgP/RA6u74xI3rdmVdpQUte\naWV1Y2XjCe9bcz928WpDdhg2O3VbRgQNsqD8j1tUPqYJX/F77MeSlr5Ok2AK\nCOYi49K6foI8JFSJtdE7N0tHda0dJ0lN2YQ11FqI26fLXQhR3WueEP1GMCjV\nC753E+8yVVr7VMV3i9LuCZAWvu6eS8VkjNXov06FxbYQil4sAWUVoVC5c6JA\nIWFKJtwIiCVXh/+h0JN+GfQeVSn7Z2V6Lg9iGOzzwWPP7oAMGwpYfJzkuLa9\nRsifl2zLjpDcbFRHLeaVRULyw+o18qEuvvOWGbSnTQh2vPUOVc2I/q31Tu+5\nZBB57I3xvKkGBmB0diYF4/F/N3H2gR76wOBm6ZJrKTM2TelWBjZ4l4lVVZAB\nNexDyTKPJj+kQy6gdu/x2aMj/arCIlBPtaB6m+L79sRWCKGrPK0M2w0VyDWp\n+zlXEmUJ/ZxBp2PLhoG8FaoxbzJ1x4pBBIXpuRgy7HJ6p7IpdKNZ5VA2iaIm\n0jbZZhuS+2418l+M9sq8++2pG2Jok5cV8cY6NGV6T36ryeJXfbCUGBPHqbR7\nIkzpHKdXMxdp/CJHHn69VjSZ9eXlWbQqaMJ7l6u99adfPPbowZWCrLyHW7mQ\norDqCHI2LrDyiEOBpOnEdKBU0kRbWUP8wwpJ+fi3ZNpLedWP7U/iEqUKtrJa\n23tI\r\n=4lwJ\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDNUl6MoE99CjghHUfGVjRjWzpDAT6ahSd7O756RDmd1AIgdN+gPvqB7jngi1c81Ju8mJaNfL+WH7/l1IF1kOn/M/Q="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.3.1_1587589316420_0.3429857579735567"},"_hasShrinkwrap":false},"0.4.0-beta.1":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.4.0-beta.1","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js","!**/__test_utils__/**"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack)\n[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern — Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n- 🅰️ Typed — Easier inclusion for TypeScript projects\n- ✨ Support for NodeJS 10 and 12\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nMethods\n</h2>\n\nThe following methods are available on all Thwack instances.\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n### Event listeners\n\nThwack supports the following event types: `request`, `response`, `data`, and `error`.\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nStatic Methods\n</h2>\n\n### Concurrency\n\nThwack has the following helper functions for making simultaneous requests. They are mostly for Axios compatibility. See the \"[How To](#how-to)\" section below for example usage.\n\n- `thwack.all(Promise<ThwackResponse>[])`\n\n- `thwack.spread(callback<results>)`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `defaults`\n\nThis allows you to read/set the default options for this instance and, in effect, any child instances.\n\nExample:\n\n```js\nthwack.defaults.baseURL = 'https://example.com/api';\n```\n\nFor an instance, `defaults` is the same object passed to `create`. For example, the following will output \"https://example.com/api\".\n\n```js\nconst instance = thwack.create({\n  baseURL: 'https://example.com/api',\n});\nconsole.log(instance.defaults.baseURL);\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `maxDepth`\n\nThe maximum level of recursive requests that can be made before Thwack throws an error. This is used to prevent a event callback from causing a recursive loop if it issues another `request` without proper guards in place. Default = 5.\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*.*`         |     `text`     |                    `response.text()`                     |\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between content types and parser types.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `*/*` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \"*/*\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could create an instance of Thwack (using `thwack.create()`) and share it throughout your entire application. Here we set the `baseURL` to our API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\n// api.js\nimport thwack from 'thwack';\n\nexport default thwack.create({\n  responseParserMap: { 'image/*': 'blob' },\n});\n```\n\nThen `import` the `api.js` file to use these options in other parts of your application. Any URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nimport api from './api';\n\nconst getBlobUrl = async (url) => {\n  const blob = (await api.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promise resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range Thwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with parent instances (if any) as well as with defaults.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch`.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parent's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\nHere is the event flow for all events. AS you can see, it is possible for your code to get into an endless loop, should your callback blindly issue a `request()` without checking to see if it's already done so, so take caution.\n\n![thwack events](https://user-images.githubusercontent.com/887639/79867660-aee3ce00-83ac-11ea-94fd-4078c1a36244.png)\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n### The `data` event\n\nThe event is fired after the body is streamed and parsed. It is fired only if the fetch returned a 2xx status code. Listeners will receive a `ThwackDataEvent` object with a `thwackResponse` key set to the response.\n\n### The `error` event\n\nThe event is fired after the body is streamed and parsed. It is fired if the fetch returned a non-2xx status code. Listeners will receive a `ThwackErrorEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nNodeJS\n</h2>\n\nThwack will work on NodeJS, but requires a polyfill for `window.fetch`. Luckily, there is a wonderful polyfill called [`node-fetch`](https://github.com/node-fetch/node-fetch) that you can use.\n\nIf you are using NodeJS version 10, you will also need a polyfill for `Array#flat` and `Object#fromEntries`. NodeJS version 11+ has these methods and does not require a polyfill.\n\nYou can either provide these polyfills yourself, or use one of the following convenience imports instead. If you are running NodeJS 11+, use:\n\n```js\nimport thwack from 'thwack/node'; // NodeJS version 11+\n```\n\nIf you are running on NodeJS 10, use:\n\n```js\nimport thwack from 'thwack/node10'; // NodeJS version 10\n```\n\n> Note: The `responseType` of `blob` is not supported on NodeJS.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nHow to\n</h2>\n\n### Multiple concurrent requests\n\nYou can use `thwack.all()` and `thwack.spread()` to make simultaneous requests. Data is then presented to your callback as one array.\n\nHere we display information for two GitHub users.\n\n```js\nfunction displayGitHubUsers() {\n  return thwack\n    .all([\n      thwack.get('https://api.github.com/users/donavon'),\n      thwack.get('https://api.github.com/users/revelcw'),\n    ])\n    .then(\n      thwack.spread((...results) => {\n        const output = results\n          .map(\n            ({ data }) => `${data.login} has ${data.public_repos} public repos`\n          )\n          .join('\\n');\n        console.log(output);\n      })\n    );\n}\n```\n\nNote the these are simply helper functions. If you are using `async`/`await` you can write this without the Thwack helpers using `Promise.all`.\n\n```js\nasync function displayGitHubUsers() {\n  const results = await Promise.all([\n    thwack.get('https://api.github.com/users/donavon'),\n    thwack.get('https://api.github.com/users/revelcw'),\n  ]);\n  const output = results\n    .map(({ data }) => `${data.login} has ${data.public_repos} public repos`)\n    .join('\\n');\n  console.log(output);\n}\n```\n\nYou can see this running live in the [CodeSandbox](https://codesandbox.io/s/thwack-allspread-demo-zx2nt?file=/src/index.js:140-642).\n\n(Demo inspired by [this blob post](https://blog.logrocket.com/how-to-make-http-requests-like-a-pro-with-axios/) on axios/fetch)\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options:\n\n```js\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\nThe `status` in the `ThwackResponse` defaults to 200, so unless you need to mock a non-OK response, you only need to return `data`.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n    // tells Thwack to use the returned value instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above, the caller's request\n    // will be resolved to this `ThwackResponse` (defaults to status of 200)\n    return new thwack.ThwackResponse({\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      },\n    });\n  }\n});\n```\n\n### Convert DTO to Model\n\nOften it is desirable to convert a DTO (Data Transfer Object) into something easier to consume by the client. In this example below, we convert a complex DTO into `firstName`, `lastName`, `avatar`, and `email`. Other data elements that are returned from the API call are ignored.\n\nYou can see an example of DTO conversion, logging, and returning fake data in this sample app.\n\n<div>\n<img src=\"https://user-images.githubusercontent.com/887639/79865700-94f4bc00-83a9-11ea-9de6-9f204d5857c8.png\" width=\"500\" height=\"526\" alt=\"Mickey Mouse sample app\">\n</div>\n\nYou can [view the source code](https://codesandbox.io/s/sharp-chatelet-mje3w?file=/src/App.js) on CodeSandbox.\n\n### Load an Image as a Blob\n\nIn this example, we have a React Hook that loads an image as a Blob URL. It caches the URL to Blob URL mapping in session storage. Once loaded, any refresh of the page will instantaneously load the image from Blob URL.\n\n```js\nconst useBlobUrl = (imageUrl) => {\n  const [objectURL, setObjectURL] = useState('');\n\n  useEffect(() => {\n    let url = sessionStorage.getItem(imageUrl);\n\n    async function fetchData() {\n      if (!url) {\n        const { data } = await thwack.get(imageUrl, {\n          responseType: 'blob',\n        });\n        url = URL.createObjectURL(data);\n        sessionStorage.setItem(imageUrl, url);\n      }\n      setObjectURL(url);\n    }\n\n    fetchData();\n  }, [imageUrl]);\n\n  return objectURL;\n};\n```\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRight now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint o a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nLicense\n</h2>\n\nLicensed under [MIT](LICENSE)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nContributors ✨\n</h2>\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://kojikanao.netlify.com/\"><img src=\"https://avatars0.githubusercontent.com/u/474225?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Koji</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/tomByrer\"><img src=\"https://avatars2.githubusercontent.com/u/1308419?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tom Byrer</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=tomByrer\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://iansutherland.ca/\"><img src=\"https://avatars2.githubusercontent.com/u/433725?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ian Sutherland</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=iansu\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https:///www.blakeyoder.com\"><img src=\"https://avatars0.githubusercontent.com/u/5393338?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Blake Yoder</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=blakeyoder\" title=\"Code\">💻</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","gitHead":"80822ddc48d2ed482a0ee27bfb712d41dc9d09dd","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.4.0-beta.1","_nodeVersion":"10.19.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-oXFyxVNwLEe3VN3epTfu6Sl0GE+GEVjIRaiqi3rShc26WBylsR9680M9gnzRSJtz45AiEO2TRfngGhbk6CmUZw==","shasum":"3631878f26fdfa6c54e0800071ca040e6cada4c2","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.4.0-beta.1.tgz","fileCount":14,"unpackedSize":130160,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeoTZtCRA9TVsSAnZWagAAMMcQAKPj/a/c8mVu8ehn3rlv\nanNaeo9VMl55Dj+gokC6xS1fGzgjX2PqKD8n16gj0Nf16BxgaeEr3BVkraVg\nJm6rB76GMeUtjD2bPzaucFreC1DtWoEl7zCfXV90b82BaDgPTAPPTsPQjies\n/SL4BTeD6ijOTRbLyy2U7kCqiZ/HgIn0rArE3zmIkWl3TSeHniU6no42IfUl\nAgDY3rPKFI6i92sAHYVRm5en6KhxJlWlNbpPdJrudB3Ibqx3ENa7I6vfszGf\nZP6eziluHStbQKNrbATycrzGXHmP209HheVV7Fk5fTqedmeNtmxAOKdj088Z\nyYGciUunrQ5A7Y7ZVVaM4+4UT6i52g+VNkDF53kT3Lb6prXZVB05/uuEXuyR\njXzEyrq6JdpddjbWyzbCG8PGjTK0IjLcYDidMoVWSDAPbdFubkXKqfN+kfKL\nH4EFPys7HVoS6/LULrxPcXwR7B0VoCZbwKveHQPu8vnf6Xt9zK0rFnZMY9UE\nyN+TPwUPVBBGaJRXGSBUiSMsU63ARgDbgdZRyKJF63YUW4/Rjf/zwNxihwCl\ng9o0fhTGT9xrP4vIv5hTjbM7jPpBfKpYDT4Si1Zw+T7hR78RDw0GSBC8/6x8\ne2Rd2h/qA+mbNY5rOHfM6RUOXuM8LV5pON8QcfWCm9CqwTmB2WczfB+77H2U\nWYQf\r\n=lkEz\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCoBwcqXyTRO1Gwz0Jv7GfOoJLK8Lzjf++qpW5F7W37/gIgBHGxud89qVJRSfeO7vOO0ak9LguRUQj9IQHyjunv4P4="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.4.0-beta.1_1587623533332_0.18382873787727627"},"_hasShrinkwrap":false},"0.4.0":{"name":"thwack","publishConfig":{"access":"public","tag":"latest"},"version":"0.4.0","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js","!**/__test_utils__/**"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"fff13fd399ae5bc0e712511930cf20fddddc3385","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.4.0","_nodeVersion":"10.20.1","_npmVersion":"6.14.4","dist":{"integrity":"sha512-A0niB70WbNxZTKOVVp3XfQI9Vnctehse4UBtHNb0tdPiVfbEQ29xNdZ7uBmnBq9ig/bXvGdvArc7lXWnr1ZElA==","shasum":"feb5d85f61c8a1b612badc971b720245b2b269cd","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.4.0.tgz","fileCount":14,"unpackedSize":130155,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeoa9pCRA9TVsSAnZWagAA5CAP/jDJ2McK3qucrpNpM3TD\nCWDG9TCxPDygSD0zjDFhWqxJful6riOuxYjNgZ50ESmUiZ/jBRH9bFnAa899\nxgOLP955/tZ+/j2KTFEkB6Eh5HTmc2p2ojuBiVFA+8v96W7BFjWbrcSp1IOo\nFQanRIGQ9kGDCsXDR+nyDJisHLe1tpz7AX+zMlCI7EDbtcirolP+oV9a5DXS\nSwaZk2naLRnjiAVD3cXJ1J/0mQLsEzUKnRsI9qyPAd8oHe+aMxH0Gzx8Sk5r\n4PZlRcraIpwj/8Rw07oMfkWnWxVfAncBbJVoOYHU40cxyJkzeP3F+4QUNCZ0\nUA3tiVeMEXpnWWKNs6ivezkna0q/kFcy3m78pcycIWViRO+OBNXZLLfNzqmk\ntSpMCcAI5YKSDKCfXGv8QWE4p0/0Yo2dOJtjd/MlkhnegvvblbpIdH9hD80I\n2qTGsnYEUHcuDDwaVW/B+/MInUJSII7yoeAiAbrEAv1G8qJOtsaF4c0qAHNE\n4XoOE7wA6pua1bCS4dBCsElyeZm+d2r4sZD+HY8f7kODwjjxDY3GWO1XgKC1\nzdag8hfzfhLIfQM2sS+j5LWy/HV2gsf7YOFoB9Iv9FOXxNAdDmCoM1yjBVAW\neIX4JBEFAtAT+FsUKqfPw30NGKhBAvw6S3/lVxjeIfdlsAGxl86Bimu+XOg0\n8qUe\r\n=J3hF\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDbAsCXr7Yv3Ey9V2D3UYUNrJVvdJ1gUN/X4mzfXJOeoQIgZRSjpl6S//6ZwXGFN2mJQ4n2rCbOD7t/U098YfQ2Uk8="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.4.0_1587654505091_0.26423523734374776"},"_hasShrinkwrap":false},"0.4.1-beta.1":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.4.1-beta.1","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js","!**/__test_utils__/**"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"fff13fd399ae5bc0e712511930cf20fddddc3385","readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack)\n[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern — Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n- 🅰️ Typed — Easier inclusion for TypeScript projects\n- ✨ Support for NodeJS 10 and 12\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nMethods\n</h2>\n\nThe following methods are available on all Thwack instances.\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n### Event listeners\n\nThwack supports the following event types: `request`, `response`, `data`, and `error`.\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nStatic Methods\n</h2>\n\n### Concurrency\n\nThwack has the following helper functions for making simultaneous requests. They are mostly for Axios compatibility. See the \"[How To](#how-to)\" section below for example usage.\n\n- `thwack.all(Promise<ThwackResponse>[])`\n\n- `thwack.spread(callback<results>)`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `defaults`\n\nThis allows you to read/set the default options for this instance and, in effect, any child instances.\n\nExample:\n\n```js\nthwack.defaults.baseURL = 'https://example.com/api';\n```\n\nFor an instance, `defaults` is the same object passed to `create`. For example, the following will output \"https://example.com/api\".\n\n```js\nconst instance = thwack.create({\n  baseURL: 'https://example.com/api',\n});\nconsole.log(instance.defaults.baseURL);\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `maxDepth`\n\nThe maximum level of recursive requests that can be made before Thwack throws an error. This is used to prevent a event callback from causing a recursive loop if it issues another `request` without proper guards in place. Default = 5.\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*.*`         |     `text`     |                    `response.text()`                     |\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between content types and parser types.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `*/*` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \"*/*\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could create an instance of Thwack (using `thwack.create()`) and share it throughout your entire application. Here we set the `baseURL` to our API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\n// api.js\nimport thwack from 'thwack';\n\nexport default thwack.create({\n  responseParserMap: { 'image/*': 'blob' },\n});\n```\n\nThen `import` the `api.js` file to use these options in other parts of your application. Any URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nimport api from './api';\n\nconst getBlobUrl = async (url) => {\n  const blob = (await api.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promise resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range Thwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with parent instances (if any) as well as with defaults.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch`.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parent's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\nHere is the event flow for all events. AS you can see, it is possible for your code to get into an endless loop, should your callback blindly issue a `request()` without checking to see if it's already done so, so take caution.\n\n![thwack events](https://user-images.githubusercontent.com/887639/79867660-aee3ce00-83ac-11ea-94fd-4078c1a36244.png)\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n### The `data` event\n\nThe event is fired after the body is streamed and parsed. It is fired only if the fetch returned a 2xx status code. Listeners will receive a `ThwackDataEvent` object with a `thwackResponse` key set to the response.\n\n### The `error` event\n\nThe event is fired after the body is streamed and parsed. It is fired if the fetch returned a non-2xx status code. Listeners will receive a `ThwackErrorEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nNodeJS\n</h2>\n\nThwack will work on NodeJS, but requires a polyfill for `window.fetch`. Luckily, there is a wonderful polyfill called [`node-fetch`](https://github.com/node-fetch/node-fetch) that you can use.\n\nIf you are using NodeJS version 10, you will also need a polyfill for `Array#flat` and `Object#fromEntries`. NodeJS version 11+ has these methods and does not require a polyfill.\n\nYou can either provide these polyfills yourself, or use one of the following convenience imports instead. If you are running NodeJS 11+, use:\n\n```js\nimport thwack from 'thwack/node'; // NodeJS version 11+\n```\n\nIf you are running on NodeJS 10, use:\n\n```js\nimport thwack from 'thwack/node10'; // NodeJS version 10\n```\n\n> Note: The `responseType` of `blob` is not supported on NodeJS.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nHow to\n</h2>\n\n### Multiple concurrent requests\n\nYou can use `thwack.all()` and `thwack.spread()` to make simultaneous requests. Data is then presented to your callback as one array.\n\nHere we display information for two GitHub users.\n\n```js\nfunction displayGitHubUsers() {\n  return thwack\n    .all([\n      thwack.get('https://api.github.com/users/donavon'),\n      thwack.get('https://api.github.com/users/revelcw'),\n    ])\n    .then(\n      thwack.spread((...results) => {\n        const output = results\n          .map(\n            ({ data }) => `${data.login} has ${data.public_repos} public repos`\n          )\n          .join('\\n');\n        console.log(output);\n      })\n    );\n}\n```\n\nNote the these are simply helper functions. If you are using `async`/`await` you can write this without the Thwack helpers using `Promise.all`.\n\n```js\nasync function displayGitHubUsers() {\n  const results = await Promise.all([\n    thwack.get('https://api.github.com/users/donavon'),\n    thwack.get('https://api.github.com/users/revelcw'),\n  ]);\n  const output = results\n    .map(({ data }) => `${data.login} has ${data.public_repos} public repos`)\n    .join('\\n');\n  console.log(output);\n}\n```\n\nYou can see this running live in the [CodeSandbox](https://codesandbox.io/s/thwack-allspread-demo-zx2nt?file=/src/index.js:140-642).\n\n(Demo inspired by [this blob post](https://blog.logrocket.com/how-to-make-http-requests-like-a-pro-with-axios/) on axios/fetch)\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options:\n\n```js\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\nThe `status` in the `ThwackResponse` defaults to 200, so unless you need to mock a non-OK response, you only need to return `data`.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n    // tells Thwack to use the returned value instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above, the caller's request\n    // will be resolved to this `ThwackResponse` (defaults to status of 200)\n    return new thwack.ThwackResponse({\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      },\n    });\n  }\n});\n```\n\n### Convert DTO to Model\n\nOften it is desirable to convert a DTO (Data Transfer Object) into something easier to consume by the client. In this example below, we convert a complex DTO into `firstName`, `lastName`, `avatar`, and `email`. Other data elements that are returned from the API call are ignored.\n\nYou can see an example of DTO conversion, logging, and returning fake data in this sample app.\n\n<div>\n<img src=\"https://user-images.githubusercontent.com/887639/79865700-94f4bc00-83a9-11ea-9de6-9f204d5857c8.png\" width=\"500\" height=\"526\" alt=\"Mickey Mouse sample app\">\n</div>\n\nYou can [view the source code](https://codesandbox.io/s/sharp-chatelet-mje3w?file=/src/App.js) on CodeSandbox.\n\n### Load an Image as a Blob\n\nIn this example, we have a React Hook that loads an image as a Blob URL. It caches the URL to Blob URL mapping in session storage. Once loaded, any refresh of the page will instantaneously load the image from Blob URL.\n\n```js\nconst useBlobUrl = (imageUrl) => {\n  const [objectURL, setObjectURL] = useState('');\n\n  useEffect(() => {\n    let url = sessionStorage.getItem(imageUrl);\n\n    async function fetchData() {\n      if (!url) {\n        const { data } = await thwack.get(imageUrl, {\n          responseType: 'blob',\n        });\n        url = URL.createObjectURL(data);\n        sessionStorage.setItem(imageUrl, url);\n      }\n      setObjectURL(url);\n    }\n\n    fetchData();\n  }, [imageUrl]);\n\n  return objectURL;\n};\n```\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRight now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint o a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nLicense\n</h2>\n\nLicensed under [MIT](LICENSE)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nContributors ✨\n</h2>\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://kojikanao.netlify.com/\"><img src=\"https://avatars0.githubusercontent.com/u/474225?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Koji</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/tomByrer\"><img src=\"https://avatars2.githubusercontent.com/u/1308419?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tom Byrer</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=tomByrer\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://iansutherland.ca/\"><img src=\"https://avatars2.githubusercontent.com/u/433725?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ian Sutherland</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=iansu\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https:///www.blakeyoder.com\"><img src=\"https://avatars0.githubusercontent.com/u/5393338?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Blake Yoder</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=blakeyoder\" title=\"Code\">💻</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.4.1-beta.1","_nodeVersion":"10.19.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-wofsi6KmosTtUlHb38/YOC4iwMSFVQ3/TL350Ee1vi5oGOR6bCWfUE8nwQB6SDr8NJ/QGTOawURauLWyi2RYHw==","shasum":"0a83d62f8febddf1411f7117cfb122f58b9ae844","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.4.1-beta.1.tgz","fileCount":14,"unpackedSize":130766,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeocEkCRA9TVsSAnZWagAAyf8P/1JAeujfRoWDwxwI96a9\nOC6buoy6twCEXYQeiySi25FbFDmDX7erKeVqb28szXjJNp4mqSgZOfi1sbcn\n8wbHC1NYKSvIQAbRY5K59jPhA8yRp7JSSaanWLWGxc6YIoVOAU8JQc3m0qn5\n3XyM/nCUVJNp1VUEDw5AI4g+0nFSW7Jaca+Hv0o8ZiHSJq6zgK+R1PyJ8/Ya\n82wXw2kFzJHevwOOnt1iE5CtT/wTb3eI4t3HcDwzK22LWPu/4yCg33GGAuQZ\nPBuI6WKtklI3TgiIz7HUahCavuFcXb3tWoqy5kRJT8iBvxDYUlYeqI24+b7F\nk7wjD5A/RwE07OgjCMQEFbbkXas6oKgm4HnK6gJ03AG9gHkjiya0/713RZy2\njkAJIDvvmSQx6UNNII2EtR2BAXKnPulFobYRB5vD8mvyM+erR9kw4sfeqzrA\ni/CLN/yGRWIqYaJPqkxn1O3aSfG2ojjwlicNUCsGRWX3gPvVXyJGP0LOIt+1\nZcBxSeqonIU/gUYcvy/Vm/y3uyzruDbyWLfdwac9eVr9pXYEcqKn0aB7dgdt\nnQZfPvHfnpQuBsNtGAr2TgsxkBmPYe6lk8/8ckHaIYZ16B1ZvXrQzS8H8CFN\nhkUMcmJvfvN2JcxEaEM6tk538V8fFjcdGbADWfVxniE/+ASOgvp+du6i7V9I\nkC+3\r\n=LXgs\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCHEFp6NMYrMrCyej2LyEok673qD4MVIbcL5J9SEL6TKQIgDxC1UPyNoRYdpU//tFHnOPZiViqYO2SvbuibXDRJTpg="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.4.1-beta.1_1587659043849_0.007776040776091175"},"_hasShrinkwrap":false},"0.4.1-beta.2":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.4.1-beta.2","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle","dev":"microbundle watch"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.3.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.3.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js","!**/__test_utils__/**"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack)\n[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern — Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n- 🅰️ Typed — Easier inclusion for TypeScript projects\n- ✨ Support for NodeJS 10 and 12\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nMethods\n</h2>\n\nThe following methods are available on all Thwack instances.\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n### Event listeners\n\nThwack supports the following event types: `request`, `response`, `data`, and `error`.\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nStatic Methods\n</h2>\n\n### Concurrency\n\nThwack has the following helper functions for making simultaneous requests. They are mostly for Axios compatibility. See the \"[How To](#how-to)\" section below for example usage.\n\n- `thwack.all(Promise<ThwackResponse>[])`\n\n- `thwack.spread(callback<results>)`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `defaults`\n\nThis allows you to read/set the default options for this instance and, in effect, any child instances.\n\nExample:\n\n```js\nthwack.defaults.baseURL = 'https://example.com/api';\n```\n\nFor an instance, `defaults` is the same object passed to `create`. For example, the following will output \"https://example.com/api\".\n\n```js\nconst instance = thwack.create({\n  baseURL: 'https://example.com/api',\n});\nconsole.log(instance.defaults.baseURL);\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `maxDepth`\n\nThe maximum level of recursive requests that can be made before Thwack throws an error. This is used to prevent a event callback from causing a recursive loop if it issues another `request` without proper guards in place. Default = 5.\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*.*`         |     `text`     |                    `response.text()`                     |\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between content types and parser types.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `*/*` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \"*/*\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could create an instance of Thwack (using `thwack.create()`) and share it throughout your entire application. Here we set the `baseURL` to our API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\n// api.js\nimport thwack from 'thwack';\n\nexport default thwack.create({\n  responseParserMap: { 'image/*': 'blob' },\n});\n```\n\nThen `import` the `api.js` file to use these options in other parts of your application. Any URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nimport api from './api';\n\nconst getBlobUrl = async (url) => {\n  const blob = (await api.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promise resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range Thwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with parent instances (if any) as well as with defaults.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch`.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parent's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\nHere is the event flow for all events. AS you can see, it is possible for your code to get into an endless loop, should your callback blindly issue a `request()` without checking to see if it's already done so, so take caution.\n\n![thwack events](https://user-images.githubusercontent.com/887639/79867660-aee3ce00-83ac-11ea-94fd-4078c1a36244.png)\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n### The `data` event\n\nThe event is fired after the body is streamed and parsed. It is fired only if the fetch returned a 2xx status code. Listeners will receive a `ThwackDataEvent` object with a `thwackResponse` key set to the response.\n\n### The `error` event\n\nThe event is fired after the body is streamed and parsed. It is fired if the fetch returned a non-2xx status code. Listeners will receive a `ThwackErrorEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nNodeJS\n</h2>\n\nThwack will work on NodeJS, but requires a polyfill for `window.fetch`. Luckily, there is a wonderful polyfill called [`node-fetch`](https://github.com/node-fetch/node-fetch) that you can use.\n\nIf you are using NodeJS version 10, you will also need a polyfill for `Array#flat` and `Object#fromEntries`. NodeJS version 11+ has these methods and does not require a polyfill.\n\nYou can either provide these polyfills yourself, or use one of the following convenience imports instead. If you are running NodeJS 11+, use:\n\n```js\nimport thwack from 'thwack/node'; // NodeJS version 11+\n```\n\nIf you are running on NodeJS 10, use:\n\n```js\nimport thwack from 'thwack/node10'; // NodeJS version 10\n```\n\n> Note: The `responseType` of `blob` is not supported on NodeJS.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nHow to\n</h2>\n\n### Multiple concurrent requests\n\nYou can use `thwack.all()` and `thwack.spread()` to make simultaneous requests. Data is then presented to your callback as one array.\n\nHere we display information for two GitHub users.\n\n```js\nfunction displayGitHubUsers() {\n  return thwack\n    .all([\n      thwack.get('https://api.github.com/users/donavon'),\n      thwack.get('https://api.github.com/users/revelcw'),\n    ])\n    .then(\n      thwack.spread((...results) => {\n        const output = results\n          .map(\n            ({ data }) => `${data.login} has ${data.public_repos} public repos`\n          )\n          .join('\\n');\n        console.log(output);\n      })\n    );\n}\n```\n\nNote the these are simply helper functions. If you are using `async`/`await` you can write this without the Thwack helpers using `Promise.all`.\n\n```js\nasync function displayGitHubUsers() {\n  const results = await Promise.all([\n    thwack.get('https://api.github.com/users/donavon'),\n    thwack.get('https://api.github.com/users/revelcw'),\n  ]);\n  const output = results\n    .map(({ data }) => `${data.login} has ${data.public_repos} public repos`)\n    .join('\\n');\n  console.log(output);\n}\n```\n\nYou can see this running live in the [CodeSandbox](https://codesandbox.io/s/thwack-allspread-demo-zx2nt?file=/src/index.js:140-642).\n\n(Demo inspired by [this blob post](https://blog.logrocket.com/how-to-make-http-requests-like-a-pro-with-axios/) on axios/fetch)\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options:\n\n```js\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\nThe `status` in the `ThwackResponse` defaults to 200, so unless you need to mock a non-OK response, you only need to return `data`.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n    // tells Thwack to use the returned value instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above, the caller's request\n    // will be resolved to this `ThwackResponse` (defaults to status of 200)\n    return new thwack.ThwackResponse({\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      },\n    });\n  }\n});\n```\n\n### Convert DTO to Model\n\nOften it is desirable to convert a DTO (Data Transfer Object) into something easier to consume by the client. In this example below, we convert a complex DTO into `firstName`, `lastName`, `avatar`, and `email`. Other data elements that are returned from the API call are ignored.\n\nYou can see an example of DTO conversion, logging, and returning fake data in this sample app.\n\n<div>\n<img src=\"https://user-images.githubusercontent.com/887639/79865700-94f4bc00-83a9-11ea-9de6-9f204d5857c8.png\" width=\"500\" height=\"526\" alt=\"Mickey Mouse sample app\">\n</div>\n\nYou can [view the source code](https://codesandbox.io/s/sharp-chatelet-mje3w?file=/src/App.js) on CodeSandbox.\n\n### Load an Image as a Blob\n\nIn this example, we have a React Hook that loads an image as a Blob URL. It caches the URL to Blob URL mapping in session storage. Once loaded, any refresh of the page will instantaneously load the image from Blob URL.\n\n```js\nconst useBlobUrl = (imageUrl) => {\n  const [objectURL, setObjectURL] = useState('');\n\n  useEffect(() => {\n    let url = sessionStorage.getItem(imageUrl);\n\n    async function fetchData() {\n      if (!url) {\n        const { data } = await thwack.get(imageUrl, {\n          responseType: 'blob',\n        });\n        url = URL.createObjectURL(data);\n        sessionStorage.setItem(imageUrl, url);\n      }\n      setObjectURL(url);\n    }\n\n    fetchData();\n  }, [imageUrl]);\n\n  return objectURL;\n};\n```\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRight now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint o a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nLicense\n</h2>\n\nLicensed under [MIT](LICENSE)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nContributors ✨\n</h2>\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://kojikanao.netlify.com/\"><img src=\"https://avatars0.githubusercontent.com/u/474225?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Koji</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/tomByrer\"><img src=\"https://avatars2.githubusercontent.com/u/1308419?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tom Byrer</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=tomByrer\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://iansutherland.ca/\"><img src=\"https://avatars2.githubusercontent.com/u/433725?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ian Sutherland</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=iansu\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https:///www.blakeyoder.com\"><img src=\"https://avatars0.githubusercontent.com/u/5393338?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Blake Yoder</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=blakeyoder\" title=\"Code\">💻</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","gitHead":"fff13fd399ae5bc0e712511930cf20fddddc3385","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.4.1-beta.2","_nodeVersion":"10.19.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-4e5XgHsEZnBmjAJ9deuPgsbZrAxyo1BcnYLJ27z2uX4xV3Ue4KMVf8Q/sacb6psvXbFMqFD0leR+nwVUVfGD4g==","shasum":"b503ecb9939b865b0802a81b37390903b879cc6f","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.4.1-beta.2.tgz","fileCount":14,"unpackedSize":133016,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeocoZCRA9TVsSAnZWagAAIrQP/AyDA3e8zxEWznBAqV5p\nWPeIrQa4405lSdCecm+CzEPeqbRuyyr4WRC1BER6jcKxDl+qbLyOFXpfIG3r\n4m5pTAgpsg9TcyMFRJuDPjmT+vnuqyaMG4g1qTbYPXBU5EdFjSObygxcghQW\nDPB4N9KK48LpV//GftLC8QbY5q+9TGmUcTuIjzgyjZ5bUuJr+E1sXQJNgZVZ\ngniji4DPVNlEBRMeb65AtnBu0pWnd+7WyVKnHthlJhJzCnYx1uXf5B6WbI7u\nx5PytbTEc0isWWVEOrRc38h7OgtvQNh5JWiifRJCd24LiHnHde3CQAuib8eS\n9MFDCXXYCvOnUE7lhqroEmZKQOjt9IGvMH5fAFMrc0Lyxssx+dpJ8Sz0HD1R\nAFPIPT0xxvWWyV+ovqrb14X5LYImgNI+/iK1tm2+9rtl/SjULoMNKEFgd2o7\n+q07rcQTGSkzhJC3VSw9LNsrsyr1ZZScMRHEMeYqH9NV4opCCz4Ou0h4/pqa\nxPagCdw8gF5W/oATeUIldL2OD+66x/+yJKHiV6ZbvOLrJ7xATdtOwAMD1MNr\nOjPxyMjIlwHEWpoZgVK/lAMleUdtSEp0+5qmUaJBleW53jzt5BbI+UC1Q3pu\nJFga0qlc7Yc4oeULf3xhHJwjGFvJ1m3VrPtdTuysakEqkGbhJ8Dddi3CZA5H\nWxMn\r\n=Pzkc\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDkp01hbSumClPhOjoRdAvRI9hqmsKpp85gCoQFzOCYgwIgXbe8JtMqACm13zC+/ojLoilzEIafbDru1TsEH4PKLx4="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.4.1-beta.2_1587661336666_0.41114016357797434"},"_hasShrinkwrap":false},"0.4.1-beta.3":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.4.1-beta.3","description":"A tiny modern data fetching solution.","main":"dist/default/thwack.js","umd:main":"dist/default/thwack.umd.js","module":"dist/default/thwack.m.js","source":"src/default/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && npm t && rimraf dist","build":"microbundle && pushd core && npm run build && popd"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise","nodejs"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.4.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.4.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js","!**/__test_utils__/**"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack)\n[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern — Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n- 🅰️ Typed — Easier inclusion for TypeScript projects\n- ✨ Support for NodeJS 10 and 12\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nMethods\n</h2>\n\nThe following methods are available on all Thwack instances.\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n### Event listeners\n\nThwack supports the following event types: `request`, `response`, `data`, and `error`.\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nStatic Methods\n</h2>\n\n### Concurrency\n\nThwack has the following helper functions for making simultaneous requests. They are mostly for Axios compatibility. See the \"[How To](#how-to)\" section below for example usage.\n\n- `thwack.all(Promise<ThwackResponse>[])`\n\n- `thwack.spread(callback<results>)`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `defaults`\n\nThis allows you to read/set the default options for this instance and, in effect, any child instances.\n\nExample:\n\n```js\nthwack.defaults.baseURL = 'https://example.com/api';\n```\n\nFor an instance, `defaults` is the same object passed to `create`. For example, the following will output \"https://example.com/api\".\n\n```js\nconst instance = thwack.create({\n  baseURL: 'https://example.com/api',\n});\nconsole.log(instance.defaults.baseURL);\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `maxDepth`\n\nThe maximum level of recursive requests that can be made before Thwack throws an error. This is used to prevent a event callback from causing a recursive loop if it issues another `request` without proper guards in place. Default = 5.\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*/*`         |     `text`     |                    `response.text()`                     |\n\n> Note: `stream` is currently unsupported in React Native due to [#27741](https://github.com/facebook/react-native/issues/27741)\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between the resulting `content-type` from the response header and the parser type.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `*/*` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \"*/*\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could create an instance of Thwack (using `thwack.create()`) and share it throughout your entire application. Here we set the `baseURL` to our API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\n// api.js\nimport thwack from 'thwack';\n\nexport default thwack.create({\n  responseParserMap: { 'image/*': 'blob' },\n});\n```\n\nThen `import` the `api.js` file to use these options in other parts of your application. Any URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nimport api from './api';\n\nconst getBlobUrl = async (url) => {\n  const blob = (await api.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promise resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range Thwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with parent instances (if any) as well as with defaults.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch` or the `response` from a synthetic event callback.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parent's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\nHere is the event flow for all events. AS you can see, it is possible for your code to get into an endless loop, should your callback blindly issue a `request()` without checking to see if it's already done so, so take caution.\n\n![thwack events](https://user-images.githubusercontent.com/887639/79867660-aee3ce00-83ac-11ea-94fd-4078c1a36244.png)\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n### The `data` event\n\nThe event is fired after the body is streamed and parsed. It is fired only if the fetch returned a 2xx status code. Listeners will receive a `ThwackDataEvent` object with a `thwackResponse` key set to the response.\n\n### The `error` event\n\nThe event is fired after the body is streamed and parsed. It is fired if the fetch returned a non-2xx status code. Listeners will receive a `ThwackErrorEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nNodeJS\n</h2>\n\nThwack will work on NodeJS, but requires a polyfill for `window.fetch`. Luckily, there is a wonderful polyfill called [`node-fetch`](https://github.com/node-fetch/node-fetch) that you can use.\n\nIf you are using NodeJS version 10, you will also need a polyfill for `Array#flat` and `Object#fromEntries`. NodeJS version 11+ has these methods and does not require a polyfill.\n\nYou can either provide these polyfills yourself, or use one of the following convenience imports instead. If you are running NodeJS 11+, use:\n\n```js\nimport thwack from 'thwack/node'; // NodeJS version 11+\n```\n\nIf you are running on NodeJS 10, use:\n\n```js\nimport thwack from 'thwack/node10'; // NodeJS version 10\n```\n\n> Note: The `responseType` of `blob` is not supported on NodeJS.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nHow to\n</h2>\n\n### Multiple concurrent requests\n\nYou can use `thwack.all()` and `thwack.spread()` to make simultaneous requests. Data is then presented to your callback as one array.\n\nHere we display information for two GitHub users.\n\n```js\nfunction displayGitHubUsers() {\n  return thwack\n    .all([\n      thwack.get('https://api.github.com/users/donavon'),\n      thwack.get('https://api.github.com/users/revelcw'),\n    ])\n    .then(\n      thwack.spread((...results) => {\n        const output = results\n          .map(\n            ({ data }) => `${data.login} has ${data.public_repos} public repos`\n          )\n          .join('\\n');\n        console.log(output);\n      })\n    );\n}\n```\n\nNote the these are simply helper functions. If you are using `async`/`await` you can write this without the Thwack helpers using `Promise.all`.\n\n```js\nasync function displayGitHubUsers() {\n  const results = await Promise.all([\n    thwack.get('https://api.github.com/users/donavon'),\n    thwack.get('https://api.github.com/users/revelcw'),\n  ]);\n  const output = results\n    .map(({ data }) => `${data.login} has ${data.public_repos} public repos`)\n    .join('\\n');\n  console.log(output);\n}\n```\n\nYou can see this running live in the [CodeSandbox](https://codesandbox.io/s/thwack-allspread-demo-zx2nt?file=/src/index.js:140-642).\n\n(Demo inspired by [this blob post](https://blog.logrocket.com/how-to-make-http-requests-like-a-pro-with-axios/) on axios/fetch)\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options:\n\n```js\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\nThe `status` in the `ThwackResponse` defaults to 200, so unless you need to mock a non-OK response, you only need to return `data`.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n    // tells Thwack to use the returned value instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above, the caller's request\n    // will be resolved to this `ThwackResponse` (defaults to status of 200)\n    return new thwack.ThwackResponse({\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      },\n    });\n  }\n});\n```\n\n### Convert DTO to Model\n\nOften it is desirable to convert a DTO (Data Transfer Object) into something easier to consume by the client. In this example below, we convert a complex DTO into `firstName`, `lastName`, `avatar`, and `email`. Other data elements that are returned from the API call are ignored.\n\nYou can see an example of DTO conversion, logging, and returning fake data in this sample app.\n\n<div>\n<img src=\"https://user-images.githubusercontent.com/887639/79865700-94f4bc00-83a9-11ea-9de6-9f204d5857c8.png\" width=\"500\" height=\"526\" alt=\"Mickey Mouse sample app\">\n</div>\n\nYou can [view the source code](https://codesandbox.io/s/sharp-chatelet-mje3w?file=/src/App.js) on CodeSandbox.\n\n### Load an Image as a Blob\n\nIn this example, we have a React Hook that loads an image as a Blob URL. It caches the URL to Blob URL mapping in session storage. Once loaded, any refresh of the page will instantaneously load the image from Blob URL.\n\n```js\nconst useBlobUrl = (imageUrl) => {\n  const [objectURL, setObjectURL] = useState('');\n\n  useEffect(() => {\n    let url = sessionStorage.getItem(imageUrl);\n\n    async function fetchData() {\n      if (!url) {\n        const { data } = await thwack.get(imageUrl, {\n          responseType: 'blob',\n        });\n        url = URL.createObjectURL(data);\n        sessionStorage.setItem(imageUrl, url);\n      }\n      setObjectURL(url);\n    }\n\n    fetchData();\n  }, [imageUrl]);\n\n  return objectURL;\n};\n```\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRight now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint o a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nLicense\n</h2>\n\nLicensed under [MIT](LICENSE)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nContributors ✨\n</h2>\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://kojikanao.netlify.com/\"><img src=\"https://avatars0.githubusercontent.com/u/474225?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Koji</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/tomByrer\"><img src=\"https://avatars2.githubusercontent.com/u/1308419?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tom Byrer</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=tomByrer\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://iansutherland.ca/\"><img src=\"https://avatars2.githubusercontent.com/u/433725?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ian Sutherland</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=iansu\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https:///www.blakeyoder.com\"><img src=\"https://avatars0.githubusercontent.com/u/5393338?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Blake Yoder</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=blakeyoder\" title=\"Code\">💻</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","gitHead":"679fa9de97c2156583584d8d20f9764fb7c1b06c","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.4.1-beta.3","_nodeVersion":"10.19.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-crnTQ+k5S8qb/pJEmlV5waFb9Mx/2KJQ98LJxFvCidYxoSZyTpKJOPKjtswyOhdKmgZ5aY45wGmXXLQXKJ4VqQ==","shasum":"ea3028cbb86f584972a917f3243a927ea2fbd135","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.4.1-beta.3.tgz","fileCount":19,"unpackedSize":229633,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeoz5VCRA9TVsSAnZWagAACoIP/3oP85sIvatGqw/xij9Q\nbSj9ZLyo6v6Z8kQRhMDgFqzSLPGExLdgeHEWIPXBYQYq7nxmUmPiND9+8kn+\n0OjaW7vGu2MYnvnSM4+hDdm2X+cP95zx26DUi5QR6YyDxGDZI1I5rMjfjJrz\n9JVjvlja9uoMpzlS3cHdlE++LJ7YFkO7Kwg1v8qieN82GuLVLRv+4ct9K6dT\nfB9UhvKVKweEYNWZCM4ejXgCpkENBAfnkzzn2pfl1OPY8TbuRtp3m1snuJGy\nPcTo1qIMZ4mbN53THwsTmVkK4wU/7VloVyoSRVEhu1yUa8+gnQC47gHZ5pCf\nJ8QsmhBTdNPpKe7y+/8ruYb9jOT3tr7FpCvzvx+/cbpu7Gx4vUnU4kvPOgCl\naWX1TTw5PFN5zlhKW3S+xw/q72ukozuf+XyHfK0Uo8myCxNzClbVHcreyTDL\nElAtkjujsXjsrdkKgfsmfwqhzW1EpXHyuDIrcMXi4+iR6jNu0/B9FwE36HQh\nmoJHS0FJXjsJJiqcu1wTjD9i+3GmjNmPjrrN6IZjkTbyZTce7vXDo4CXh2tj\nORtirA7L3/xp1Aeg1ekQHRVkb8nvc1KMbqpqlICZ+ozl7UTEinKUQJ6LzuLe\nR6/9AmZDDt3l+WVuWDUxrrG2sEp6Eu9q3DnvGrP19nM9KFOeB2MgvhJmadRx\npWm/\r\n=Mq7m\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIAqSJKy6N315Qtd6iD2c9L0ZOOzhYDoSahZjnS1vmGpWAiBeMwhyw1yoqwoKeoh4Y0Nxcl/O0VtNX+W1roVqPkdOPw=="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.4.1-beta.3_1587756629097_0.3573047394786095"},"_hasShrinkwrap":false},"0.4.1-beta.4":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.4.1-beta.4","description":"A tiny modern data fetching solution.","main":"dist/default/thwack.js","umd:main":"dist/default/thwack.umd.js","module":"dist/default/thwack.m.js","source":"src/default/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepare":"cd core && npm i && cd ..","prepublishOnly":"npm run build","lint":"eslint src","test":"npm run build-core && jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && rimraf dist && npm t","build-core":"cd core && npm run build && cd ..","build":"microbundle"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise","nodejs"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"@thwack/resolve":"^1.0.0","core-js":"^3.6.5","node-fetch":"^2.6.0","thwack-core":"file:./core"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.4.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.4.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"44d73fdd0aeb3950d9428c847d846302c199a958","readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack)\n[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern — Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n- 🅰️ Typed — Easier inclusion for TypeScript projects\n- ✨ Support for NodeJS 10 and 12\n- ✨ Support for React Native\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nMethods\n</h2>\n\nThe following methods are available on all Thwack instances.\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n### Event listeners\n\nThwack supports the following event types: `request`, `response`, `data`, and `error`.\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nStatic Methods\n</h2>\n\n### Concurrency\n\nThwack has the following helper functions for making simultaneous requests. They are mostly for Axios compatibility. See the \"[How To](#how-to)\" section below for example usage.\n\n- `thwack.all(Promise<ThwackResponse>[])`\n\n- `thwack.spread(callback<results>)`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `defaults`\n\nThis allows you to read/set the default options for this instance and, in effect, any child instances.\n\nExample:\n\n```js\nthwack.defaults.baseURL = 'https://example.com/api';\n```\n\nFor an instance, `defaults` is the same object passed to `create`. For example, the following will output \"https://example.com/api\".\n\n```js\nconst instance = thwack.create({\n  baseURL: 'https://example.com/api',\n});\nconsole.log(instance.defaults.baseURL);\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `maxDepth`\n\nThe maximum level of recursive requests that can be made before Thwack throws an error. This is used to prevent a event callback from causing a recursive loop if it issues another `request` without proper guards in place. Default = 5.\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*/*`         |     `text`     |                    `response.text()`                     |\n\n> Note: `stream` is currently unsupported in React Native due to [#27741](https://github.com/facebook/react-native/issues/27741)\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between the resulting `content-type` from the response header and the parser type.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `*/*` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \"*/*\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could set the `baseURL` to your API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\nimport thwack from 'thwack';\n\nthwack.defaults.responseParserMap = { 'image/*': 'blob' };\n```\n\nAny URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nconst getBlobUrl = async (url) => {\n  const blob = (await thwack.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n### `buildURL`\n\nThis is a function that you can provide to override the default behavior. `buildURL` takes an `options` object with `url`, and optionally `baseURL` and `params` and returns a fully qualified URL.\n\nBy providing your own implimentation, it may not be necessary for a polyfill for `URL` in React Native. See [React Native](#react-native) for more information and a basic implimentation.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promise resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range Thwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with parent instances (if any) as well as with defaults.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch` or the `response` from a synthetic event callback.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parent's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\nHere is the event flow for all events. AS you can see, it is possible for your code to get into an endless loop, should your callback blindly issue a `request()` without checking to see if it's already done so, so take caution.\n\n![thwack events](https://user-images.githubusercontent.com/887639/79867660-aee3ce00-83ac-11ea-94fd-4078c1a36244.png)\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n### The `data` event\n\nThe event is fired after the body is streamed and parsed. It is fired only if the fetch returned a 2xx status code. Listeners will receive a `ThwackDataEvent` object with a `thwackResponse` key set to the response.\n\n### The `error` event\n\nThe event is fired after the body is streamed and parsed. It is fired if the fetch returned a non-2xx status code. Listeners will receive a `ThwackErrorEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nNodeJS\n</h2>\n\nThwack will work on NodeJS, but requires a polyfill for `window.fetch`. Luckily, there is a wonderful polyfill called [`node-fetch`](https://github.com/node-fetch/node-fetch) that you can use.\n\nIf you are using NodeJS version 10, you will also need a polyfill for `Array#flat` and `Object#fromEntries`. NodeJS version 11+ has these methods and does not require a polyfill.\n\nYou can either provide these polyfills yourself, or use one of the following convenience imports instead. If you are running NodeJS 11+, use:\n\n```js\nimport thwack from 'thwack/node'; // NodeJS version 12+\n```\n\nIf you are running on NodeJS 10, use:\n\n```js\nimport thwack from 'thwack/node10'; // NodeJS version 10\n```\n\nIf you wish to provide these polyfills yourself, then to use Thwack, you must import from `thwack/core` and set `fetch` as the default for `fetch` as so.\n\n```js\nimport thwack from 'thwack/code';\nthwack.defaults.fetch = global.fetch;\n```\n\nThis should be done in your app startup code, usually `index.js`.\n\n> Note: The `responseType` of `blob` is not supported on NodeJS.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nReact Native\n</h2>\n\nThwack will work with React Native, but requires a polyfill for `URL`. We suggect [`react-native-url-polyfill`](https://github.com/charpeni/react-native-url-polyfill).\n\nYou must also import from `thwack/rn` instead of `thwack`, like this.\n\n```js\nimport thwack from 'thwack/rn';\n```\n\nHowever, if you don't need a fully compliant URL builder, say if you only hit fully qualified URLs wirth no parameters, you can use this basic implimentation.\n\nYou must also import from `thwack/core` instead of of `thwack`.\n\n```js\nimport thwack from 'thwack/core';\n\n// This is a valid implementation ONLY if you specify an\n// absolute baseURL, a relative url, and no params.\nthwack.defaults.buildURL = ({ url, baseURL = '', params }) => {\n  if (Object.getOwnPropertyNames(params).length > 0) {\n    throw new Error('buildURL: params not supported');\n  }\n\n  return `${baseURL}${url}`;\n};\n```\n\n> Note: React Native does not support `stream` due to [#27741](https://github.com/facebook/react-native/issues/27741)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nHow to\n</h2>\n\n### Multiple concurrent requests\n\nYou can use `thwack.all()` and `thwack.spread()` to make simultaneous requests. Data is then presented to your callback as one array.\n\nHere we display information for two GitHub users.\n\n```js\nfunction displayGitHubUsers() {\n  return thwack\n    .all([\n      thwack.get('https://api.github.com/users/donavon'),\n      thwack.get('https://api.github.com/users/revelcw'),\n    ])\n    .then(\n      thwack.spread((...results) => {\n        const output = results\n          .map(\n            ({ data }) => `${data.login} has ${data.public_repos} public repos`\n          )\n          .join('\\n');\n        console.log(output);\n      })\n    );\n}\n```\n\nNote the these are simply helper functions. If you are using `async`/`await` you can write this without the Thwack helpers using `Promise.all`.\n\n```js\nasync function displayGitHubUsers() {\n  const results = await Promise.all([\n    thwack.get('https://api.github.com/users/donavon'),\n    thwack.get('https://api.github.com/users/revelcw'),\n  ]);\n  const output = results\n    .map(({ data }) => `${data.login} has ${data.public_repos} public repos`)\n    .join('\\n');\n  console.log(output);\n}\n```\n\nYou can see this running live in the [CodeSandbox](https://codesandbox.io/s/thwack-allspread-demo-zx2nt?file=/src/index.js:140-642).\n\n(Demo inspired by [this blob post](https://blog.logrocket.com/how-to-make-http-requests-like-a-pro-with-axios/) on axios/fetch)\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options. \n\nIn the browser, you can use the built-in [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).\n\n```js\nimport thwack from 'thwack';\n\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn NodeJS, you can use something like [abort-controller](https://www.npmjs.com/package/abort-controller).\n\n```js\nimport thwack from 'thwack';\nimport AbortController from \"abort-controller\"\n\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\nThe `status` in the `ThwackResponse` defaults to 200, so unless you need to mock a non-OK response, you only need to return `data`.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n    // tells Thwack to use the returned value instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above, the caller's request\n    // will be resolved to this `ThwackResponse` (defaults to status of 200)\n    return new thwack.ThwackResponse({\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      },\n    });\n  }\n});\n```\n\n### Convert DTO to Model\n\nOften it is desirable to convert a DTO (Data Transfer Object) into something easier to consume by the client. In this example below, we convert a complex DTO into `firstName`, `lastName`, `avatar`, and `email`. Other data elements that are returned from the API call are ignored.\n\nYou can see an example of DTO conversion, logging, and returning fake data in this sample app.\n\n<div>\n<img src=\"https://user-images.githubusercontent.com/887639/79865700-94f4bc00-83a9-11ea-9de6-9f204d5857c8.png\" width=\"500\" height=\"526\" alt=\"Mickey Mouse sample app\">\n</div>\n\nYou can [view the source code](https://codesandbox.io/s/sharp-chatelet-mje3w?file=/src/App.js) on CodeSandbox.\n\n### Load an Image as a Blob\n\nIn this example, we have a React Hook that loads an image as a Blob URL. It caches the URL to Blob URL mapping in session storage. Once loaded, any refresh of the page will instantaneously load the image from Blob URL.\n\n```js\nconst useBlobUrl = (imageUrl) => {\n  const [objectURL, setObjectURL] = useState('');\n\n  useEffect(() => {\n    let url = sessionStorage.getItem(imageUrl);\n\n    async function fetchData() {\n      if (!url) {\n        const { data } = await thwack.get(imageUrl, {\n          responseType: 'blob',\n        });\n        url = URL.createObjectURL(data);\n        sessionStorage.setItem(imageUrl, url);\n      }\n      setObjectURL(url);\n    }\n\n    fetchData();\n  }, [imageUrl]);\n\n  return objectURL;\n};\n```\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRight now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint to a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nLicense\n</h2>\n\nLicensed under [MIT](LICENSE)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nContributors ✨\n</h2>\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://kojikanao.netlify.com/\"><img src=\"https://avatars0.githubusercontent.com/u/474225?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Koji</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/tomByrer\"><img src=\"https://avatars2.githubusercontent.com/u/1308419?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tom Byrer</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=tomByrer\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://iansutherland.ca/\"><img src=\"https://avatars2.githubusercontent.com/u/433725?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ian Sutherland</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=iansu\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https:///www.blakeyoder.com\"><img src=\"https://avatars0.githubusercontent.com/u/5393338?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Blake Yoder</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=blakeyoder\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://www.ryanhinchey.co\"><img src=\"https://avatars0.githubusercontent.com/u/3943764?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ryan Hinchey</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=ryhinchey\" title=\"Documentation\">📖</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.4.1-beta.4","_nodeVersion":"10.19.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-w85VR0YHcJXElJOzUaWmHg7LD3awzTQIr07iVyVaKWDOHW0S8v0Jusrk9SCj9vkVtwtCvW1C2osmXYcVEU8U7A==","shasum":"2b604c7731c9cc5018b01d5775dacee5ae75607c","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.4.1-beta.4.tgz","fileCount":19,"unpackedSize":140244,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJepeQ5CRA9TVsSAnZWagAAO9gQAIhTNIgC+8spp1Mwvd40\nVTycmKJrlH6qx2GyD0m12dnrIyw4B44UI+i72ykgl4i+auYdG0nJknYZOOrT\neYByk5u2D2KVC3rPMPn32CEiWQBG1dv0LVPMwjAONPOxhPLuEsU/i57qWl8G\nP5g1vyn5mKvsUz13f/7PQlbrvvZNDQDoyJPn1P3lEghVkQUORnSosuz3kCIK\nHt8qyN0cPSHqzsaTDavYzDsDC59ZS7pdfTJghrcFuR+UEg1n3mZs1h+Au9ZX\nS5ba8jrQlMOabURY7LfKAJu1/oBThULwPsb5myxfgnS4fjusecKiQRLo6EBC\nr9C+axEsJ30qVYTU8hlxiEVkLEKvEag5YvLAP13HCFVMZdKd9Kz5PHi2uPTs\n6jaWMZsUnWRhToc+pj2tQIgyh0C9lW7VKqMPRm8hN9zF9lT5A21wM9eRSXaf\nGpJhIDdFPMXVxmbGqO20GhI3egAWyvIyxC7B1ikAzdWFtt4ISvd/SJsJo+7y\n7d9Q4Qv4/yMXB9KkLzE17ILWs4mAdIQ+FNsANpvkEBCLhHn42vGPWbdKQEQh\n0GtHFHGz+8kKnpJ17FgSI7sCT2DPSoOv1t7hOd38vIO7CuiE9VXADWuCq6Ik\nYJTPcl+GB8cikNiNW+MEHOxbXQfGfPKfb1/nuYzUccFaFy8nSw2VRH4ZW/Bg\nMj7s\r\n=tl6G\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIHEO6/3V6Wn30WNXeXtg8gBul4fQaZoVpF72glEKYFJFAiEAqWP3ZvrCqfzaXBoct6EIArFmkW+nhBLR2n14R6rzqVo="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.4.1-beta.4_1587930169301_0.930098615334962"},"_hasShrinkwrap":false},"0.4.1-beta.5":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.4.1-beta.5","description":"A tiny modern data fetching solution.","main":"dist/default/thwack.js","umd:main":"dist/default/thwack.umd.js","module":"dist/default/thwack.m.js","source":"src/default/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepare":"cd core && npm i && cd ..","prepublishOnly":"npm run build","lint":"eslint src","test":"npm run build-core && jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && rimraf dist && npm t","build-core":"cd core && npm run build && cd ..","build":"microbundle"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise","nodejs"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"@thwack/resolve":"^1.0.0","core-js":"^3.6.5","node-fetch":"^2.6.0","thwack-core":"file:./core"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.4.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.4.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack)\n[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern — Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n- 🅰️ Typed — Easier inclusion for TypeScript projects\n- ✨ Support for NodeJS 10 and 12\n- ✨ Support for React Native\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nMethods\n</h2>\n\nThe following methods are available on all Thwack instances.\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n### Event listeners\n\nThwack supports the following event types: `request`, `response`, `data`, and `error`.\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nStatic Methods\n</h2>\n\n### Concurrency\n\nThwack has the following helper functions for making simultaneous requests. They are mostly for Axios compatibility. See the \"[How To](#how-to)\" section below for example usage.\n\n- `thwack.all(Promise<ThwackResponse>[])`\n\n- `thwack.spread(callback<results>)`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `defaults`\n\nThis allows you to read/set the default options for this instance and, in effect, any child instances.\n\nExample:\n\n```js\nthwack.defaults.baseURL = 'https://example.com/api';\n```\n\nFor an instance, `defaults` is the same object passed to `create`. For example, the following will output \"https://example.com/api\".\n\n```js\nconst instance = thwack.create({\n  baseURL: 'https://example.com/api',\n});\nconsole.log(instance.defaults.baseURL);\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `maxDepth`\n\nThe maximum level of recursive requests that can be made before Thwack throws an error. This is used to prevent a event callback from causing a recursive loop if it issues another `request` without proper guards in place. Default = 5.\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*/*`         |     `text`     |                    `response.text()`                     |\n\n> Note: `stream` is currently unsupported in React Native due to [#27741](https://github.com/facebook/react-native/issues/27741)\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between the resulting `content-type` from the response header and the parser type.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `*/*` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \"*/*\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could set the `baseURL` to your API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\nimport thwack from 'thwack';\n\nthwack.defaults.responseParserMap = { 'image/*': 'blob' };\n```\n\nAny URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nconst getBlobUrl = async (url) => {\n  const blob = (await thwack.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n### `buildURL`\n\nThis is a function that you can provide to override the default behavior. `buildURL` takes an `options` object with `url`, and optionally `baseURL` and `params` and returns a fully qualified URL.\n\nBy providing your own implimentation, it may not be necessary for a polyfill for `URL` in React Native. See [React Native](#react-native) for more information and a basic implimentation.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promise resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range Thwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with parent instances (if any) as well as with defaults.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch` or the `response` from a synthetic event callback.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parent's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\nHere is the event flow for all events. AS you can see, it is possible for your code to get into an endless loop, should your callback blindly issue a `request()` without checking to see if it's already done so, so take caution.\n\n![thwack events](https://user-images.githubusercontent.com/887639/79867660-aee3ce00-83ac-11ea-94fd-4078c1a36244.png)\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n### The `data` event\n\nThe event is fired after the body is streamed and parsed. It is fired only if the fetch returned a 2xx status code. Listeners will receive a `ThwackDataEvent` object with a `thwackResponse` key set to the response.\n\n### The `error` event\n\nThe event is fired after the body is streamed and parsed. It is fired if the fetch returned a non-2xx status code. Listeners will receive a `ThwackErrorEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nNodeJS\n</h2>\n\nThwack will work on NodeJS, but requires a polyfill for `window.fetch`. Luckily, there is a wonderful polyfill called [`node-fetch`](https://github.com/node-fetch/node-fetch) that you can use.\n\nIf you are using NodeJS version 10, you will also need a polyfill for `Array#flat` and `Object#fromEntries`. NodeJS version 11+ has these methods and does not require a polyfill.\n\nYou can either provide these polyfills yourself, or use one of the following convenience imports instead. If you are running NodeJS 11+, use:\n\n```js\nimport thwack from 'thwack/node'; // NodeJS version 12+\n```\n\nIf you are running on NodeJS 10, use:\n\n```js\nimport thwack from 'thwack/node10'; // NodeJS version 10\n```\n\nIf you wish to provide these polyfills yourself, then to use Thwack, you must import from `thwack/core` and set `fetch` as the default for `fetch` as so.\n\n```js\nimport thwack from 'thwack/code';\nthwack.defaults.fetch = global.fetch;\n```\n\nThis should be done in your app startup code, usually `index.js`.\n\n> Note: The `responseType` of `blob` is not supported on NodeJS.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nReact Native\n</h2>\n\nThwack will work with React Native, but requires a polyfill for `URL`. We suggect [`react-native-url-polyfill`](https://github.com/charpeni/react-native-url-polyfill).\n\nYou must also import from `thwack/rn` instead of `thwack`, like this.\n\n```js\nimport thwack from 'thwack/rn';\n```\n\nHowever, if you don't need a fully compliant URL builder, say if you only hit fully qualified URLs wirth no parameters, you can use this basic implimentation.\n\nYou must also import from `thwack/core` instead of of `thwack`.\n\n```js\nimport thwack from 'thwack/core';\n\n// This is a valid implementation ONLY if you specify an\n// absolute baseURL, a relative url, and no params.\nthwack.defaults.buildURL = ({ url, baseURL = '', params }) => {\n  if (Object.getOwnPropertyNames(params).length > 0) {\n    throw new Error('buildURL: params not supported');\n  }\n\n  return `${baseURL}${url}`;\n};\n```\n\n> Note: React Native does not support `stream` due to [#27741](https://github.com/facebook/react-native/issues/27741)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nHow to\n</h2>\n\n### Multiple concurrent requests\n\nYou can use `thwack.all()` and `thwack.spread()` to make simultaneous requests. Data is then presented to your callback as one array.\n\nHere we display information for two GitHub users.\n\n```js\nfunction displayGitHubUsers() {\n  return thwack\n    .all([\n      thwack.get('https://api.github.com/users/donavon'),\n      thwack.get('https://api.github.com/users/revelcw'),\n    ])\n    .then(\n      thwack.spread((...results) => {\n        const output = results\n          .map(\n            ({ data }) => `${data.login} has ${data.public_repos} public repos`\n          )\n          .join('\\n');\n        console.log(output);\n      })\n    );\n}\n```\n\nNote the these are simply helper functions. If you are using `async`/`await` you can write this without the Thwack helpers using `Promise.all`.\n\n```js\nasync function displayGitHubUsers() {\n  const results = await Promise.all([\n    thwack.get('https://api.github.com/users/donavon'),\n    thwack.get('https://api.github.com/users/revelcw'),\n  ]);\n  const output = results\n    .map(({ data }) => `${data.login} has ${data.public_repos} public repos`)\n    .join('\\n');\n  console.log(output);\n}\n```\n\nYou can see this running live in the [CodeSandbox](https://codesandbox.io/s/thwack-allspread-demo-zx2nt?file=/src/index.js:140-642).\n\n(Demo inspired by [this blob post](https://blog.logrocket.com/how-to-make-http-requests-like-a-pro-with-axios/) on axios/fetch)\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options. \n\nIn the browser, you can use the built-in [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).\n\n```js\nimport thwack from 'thwack';\n\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn NodeJS, you can use something like [abort-controller](https://www.npmjs.com/package/abort-controller).\n\n```js\nimport thwack from 'thwack';\nimport AbortController from \"abort-controller\"\n\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\nThe `status` in the `ThwackResponse` defaults to 200, so unless you need to mock a non-OK response, you only need to return `data`.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n    // tells Thwack to use the returned value instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above, the caller's request\n    // will be resolved to this `ThwackResponse` (defaults to status of 200)\n    return new thwack.ThwackResponse({\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      },\n    });\n  }\n});\n```\n\n### Convert DTO to Model\n\nOften it is desirable to convert a DTO (Data Transfer Object) into something easier to consume by the client. In this example below, we convert a complex DTO into `firstName`, `lastName`, `avatar`, and `email`. Other data elements that are returned from the API call are ignored.\n\nYou can see an example of DTO conversion, logging, and returning fake data in this sample app.\n\n<div>\n<img src=\"https://user-images.githubusercontent.com/887639/79865700-94f4bc00-83a9-11ea-9de6-9f204d5857c8.png\" width=\"500\" height=\"526\" alt=\"Mickey Mouse sample app\">\n</div>\n\nYou can [view the source code](https://codesandbox.io/s/sharp-chatelet-mje3w?file=/src/App.js) on CodeSandbox.\n\n### Load an Image as a Blob\n\nIn this example, we have a React Hook that loads an image as a Blob URL. It caches the URL to Blob URL mapping in session storage. Once loaded, any refresh of the page will instantaneously load the image from Blob URL.\n\n```js\nconst useBlobUrl = (imageUrl) => {\n  const [objectURL, setObjectURL] = useState('');\n\n  useEffect(() => {\n    let url = sessionStorage.getItem(imageUrl);\n\n    async function fetchData() {\n      if (!url) {\n        const { data } = await thwack.get(imageUrl, {\n          responseType: 'blob',\n        });\n        url = URL.createObjectURL(data);\n        sessionStorage.setItem(imageUrl, url);\n      }\n      setObjectURL(url);\n    }\n\n    fetchData();\n  }, [imageUrl]);\n\n  return objectURL;\n};\n```\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRight now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint to a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nLicense\n</h2>\n\nLicensed under [MIT](LICENSE)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nContributors ✨\n</h2>\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://kojikanao.netlify.com/\"><img src=\"https://avatars0.githubusercontent.com/u/474225?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Koji</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/tomByrer\"><img src=\"https://avatars2.githubusercontent.com/u/1308419?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tom Byrer</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=tomByrer\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://iansutherland.ca/\"><img src=\"https://avatars2.githubusercontent.com/u/433725?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ian Sutherland</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=iansu\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https:///www.blakeyoder.com\"><img src=\"https://avatars0.githubusercontent.com/u/5393338?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Blake Yoder</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=blakeyoder\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://www.ryanhinchey.co\"><img src=\"https://avatars0.githubusercontent.com/u/3943764?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ryan Hinchey</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=ryhinchey\" title=\"Documentation\">📖</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","gitHead":"44d73fdd0aeb3950d9428c847d846302c199a958","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.4.1-beta.5","_nodeVersion":"10.19.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-xKKJqMwiA4ecJmcHJ6HHaEmKKzZOWu5u/6GJ0rPvom3yEK4BnShL7cIn0qTzH73kYX3EVrAPApOYnwmhAjAbmg==","shasum":"3961ccd4fd574ff6e6f9ec03ea9764686bf6a794","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.4.1-beta.5.tgz","fileCount":14,"unpackedSize":49471,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJepecBCRA9TVsSAnZWagAAWQAP/RIo4uzc4W44lJMzAkqq\nXvoI2BkSoZ4gjEdurWrLv3PuCYIEmdznOjcTetI5Rcbm++E0queB0dFEGDdh\nuzHAJqTLmmiUVndrEnhqOaXwQgzdDTNxYLBz8lVrq9UMtVVFSaDNSklJBKhI\nmNRS+oqttAWttlFgmJDOYt6rqiaJb8eMN5+/IP69ke0HCzm2E+0Nh5HC6KSR\nemb9avgM8WjDSG5SsZdJnlhgOw/XFFrkllZu4uUbDY3jfIB31PuQqnmPQJ5a\nP2xwrSWqVcxbou4QxHGZOII/GSuAEe2lq7CbKGoXnQxL9Z6DaPukD1jEy2rz\nmLw53vvl7L3ppbtHgjnlkMFiZ7io9EWTBO9hmm3ghrXLAPx1NASNTAFR20NB\n9klKZpA8JxJGd/fui7FbTonAdcq32xlPRCypA8SR+d4FYzgW9CrIRAhL/LCy\nVtMreIEKyCdjoTKJgpKKFYHFjri6Bb9zAIYsNNNCRb7NDZtVE1dR8TR027vl\nPyEE/sf/ip7LAWU6U/Z169L575fQOEKRwWOv/VfmYRJdXgv46giIONNO53lT\nILMLFj0orc8u3G44mZw0DL7T9gWLDMKMLYBfkuDErSz4ruQSPAFnLzeLnR0D\n62wTX7Zn5ToUcURPUUNj/mAkBmyYvmUtfPyyOh2utcSmNlytG5DbAsRtBaRV\n6QF2\r\n=GVc0\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCID9TZGLlAHoJmVDXmegh7eO7RGaQQYAFLg+BV2bU1ELbAiBuyL92uNIpYzzSa7zYJd6yE3RVtrlJYqVJukv7v2F0/w=="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.4.1-beta.5_1587930880618_0.7835903945263258"},"_hasShrinkwrap":false},"0.4.1-beta.6":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.4.1-beta.6","description":"A tiny modern data fetching solution.","main":"dist/default/thwack.js","umd:main":"dist/default/thwack.umd.js","module":"dist/default/thwack.m.js","source":"src/default/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepare":"cd core && npm i && cd ..","prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && rimraf dist && npm t","build:core":"cd core && npm run build && cd ..","build":"microbundle && npm run build:core"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise","nodejs"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"@thwack/resolve":"^1.0.0","core-js":"^3.6.5","node-fetch":"^2.6.0","thwack-core":"file:./core"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.4.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.4.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"9bb09abb1ca6b721d22a1e688f439ebd1ae91eab","readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack)\n[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern — Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n- 🅰️ Typed — Easier inclusion for TypeScript projects\n- ✨ Support for NodeJS 10 and 12\n- ✨ Support for React Native\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nMethods\n</h2>\n\nThe following methods are available on all Thwack instances.\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n### Event listeners\n\nThwack supports the following event types: `request`, `response`, `data`, and `error`.\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nStatic Methods\n</h2>\n\n### Concurrency\n\nThwack has the following helper functions for making simultaneous requests. They are mostly for Axios compatibility. See the \"[How To](#how-to)\" section below for example usage.\n\n- `thwack.all(Promise<ThwackResponse>[])`\n\n- `thwack.spread(callback<results>)`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `defaults`\n\nThis allows you to read/set the default options for this instance and, in effect, any child instances.\n\nExample:\n\n```js\nthwack.defaults.baseURL = 'https://example.com/api';\n```\n\nFor an instance, `defaults` is the same object passed to `create`. For example, the following will output \"https://example.com/api\".\n\n```js\nconst instance = thwack.create({\n  baseURL: 'https://example.com/api',\n});\nconsole.log(instance.defaults.baseURL);\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `maxDepth`\n\nThe maximum level of recursive requests that can be made before Thwack throws an error. This is used to prevent a event callback from causing a recursive loop if it issues another `request` without proper guards in place. Default = 5.\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*/*`         |     `text`     |                    `response.text()`                     |\n\n> Note: `stream` is currently unsupported in React Native due to [#27741](https://github.com/facebook/react-native/issues/27741)\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between the resulting `content-type` from the response header and the parser type.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `*/*` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \"*/*\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could set the `baseURL` to your API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\nimport thwack from 'thwack';\n\nthwack.defaults.responseParserMap = { 'image/*': 'blob' };\n```\n\nAny URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nconst getBlobUrl = async (url) => {\n  const blob = (await thwack.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n### `buildURL`\n\nThis is a function that you can provide to override the default behavior. `buildURL` takes an `options` object with `url`, and optionally `baseURL` and `params` and returns a fully qualified URL.\n\nBy providing your own implimentation, it may not be necessary for a polyfill for `URL` in React Native. See [React Native](#react-native) for more information and a basic implimentation.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promise resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range Thwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with parent instances (if any) as well as with defaults.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch` or the `response` from a synthetic event callback.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parent's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\nHere is the event flow for all events. AS you can see, it is possible for your code to get into an endless loop, should your callback blindly issue a `request()` without checking to see if it's already done so, so take caution.\n\n![thwack events](https://user-images.githubusercontent.com/887639/79867660-aee3ce00-83ac-11ea-94fd-4078c1a36244.png)\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n### The `data` event\n\nThe event is fired after the body is streamed and parsed. It is fired only if the fetch returned a 2xx status code. Listeners will receive a `ThwackDataEvent` object with a `thwackResponse` key set to the response.\n\n### The `error` event\n\nThe event is fired after the body is streamed and parsed. It is fired if the fetch returned a non-2xx status code. Listeners will receive a `ThwackErrorEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nNodeJS\n</h2>\n\nThwack will work on NodeJS, but requires a polyfill for `window.fetch`. Luckily, there is a wonderful polyfill called [`node-fetch`](https://github.com/node-fetch/node-fetch) that you can use.\n\nIf you are using NodeJS version 10, you will also need a polyfill for `Array#flat` and `Object#fromEntries`. NodeJS version 11+ has these methods and does not require a polyfill.\n\nYou can either provide these polyfills yourself, or use one of the following convenience imports instead. If you are running NodeJS 11+, use:\n\n```js\nimport thwack from 'thwack/node'; // NodeJS version 12+\n```\n\nIf you are running on NodeJS 10, use:\n\n```js\nimport thwack from 'thwack/node10'; // NodeJS version 10\n```\n\nIf you wish to provide these polyfills yourself, then to use Thwack, you must import from `thwack/core` and set `fetch` as the default for `fetch` as so.\n\n```js\nimport thwack from 'thwack/code';\nthwack.defaults.fetch = global.fetch;\n```\n\nThis should be done in your app startup code, usually `index.js`.\n\n> Note: The `responseType` of `blob` is not supported on NodeJS.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nReact Native\n</h2>\n\nThwack will work with React Native, but requires a polyfill for `URL`. We suggect [`react-native-url-polyfill`](https://github.com/charpeni/react-native-url-polyfill).\n\nYou must also import from `thwack/rn` instead of `thwack`, like this.\n\n```js\nimport thwack from 'thwack/rn';\n```\n\nHowever, if you don't need a fully compliant URL builder, say if you only hit fully qualified URLs wirth no parameters, you can use this basic implimentation.\n\nYou must also import from `thwack/core` instead of of `thwack`.\n\n```js\nimport thwack from 'thwack/core';\n\n// This is a valid implementation ONLY if you specify an\n// absolute baseURL, a relative url, and no params.\nthwack.defaults.buildURL = ({ url, baseURL = '', params }) => {\n  if (Object.getOwnPropertyNames(params).length > 0) {\n    throw new Error('buildURL: params not supported');\n  }\n\n  return `${baseURL}${url}`;\n};\n```\n\n> Note: React Native does not support `stream` due to [#27741](https://github.com/facebook/react-native/issues/27741)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nHow to\n</h2>\n\n### Multiple concurrent requests\n\nYou can use `thwack.all()` and `thwack.spread()` to make simultaneous requests. Data is then presented to your callback as one array.\n\nHere we display information for two GitHub users.\n\n```js\nfunction displayGitHubUsers() {\n  return thwack\n    .all([\n      thwack.get('https://api.github.com/users/donavon'),\n      thwack.get('https://api.github.com/users/revelcw'),\n    ])\n    .then(\n      thwack.spread((...results) => {\n        const output = results\n          .map(\n            ({ data }) => `${data.login} has ${data.public_repos} public repos`\n          )\n          .join('\\n');\n        console.log(output);\n      })\n    );\n}\n```\n\nNote the these are simply helper functions. If you are using `async`/`await` you can write this without the Thwack helpers using `Promise.all`.\n\n```js\nasync function displayGitHubUsers() {\n  const results = await Promise.all([\n    thwack.get('https://api.github.com/users/donavon'),\n    thwack.get('https://api.github.com/users/revelcw'),\n  ]);\n  const output = results\n    .map(({ data }) => `${data.login} has ${data.public_repos} public repos`)\n    .join('\\n');\n  console.log(output);\n}\n```\n\nYou can see this running live in the [CodeSandbox](https://codesandbox.io/s/thwack-allspread-demo-zx2nt?file=/src/index.js:140-642).\n\n(Demo inspired by [this blob post](https://blog.logrocket.com/how-to-make-http-requests-like-a-pro-with-axios/) on axios/fetch)\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options. \n\nIn the browser, you can use the built-in [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).\n\n```js\nimport thwack from 'thwack';\n\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn NodeJS, you can use something like [abort-controller](https://www.npmjs.com/package/abort-controller).\n\n```js\nimport thwack from 'thwack';\nimport AbortController from \"abort-controller\"\n\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\nThe `status` in the `ThwackResponse` defaults to 200, so unless you need to mock a non-OK response, you only need to return `data`.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n    // tells Thwack to use the returned value instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above, the caller's request\n    // will be resolved to this `ThwackResponse` (defaults to status of 200)\n    return new thwack.ThwackResponse({\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      },\n    });\n  }\n});\n```\n\n### Convert DTO to Model\n\nOften it is desirable to convert a DTO (Data Transfer Object) into something easier to consume by the client. In this example below, we convert a complex DTO into `firstName`, `lastName`, `avatar`, and `email`. Other data elements that are returned from the API call are ignored.\n\nYou can see an example of DTO conversion, logging, and returning fake data in this sample app.\n\n<div>\n<img src=\"https://user-images.githubusercontent.com/887639/79865700-94f4bc00-83a9-11ea-9de6-9f204d5857c8.png\" width=\"500\" height=\"526\" alt=\"Mickey Mouse sample app\">\n</div>\n\nYou can [view the source code](https://codesandbox.io/s/sharp-chatelet-mje3w?file=/src/App.js) on CodeSandbox.\n\n### Load an Image as a Blob\n\nIn this example, we have a React Hook that loads an image as a Blob URL. It caches the URL to Blob URL mapping in session storage. Once loaded, any refresh of the page will instantaneously load the image from Blob URL.\n\n```js\nconst useBlobUrl = (imageUrl) => {\n  const [objectURL, setObjectURL] = useState('');\n\n  useEffect(() => {\n    let url = sessionStorage.getItem(imageUrl);\n\n    async function fetchData() {\n      if (!url) {\n        const { data } = await thwack.get(imageUrl, {\n          responseType: 'blob',\n        });\n        url = URL.createObjectURL(data);\n        sessionStorage.setItem(imageUrl, url);\n      }\n      setObjectURL(url);\n    }\n\n    fetchData();\n  }, [imageUrl]);\n\n  return objectURL;\n};\n```\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRight now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint to a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nLicense\n</h2>\n\nLicensed under [MIT](LICENSE)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nContributors ✨\n</h2>\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://kojikanao.netlify.com/\"><img src=\"https://avatars0.githubusercontent.com/u/474225?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Koji</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/tomByrer\"><img src=\"https://avatars2.githubusercontent.com/u/1308419?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tom Byrer</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=tomByrer\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://iansutherland.ca/\"><img src=\"https://avatars2.githubusercontent.com/u/433725?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ian Sutherland</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=iansu\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https:///www.blakeyoder.com\"><img src=\"https://avatars0.githubusercontent.com/u/5393338?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Blake Yoder</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=blakeyoder\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://www.ryanhinchey.co\"><img src=\"https://avatars0.githubusercontent.com/u/3943764?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ryan Hinchey</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=ryhinchey\" title=\"Documentation\">📖</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.4.1-beta.6","_nodeVersion":"10.19.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-nDijlfmj2pImquxON37JUsoH2IZCfl0mLGMfp1t2oxr6kX/VrVb+noHyT0UwuxSaq62glNlaH9Hbe1EaCG00/w==","shasum":"6342954ecf865db530cec03c3274e391c0ee4116","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.4.1-beta.6.tgz","fileCount":19,"unpackedSize":229596,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJepuFFCRA9TVsSAnZWagAAb8oP/i4KenAY3apc9Cor7mAy\nPX+hv6aJu8tDFbbAJQRFSQn2Ce2Kj6dGhyMZtgl9G4FG1hAMqCxde7QuLKJ0\nuuLYr/4VRN4YFt65Zki90wjyMxsBDdr11LaVmrG7nub2zcyazi8/Y9QMzn8Y\nLw2yz9wO9IfZeWh03SoxyaeSprD4Y8v5t+5bC0LtdSy6T6TEvJTNR6Fp/36e\nr5aIzHe/luxXvJLn7FO7UQp/64ikayCo/aGJ/RPA78BQK40Nq48IGhqo35q5\n1eUKdCTKt0qx2MtV6oGJQ6U26s5f02vbq694XPQP12vwhdvOc+jaIFQOPQxB\nVn5NfgZmOinNHaeK+6K5oWv2H/z6Gg2AM3D4OHfDCq/ChkdFgqZHJCx1zTvV\nRM3tx9UaWBDIGZGAAUWPg03dMUmno6wZEy0epJYp1rE8ODxSnkRnERedT2Nw\nneVDfGcA4p1P6cn3Uo1eEmcXKU0IK87qfhXG3FTcCIFJmMXRXSclmbzqahCB\n4WlK4GmbYyR9CbTQ62kPPiZ/nUj3LjOnx1l33V1oUJqICB5+8D73y4TmcjLW\nZ9/AO6DaFiBZDyQEzB6TDRhnLFbDh9zWZNg3Nq7KKY2R6XEExlhFgra+T1Hy\nr44wOGhcDW3R4ADeKjq8jNrawD0EKKqpvBozZ+JlRMbjn6xCIfaOjEJSerlP\nv+OL\r\n=QRZ1\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIHNMNBGK6pyfFPq47O351opX4/RfdO3wmiEJzBA+aWBUAiBBIlT45QBLXlcqqzBtCmCu/mYO/DLL1XLRzdbJFPZD5A=="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.4.1-beta.6_1587994948885_0.9771504868296417"},"_hasShrinkwrap":false},"0.4.1-beta.7":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.4.1-beta.7","description":"A tiny modern data fetching solution.","main":"dist/default/thwack.js","umd:main":"dist/default/thwack.umd.js","module":"dist/default/thwack.m.js","source":"src/default/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && rimraf dist && npm t","build:core":"cd core && npm i && npm run build && cd ..","build":"microbundle && npm run build:core"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise","nodejs"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"@thwack/resolve":"^1.0.0","core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.4.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.4.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack)\n[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern — Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n- 🅰️ Typed — Easier inclusion for TypeScript projects\n- ✨ Support for NodeJS 10 and 12\n- ✨ Support for React Native\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nMethods\n</h2>\n\nThe following methods are available on all Thwack instances.\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n### Event listeners\n\nThwack supports the following event types: `request`, `response`, `data`, and `error`.\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nStatic Methods\n</h2>\n\n### Concurrency\n\nThwack has the following helper functions for making simultaneous requests. They are mostly for Axios compatibility. See the \"[How To](#how-to)\" section below for example usage.\n\n- `thwack.all(Promise<ThwackResponse>[])`\n\n- `thwack.spread(callback<results>)`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `defaults`\n\nThis allows you to read/set the default options for this instance and, in effect, any child instances.\n\nExample:\n\n```js\nthwack.defaults.baseURL = 'https://example.com/api';\n```\n\nFor an instance, `defaults` is the same object passed to `create`. For example, the following will output \"https://example.com/api\".\n\n```js\nconst instance = thwack.create({\n  baseURL: 'https://example.com/api',\n});\nconsole.log(instance.defaults.baseURL);\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `maxDepth`\n\nThe maximum level of recursive requests that can be made before Thwack throws an error. This is used to prevent a event callback from causing a recursive loop if it issues another `request` without proper guards in place. Default = 5.\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*/*`         |     `text`     |                    `response.text()`                     |\n\n> Note: `stream` is currently unsupported in React Native due to [#27741](https://github.com/facebook/react-native/issues/27741)\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between the resulting `content-type` from the response header and the parser type.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `*/*` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \"*/*\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could set the `baseURL` to your API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\nimport thwack from 'thwack';\n\nthwack.defaults.responseParserMap = { 'image/*': 'blob' };\n```\n\nAny URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nconst getBlobUrl = async (url) => {\n  const blob = (await thwack.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n### `buildURL`\n\nThis is a function that you can provide to override the default behavior. `buildURL` takes an `options` object with `url`, and optionally `baseURL` and `params` and returns a fully qualified URL.\n\nBy providing your own implimentation, it may not be necessary for a polyfill for `URL` in React Native. See [React Native](#react-native) for more information and a basic implimentation.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promise resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range Thwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with parent instances (if any) as well as with defaults.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch` or the `response` from a synthetic event callback.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parent's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\nHere is the event flow for all events. AS you can see, it is possible for your code to get into an endless loop, should your callback blindly issue a `request()` without checking to see if it's already done so, so take caution.\n\n![thwack events](https://user-images.githubusercontent.com/887639/79867660-aee3ce00-83ac-11ea-94fd-4078c1a36244.png)\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n### The `data` event\n\nThe event is fired after the body is streamed and parsed. It is fired only if the fetch returned a 2xx status code. Listeners will receive a `ThwackDataEvent` object with a `thwackResponse` key set to the response.\n\n### The `error` event\n\nThe event is fired after the body is streamed and parsed. It is fired if the fetch returned a non-2xx status code. Listeners will receive a `ThwackErrorEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nNodeJS\n</h2>\n\nThwack will work on NodeJS, but requires a polyfill for `window.fetch`. Luckily, there is a wonderful polyfill called [`node-fetch`](https://github.com/node-fetch/node-fetch) that you can use.\n\nIf you are using NodeJS version 10, you will also need a polyfill for `Array#flat` and `Object#fromEntries`. NodeJS version 11+ has these methods and does not require a polyfill.\n\nYou can either provide these polyfills yourself, or use one of the following convenience imports instead. If you are running NodeJS 11+, use:\n\n```js\nimport thwack from 'thwack/node'; // NodeJS version 12+\n```\n\nIf you are running on NodeJS 10, use:\n\n```js\nimport thwack from 'thwack/node10'; // NodeJS version 10\n```\n\nIf you wish to provide these polyfills yourself, then to use Thwack, you must import from `thwack/core` and set `fetch` as the default for `fetch` as so.\n\n```js\nimport thwack from 'thwack/code';\nthwack.defaults.fetch = global.fetch;\n```\n\nThis should be done in your app startup code, usually `index.js`.\n\n> Note: The `responseType` of `blob` is not supported on NodeJS.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nReact Native\n</h2>\n\nThwack will work with React Native, but requires a polyfill for `URL`. We suggect [`react-native-url-polyfill`](https://github.com/charpeni/react-native-url-polyfill).\n\nYou must also import from `thwack/rn` instead of `thwack`, like this.\n\n```js\nimport thwack from 'thwack/rn';\n```\n\nHowever, if you don't need a fully compliant URL builder, say if you only hit fully qualified URLs wirth no parameters, you can use this basic implimentation.\n\nYou must also import from `thwack/core` instead of of `thwack`.\n\n```js\nimport thwack from 'thwack/core';\n\n// This is a valid implementation ONLY if you specify an\n// absolute baseURL, a relative url, and no params.\nthwack.defaults.buildURL = ({ url, baseURL = '', params }) => {\n  if (Object.getOwnPropertyNames(params).length > 0) {\n    throw new Error('buildURL: params not supported');\n  }\n\n  return `${baseURL}${url}`;\n};\n```\n\n> Note: React Native does not support `stream` due to [#27741](https://github.com/facebook/react-native/issues/27741)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nHow to\n</h2>\n\n### Multiple concurrent requests\n\nYou can use `thwack.all()` and `thwack.spread()` to make simultaneous requests. Data is then presented to your callback as one array.\n\nHere we display information for two GitHub users.\n\n```js\nfunction displayGitHubUsers() {\n  return thwack\n    .all([\n      thwack.get('https://api.github.com/users/donavon'),\n      thwack.get('https://api.github.com/users/revelcw'),\n    ])\n    .then(\n      thwack.spread((...results) => {\n        const output = results\n          .map(\n            ({ data }) => `${data.login} has ${data.public_repos} public repos`\n          )\n          .join('\\n');\n        console.log(output);\n      })\n    );\n}\n```\n\nNote the these are simply helper functions. If you are using `async`/`await` you can write this without the Thwack helpers using `Promise.all`.\n\n```js\nasync function displayGitHubUsers() {\n  const results = await Promise.all([\n    thwack.get('https://api.github.com/users/donavon'),\n    thwack.get('https://api.github.com/users/revelcw'),\n  ]);\n  const output = results\n    .map(({ data }) => `${data.login} has ${data.public_repos} public repos`)\n    .join('\\n');\n  console.log(output);\n}\n```\n\nYou can see this running live in the [CodeSandbox](https://codesandbox.io/s/thwack-allspread-demo-zx2nt?file=/src/index.js:140-642).\n\n(Demo inspired by [this blob post](https://blog.logrocket.com/how-to-make-http-requests-like-a-pro-with-axios/) on axios/fetch)\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options. \n\nIn the browser, you can use the built-in [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).\n\n```js\nimport thwack from 'thwack';\n\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn NodeJS, you can use something like [abort-controller](https://www.npmjs.com/package/abort-controller).\n\n```js\nimport thwack from 'thwack';\nimport AbortController from \"abort-controller\"\n\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\nThe `status` in the `ThwackResponse` defaults to 200, so unless you need to mock a non-OK response, you only need to return `data`.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n    // tells Thwack to use the returned value instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above, the caller's request\n    // will be resolved to this `ThwackResponse` (defaults to status of 200)\n    return new thwack.ThwackResponse({\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      },\n    });\n  }\n});\n```\n\n### Convert DTO to Model\n\nOften it is desirable to convert a DTO (Data Transfer Object) into something easier to consume by the client. In this example below, we convert a complex DTO into `firstName`, `lastName`, `avatar`, and `email`. Other data elements that are returned from the API call are ignored.\n\nYou can see an example of DTO conversion, logging, and returning fake data in this sample app.\n\n<div>\n<img src=\"https://user-images.githubusercontent.com/887639/79865700-94f4bc00-83a9-11ea-9de6-9f204d5857c8.png\" width=\"500\" height=\"526\" alt=\"Mickey Mouse sample app\">\n</div>\n\nYou can [view the source code](https://codesandbox.io/s/sharp-chatelet-mje3w?file=/src/App.js) on CodeSandbox.\n\n### Load an Image as a Blob\n\nIn this example, we have a React Hook that loads an image as a Blob URL. It caches the URL to Blob URL mapping in session storage. Once loaded, any refresh of the page will instantaneously load the image from Blob URL.\n\n```js\nconst useBlobUrl = (imageUrl) => {\n  const [objectURL, setObjectURL] = useState('');\n\n  useEffect(() => {\n    let url = sessionStorage.getItem(imageUrl);\n\n    async function fetchData() {\n      if (!url) {\n        const { data } = await thwack.get(imageUrl, {\n          responseType: 'blob',\n        });\n        url = URL.createObjectURL(data);\n        sessionStorage.setItem(imageUrl, url);\n      }\n      setObjectURL(url);\n    }\n\n    fetchData();\n  }, [imageUrl]);\n\n  return objectURL;\n};\n```\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRight now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint to a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nLicense\n</h2>\n\nLicensed under [MIT](LICENSE)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nContributors ✨\n</h2>\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://kojikanao.netlify.com/\"><img src=\"https://avatars0.githubusercontent.com/u/474225?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Koji</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/tomByrer\"><img src=\"https://avatars2.githubusercontent.com/u/1308419?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tom Byrer</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=tomByrer\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://iansutherland.ca/\"><img src=\"https://avatars2.githubusercontent.com/u/433725?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ian Sutherland</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=iansu\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https:///www.blakeyoder.com\"><img src=\"https://avatars0.githubusercontent.com/u/5393338?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Blake Yoder</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=blakeyoder\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://www.ryanhinchey.co\"><img src=\"https://avatars0.githubusercontent.com/u/3943764?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ryan Hinchey</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=ryhinchey\" title=\"Documentation\">📖</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","gitHead":"9bb09abb1ca6b721d22a1e688f439ebd1ae91eab","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.4.1-beta.7","_nodeVersion":"10.19.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-u0y0tZ9oetYf/niMZySsNyVcEA7aObVa33BGBrerk4kPlkIpyFsmMOzaY3Y1qCbQ7KStWOiJ7p3qu6/YU92MkA==","shasum":"438ade2619b2ad7f43b741c693e10a1ccd3897f1","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.4.1-beta.7.tgz","fileCount":19,"unpackedSize":229527,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJepuQ8CRA9TVsSAnZWagAAmwoP/jINhC+5KjX8551779pm\n+NK32p3c02luBXKMbmIXir81U6t8vA+UQnXDLS6SmAT7VrrBmK9Ak9uHeEaX\nQWoSz+wMO8c9+fvYNS28WLbkpmucaFY3uOzrpz6QXMiIhh97UdY9dVj/6MQT\n1RN0VbqnsGYzr9Bm7tiSeQdXeCA1yuCMfInhC3a5FtcG+7t2Gk9r1Ms8VQPI\nKzQ4Gi01Lu3B/yjYaHknHSc8uflhqTcosuC9IWPDrWHf/oS0XFL7Q8n8CtVD\nCwmLXEKBlwiUGgSXoqPm/5LnjngQwzYn3/gP5PEGyhVUlpujTZt0zG1mLdDX\nyGZa8S2DaidfM1yFFAOCm3AKwiVdrK4KTTaxgfAuKhSNwKjudb1UmCzHDwsI\neYm+LcuhCyflC/xmI2RAPB8T1s63K0BgdfLaYMcM3yyMK022yhFelCdqeqhI\n2C4IZi49E2eUkwGPsNA2Pdcwxb00ru6YaiszyUbG0FNONA9wDJ6R6MWni+AV\naQCxRB+uea9G4Edh+VUIULrj4P56+ZCD+jZYtU+tY+IIXe7xTMu21sDrj+rf\nMhwhtwN8CNSpaT2L9YujzKa1ZDBDInO/oL7fL8pdy1SKbtB/0wqn2CT3XhuU\nDYnQSGwALp/tyd6isytBosdKnkqFCdaqbxoA6VbMC4vmTGirAv3akiROehin\n9PIN\r\n=RJP6\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIE2Vmy6ZWRBXxn9Xq+LB9/JqSNkxsjuVt67Y0Pwwt3g1AiAg35mQXx9+0APnePlTgfrN+A8d1NQgdk3qnafaX/21+g=="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.4.1-beta.7_1587995707644_0.19431666667587"},"_hasShrinkwrap":false},"0.4.1-beta.8":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.4.1-beta.8","description":"A tiny modern data fetching solution.","main":"dist/default/thwack.js","umd:main":"dist/default/thwack.umd.js","module":"dist/default/thwack.m.js","source":"src/default/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && rimraf dist && npm t","build:core":"cd core && npm i && npm run build && cd ..","build":"microbundle && npm run build:core"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise","nodejs"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"@thwack/resolve":"^1.0.0","core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.4.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.4.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack)\n[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern — Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n- 🅰️ Typed — Easier inclusion for TypeScript projects\n- ✨ Support for NodeJS 10 and 12\n- ✨ Support for React Native\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\nThwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nMethods\n</h2>\n\nThe following methods are available on all Thwack instances.\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n### Event listeners\n\nThwack supports the following event types: `request`, `response`, `data`, and `error`.\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nStatic Methods\n</h2>\n\n### Concurrency\n\nThwack has the following helper functions for making simultaneous requests. They are mostly for Axios compatibility. See the \"[How To](#how-to)\" section below for example usage.\n\n- `thwack.all(Promise<ThwackResponse>[])`\n\n- `thwack.spread(callback<results>)`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `defaults`\n\nThis allows you to read/set the default options for this instance and, in effect, any child instances.\n\nExample:\n\n```js\nthwack.defaults.baseURL = 'https://example.com/api';\n```\n\nFor an instance, `defaults` is the same object passed to `create`. For example, the following will output \"https://example.com/api\".\n\n```js\nconst instance = thwack.create({\n  baseURL: 'https://example.com/api',\n});\nconsole.log(instance.defaults.baseURL);\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `maxDepth`\n\nThe maximum level of recursive requests that can be made before Thwack throws an error. This is used to prevent a event callback from causing a recursive loop if it issues another `request` without proper guards in place. Default = 5.\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*/*`         |     `text`     |                    `response.text()`                     |\n\n> Note: `stream` is currently unsupported in React Native due to [#27741](https://github.com/facebook/react-native/issues/27741)\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between the resulting `content-type` from the response header and the parser type.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `*/*` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \"*/*\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could set the `baseURL` to your API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\nimport thwack from 'thwack';\n\nthwack.defaults.responseParserMap = { 'image/*': 'blob' };\n```\n\nAny URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nconst getBlobUrl = async (url) => {\n  const blob = (await thwack.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox]().\n\n> Note that this works for other things besides images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n### `buildURL`\n\nThis is a function that you can provide to override the default behavior. `buildURL` takes an `options` object with `url`, and optionally `baseURL` and `params` and returns a fully qualified URL.\n\nBy providing your own implimentation, it may not be necessary for a polyfill for `URL` in React Native. See [React Native](#react-native) for more information and a basic implimentation.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). If the promise resolves successfully, this value will always be `true`. If the request has a status outside of the 2xx range Thwack will throw a `ThwackResponseError` and `ok` will be false.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with parent instances (if any) as well as with defaults.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch` or the `response` from a synthetic event callback.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parent's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\nHere is the event flow for all events. AS you can see, it is possible for your code to get into an endless loop, should your callback blindly issue a `request()` without checking to see if it's already done so, so take caution.\n\n![thwack events](https://user-images.githubusercontent.com/887639/79867660-aee3ce00-83ac-11ea-94fd-4078c1a36244.png)\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n### The `data` event\n\nThe event is fired after the body is streamed and parsed. It is fired only if the fetch returned a 2xx status code. Listeners will receive a `ThwackDataEvent` object with a `thwackResponse` key set to the response.\n\n### The `error` event\n\nThe event is fired after the body is streamed and parsed. It is fired if the fetch returned a non-2xx status code. Listeners will receive a `ThwackErrorEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nNodeJS\n</h2>\n\nThwack will work on NodeJS, but requires a polyfill for `window.fetch`. Luckily, there is a wonderful polyfill called [`node-fetch`](https://github.com/node-fetch/node-fetch) that you can use.\n\nIf you are using NodeJS version 10, you will also need a polyfill for `Array#flat` and `Object#fromEntries`. NodeJS version 11+ has these methods and does not require a polyfill.\n\nYou can either provide these polyfills yourself, or use one of the following convenience imports instead. If you are running NodeJS 11+, use:\n\n```js\nimport thwack from 'thwack/node'; // NodeJS version 12+\n```\n\nIf you are running on NodeJS 10, use:\n\n```js\nimport thwack from 'thwack/node10'; // NodeJS version 10\n```\n\nIf you wish to provide these polyfills yourself, then to use Thwack, you must import from `thwack/core` and set `fetch` as the default for `fetch` as so.\n\n```js\nimport thwack from 'thwack/code';\nthwack.defaults.fetch = global.fetch;\n```\n\nThis should be done in your app startup code, usually `index.js`.\n\n> Note: The `responseType` of `blob` is not supported on NodeJS.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nReact Native\n</h2>\n\nThwack will work with React Native, but requires a polyfill for `URL`. We suggect [`react-native-url-polyfill`](https://github.com/charpeni/react-native-url-polyfill).\n\nYou must also import from `thwack/rn` instead of `thwack`, like this.\n\n```js\nimport thwack from 'thwack/rn';\n```\n\nHowever, if you don't need a fully compliant URL builder, say if you only hit fully qualified URLs wirth no parameters, you can use this basic implimentation.\n\nYou must also import from `thwack/core` instead of of `thwack`.\n\n```js\nimport thwack from 'thwack/core';\n\n// This is a valid implementation ONLY if you specify an\n// absolute baseURL, a relative url, and no params.\nthwack.defaults.buildURL = ({ url, baseURL = '', params }) => {\n  if (Object.getOwnPropertyNames(params).length > 0) {\n    throw new Error('buildURL: params not supported');\n  }\n\n  return `${baseURL}${url}`;\n};\n```\n\n> Note: React Native does not support `stream` due to [#27741](https://github.com/facebook/react-native/issues/27741)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nHow to\n</h2>\n\n### Multiple concurrent requests\n\nYou can use `thwack.all()` and `thwack.spread()` to make simultaneous requests. Data is then presented to your callback as one array.\n\nHere we display information for two GitHub users.\n\n```js\nfunction displayGitHubUsers() {\n  return thwack\n    .all([\n      thwack.get('https://api.github.com/users/donavon'),\n      thwack.get('https://api.github.com/users/revelcw'),\n    ])\n    .then(\n      thwack.spread((...results) => {\n        const output = results\n          .map(\n            ({ data }) => `${data.login} has ${data.public_repos} public repos`\n          )\n          .join('\\n');\n        console.log(output);\n      })\n    );\n}\n```\n\nNote the these are simply helper functions. If you are using `async`/`await` you can write this without the Thwack helpers using `Promise.all`.\n\n```js\nasync function displayGitHubUsers() {\n  const results = await Promise.all([\n    thwack.get('https://api.github.com/users/donavon'),\n    thwack.get('https://api.github.com/users/revelcw'),\n  ]);\n  const output = results\n    .map(({ data }) => `${data.login} has ${data.public_repos} public repos`)\n    .join('\\n');\n  console.log(output);\n}\n```\n\nYou can see this running live in the [CodeSandbox](https://codesandbox.io/s/thwack-allspread-demo-zx2nt?file=/src/index.js:140-642).\n\n(Demo inspired by [this blob post](https://blog.logrocket.com/how-to-make-http-requests-like-a-pro-with-axios/) on axios/fetch)\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options. \n\nIn the browser, you can use the built-in [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).\n\n```js\nimport thwack from 'thwack';\n\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn NodeJS, you can use something like [abort-controller](https://www.npmjs.com/package/abort-controller).\n\n```js\nimport thwack from 'thwack';\nimport AbortController from \"abort-controller\"\n\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\nThe `status` in the `ThwackResponse` defaults to 200, so unless you need to mock a non-OK response, you only need to return `data`.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n    // tells Thwack to use the returned value instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above, the caller's request\n    // will be resolved to this `ThwackResponse` (defaults to status of 200)\n    return new thwack.ThwackResponse({\n      data: {\n        name: 'Fake Username',\n        email: 'fakeuser@example.com',\n      },\n    });\n  }\n});\n```\n\n### Convert DTO to Model\n\nOften it is desirable to convert a DTO (Data Transfer Object) into something easier to consume by the client. In this example below, we convert a complex DTO into `firstName`, `lastName`, `avatar`, and `email`. Other data elements that are returned from the API call are ignored.\n\nYou can see an example of DTO conversion, logging, and returning fake data in this sample app.\n\n<div>\n<img src=\"https://user-images.githubusercontent.com/887639/79865700-94f4bc00-83a9-11ea-9de6-9f204d5857c8.png\" width=\"500\" height=\"526\" alt=\"Mickey Mouse sample app\">\n</div>\n\nYou can [view the source code](https://codesandbox.io/s/sharp-chatelet-mje3w?file=/src/App.js) on CodeSandbox.\n\n### Load an Image as a Blob\n\nIn this example, we have a React Hook that loads an image as a Blob URL. It caches the URL to Blob URL mapping in session storage. Once loaded, any refresh of the page will instantaneously load the image from Blob URL.\n\n```js\nconst useBlobUrl = (imageUrl) => {\n  const [objectURL, setObjectURL] = useState('');\n\n  useEffect(() => {\n    let url = sessionStorage.getItem(imageUrl);\n\n    async function fetchData() {\n      if (!url) {\n        const { data } = await thwack.get(imageUrl, {\n          responseType: 'blob',\n        });\n        url = URL.createObjectURL(data);\n        sessionStorage.setItem(imageUrl, url);\n      }\n      setObjectURL(url);\n    }\n\n    fetchData();\n  }, [imageUrl]);\n\n  return objectURL;\n};\n```\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRight now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint to a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nLicense\n</h2>\n\nLicensed under [MIT](LICENSE)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nContributors ✨\n</h2>\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://kojikanao.netlify.com/\"><img src=\"https://avatars0.githubusercontent.com/u/474225?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Koji</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/tomByrer\"><img src=\"https://avatars2.githubusercontent.com/u/1308419?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tom Byrer</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=tomByrer\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://iansutherland.ca/\"><img src=\"https://avatars2.githubusercontent.com/u/433725?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ian Sutherland</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=iansu\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https:///www.blakeyoder.com\"><img src=\"https://avatars0.githubusercontent.com/u/5393338?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Blake Yoder</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=blakeyoder\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://www.ryanhinchey.co\"><img src=\"https://avatars0.githubusercontent.com/u/3943764?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ryan Hinchey</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=ryhinchey\" title=\"Documentation\">📖</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","gitHead":"9bb09abb1ca6b721d22a1e688f439ebd1ae91eab","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.4.1-beta.8","_nodeVersion":"10.19.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-jRZj0pNFmxPovF4J6XRIHlq+GGjK9XvNpvJxd6TpeZb3Iaym43NMXYdwSCtJ+4KxG/Y8R2noHk80RbPpCrArZA==","shasum":"f064c1048a0dd5fe45e613d90d59914164f0f183","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.4.1-beta.8.tgz","fileCount":19,"unpackedSize":229482,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJepvIvCRA9TVsSAnZWagAAwlQQAKS2LCwmmmtkuu6t+aHJ\nDhYhd8Qy3A29wUOes1qsT8ZvN93qB1U2ZJzdGMvdGwPxqNKrp+9+qeMWAbLY\nTs679xk8JkOuMXWNzCwksP+cAitJHNmHotBJ/pp9zv8C2GsBJ77zRlarp4C6\ncU/troygGhxCWBswA0nGhLGxI0Pmkvi5hL9J0VbA8VW/pHowwrT8UMXbiSHK\n9jgWbx10PfsLgpZFs2qChVo4y/Pmt6K72P0HFh+RNFhVeYA7POo2eHI+12Xu\nyeq16C6GSx9kNZBxfMFCpYKtR8yUzWgUk4SJSHFNIeUSrGsRiKfPGtqHkUIT\naB2QnWD+Gw5CyQxSo1c8oihUq8dWUfFc/z77BXWhrNBs+e4teW6LVyaRTBAp\n5I8aR8HLQ7WFqNyiCFpVpHYIaM/o7lVbh2jQ//9hBD4qD7rK+BGiXVmMdKb0\n2WmIoxVWBc/milB5LF4UStZT+axmXeIxmJrggaMTluABnFcer+khnizV2zst\ny/5HJLKgqvpwdWmPeAyizpEnRrFzTRkDgGd7v0bub1CjbI4oITZMERc3v+rz\nrdbr5jI/N3G8oGfZ+0yBoXKLMah3DuOZa/NqtYe9LUOtL1dSXbjY+e6JuwHA\n09rIRfs65JLFbtayzB4fnSR1VZXcoF5eVq0ebnazf8kjUNNxjXEEnddvF9cZ\ntX6+\r\n=zroP\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDR+/pRdnvnk5wpruID9ZJnrqeG3Me8a8p4CjxpBTHtNQIgSmgq7XeJJuKYWiHdGFkgZvZIKH9eURjT4wlIUNiYac4="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.4.1-beta.8_1587999279285_0.12454930715485601"},"_hasShrinkwrap":false},"0.5.0-beta.1":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.5.0-beta.1","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/default/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && rimraf dist && npm t","build":"microbundle"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise","nodejs","react-native"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"@thwack/resolve":"^1.0.0","core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.4.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.4.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"9b823061841df23e9d2c7703411fa9c2c5fff66d","readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack)\n[![All Contributors](https://img.shields.io/badge/all_contributors-11-orange.svg?style=flat-square)](#contributors-)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern — Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n- 🅰️ Typed — Easier inclusion for TypeScript projects\n- ✨ Support for NodeJS 10 and 12\n- 📱 Support for React Native\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\n~~Thwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.~~\n\nScratch that. Thwack provides the same power as Axios with a much smaller footprint. And Thwack's promise based event system is easier to use.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nMethods\n</h2>\n\nThe following methods are available on all Thwack instances.\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n  Thwacks URL resolution is [RFC-3986](https://tools.ietf.org/html/rfc3986#appendix-B) compliant. Axios's is not. It's powered by [`@thwack/resolve`](https://www.npmjs.com/package/@thwack/resolve).\n\n### Event listeners\n\nThwack supports the following event types: `request`, `response`, `data`, and `error`.\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nStatic Methods\n</h2>\n\n### Concurrency\n\nThwack has the following helper functions for making simultaneous requests. They are mostly for Axios compatibility. See the \"[How To](#how-to)\" section below for example usage.\n\n- `thwack.all(Promise<ThwackResponse>[])`\n\n- `thwack.spread(callback<results>)`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Defaults to the `origin` + `pathname` of the current web page.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `defaults`\n\nThis allows you to read/set the default options for this instance and, in effect, any child instances.\n\nExample:\n\n```js\nthwack.defaults.baseURL = 'https://example.com/api';\n```\n\nFor an instance, `defaults` is the same object passed to `create`. For example, the following will output \"https://example.com/api\".\n\n```js\nconst instance = thwack.create({\n  baseURL: 'https://example.com/api',\n});\nconsole.log(instance.defaults.baseURL);\n```\n\nAlso note that setting `defaults` on an instance (or even passing `options`) to an instance does NOT effect the parent. So for the following example, `thwack.defaults.baseURL` will still be \"https://api1.example.net/\".\n\n```js\nthwack.defaults.baseURL = 'https://api1.example.net/';\nconst instance = thwack.create();\ninstance.defaults.baseURL = 'https://example.com/api';\n\nconsole.log(thwack.defaults.baseURL);\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `maxDepth`\n\nThe maximum level of recursive requests that can be made in a callbck before Thwack throws an error. This is used to prevent an event callback from causing a recursive loop, This if it issues another `request` without proper safeguards in place. Default = 3.\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|                       |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*/*`         |     `text`     |                    `response.text()`                     |\n\n> Note: `stream` is currently unsupported in React Native due to [#27741](https://github.com/facebook/react-native/issues/27741)\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between the resulting `content-type` from the response header and the parser type.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `*/*` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \"*/*\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could set the `baseURL` to your API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\nimport thwack from 'thwack';\n\nthwack.defaults.responseParserMap = { 'image/*': 'blob' };\n```\n\nAny URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nconst getBlobUrl = async (url) => {\n  const blob = (await thwack.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox](https://codesandbox.io/s/load-image-as-blob-410uq).\n\n> Note that you can use this technique for other things other than images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n### `validateStatus`\n\nThis optional function is used to determine what status codes Thwack uses to return a promise or throw. It is passed the response `status`. If this function returns truthy, the promise is resolved, else the promise is rejected.\n\nThe default function throws for any status not in the 2xx (i.e. 200-299)\n\n### `paramsSerializer`\n\nThis is an optional function which Thwack will call to serialize the `params`. For example, given an object `{a:1, b:2, foo: 'bar'}`, it should serialize to the string `a=1&b=2&foo=bar`.\n\nFor most people, the [default serializer](https://github.com/donavon/thwack/blob/master/src/core/utils/buildUrl/defaultParamSerializer.js) should work just fine. This is mainly for edge case and Axios compatibility.\n\n> Note that the default serializer alphabetizes the parameters, which is a good practice to follow. If, however, this doesn't work for your situation, you can roll your own serializer.\n\n### `resolver`\n\nThis is a function that you can provide to override the [default resolver](https://github.com/donavon/thwack-resolve) behavior. `resolver` takes two arguments: a `url` and a `baseURL` which must be undefined, or an absolute URL. There should be little reason for you to to replace the resolver, but this is your escape hatch in case you need to.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). This value is not effected by `validateStatus`.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with any parent instance(s), as well as with `defaults`.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch` or the `response` from a synthetic event callback.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parent's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\nHere is the event flow for all events. AS you can see, it is possible for your code to get into an endless loop, should your callback blindly issue a `request()` without checking to see if it's already done so, so take caution.\n\n![thwack events](https://user-images.githubusercontent.com/887639/79867660-aee3ce00-83ac-11ea-94fd-4078c1a36244.png)\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n### The `data` event\n\nThe event is fired after the body is streamed and parsed. It is fired only if the fetch returned a 2xx status code. Listeners will receive a `ThwackDataEvent` object with a `thwackResponse` key set to the response.\n\n### The `error` event\n\nThe event is fired after the body is streamed and parsed. It is fired if the fetch returned a non-2xx status code. Listeners will receive a `ThwackErrorEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nNodeJS\n</h2>\n\nThwack will work on NodeJS, but requires a polyfill for `window.fetch`. Luckily, there is a wonderful polyfill called [`node-fetch`](https://github.com/node-fetch/node-fetch) that you can use.\n\nIf you are using NodeJS version 10, you will also need a polyfill for `Array#flat` and `Object#fromEntries`. NodeJS version 11+ has these methods and does not require a polyfill.\n\nYou can either provide these polyfills yourself, or use one of the following convenience imports instead. If you are running NodeJS 11+, use:\n\n```js\nimport thwack from 'thwack/node'; // NodeJS version 12+\n```\n\nIf you are running on NodeJS 10, use:\n\n```js\nimport thwack from 'thwack/node10'; // NodeJS version 10\n```\n\nIf you wish to provide these polyfills yourself, then to use Thwack, you must import from `thwack/core` and set `fetch` as the default for `fetch` as so.\n\n```js\nimport thwack from 'thwack/code';\nthwack.defaults.fetch = global.fetch;\n```\n\nThis should be done in your app startup code, usually `index.js`.\n\n> Note: The `responseType` of `blob` is not supported on NodeJS.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nReact Native\n</h2>\n\nThwack is compatible with React Native and needs no additional polyfills. See below for a sample app written in React Native.\n\n> Note: React Native does not support `stream` due to [#27741](https://github.com/facebook/react-native/issues/27741)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nHow to\n</h2>\n\n### Multiple concurrent requests\n\nYou can use `thwack.all()` and `thwack.spread()` to make simultaneous requests. Data is then presented to your callback as one array.\n\nHere we display information for two GitHub users.\n\n```js\nfunction displayGitHubUsers() {\n  return thwack\n    .all([\n      thwack.get('https://api.github.com/users/donavon'),\n      thwack.get('https://api.github.com/users/revelcw'),\n    ])\n    .then(\n      thwack.spread((...results) => {\n        const output = results\n          .map(\n            ({ data }) => `${data.login} has ${data.public_repos} public repos`\n          )\n          .join('\\n');\n        console.log(output);\n      })\n    );\n}\n```\n\nNote the these are simply helper functions. If you are using `async`/`await` you can write this without the Thwack helpers using `Promise.all`.\n\n```js\nasync function displayGitHubUsers() {\n  const results = await Promise.all([\n    thwack.get('https://api.github.com/users/donavon'),\n    thwack.get('https://api.github.com/users/revelcw'),\n  ]);\n  const output = results\n    .map(({ data }) => `${data.login} has ${data.public_repos} public repos`)\n    .join('\\n');\n  console.log(output);\n}\n```\n\nYou can see this running live in the [CodeSandbox](https://codesandbox.io/s/thwack-allspread-demo-zx2nt?file=/src/index.js:140-642).\n\n(Demo inspired by [this blob post](https://blog.logrocket.com/how-to-make-http-requests-like-a-pro-with-axios/) on axios/fetch)\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options.\n\nIn the browser, you can use the built-in [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).\n\n```js\nimport thwack from 'thwack';\n\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn NodeJS, you can use something like [abort-controller](https://www.npmjs.com/package/abort-controller).\n\n```js\nimport thwack from 'thwack';\nimport AbortController from 'abort-controller';\n\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\nThe `status` in the `ThwackResponse` defaults to 200, so unless you need to mock a non-OK response, you only need to return `data`.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n    // tells Thwack to use the returned value instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above, the caller's request\n    // will be resolved to this `ThwackResponse` (defaults to status of 200 and ok)\n    return new thwack.ThwackResponse(\n      {\n        data: {\n          name: 'Fake Username',\n          email: 'fakeuser@example.com',\n        },\n      },\n      options\n    );\n  }\n});\n```\n\n### Convert DTO to Model\n\nOften it is desirable to convert a DTO (Data Transfer Object) into something easier to consume by the client. In this example below, we convert a complex DTO into `firstName`, `lastName`, `avatar`, and `email`. Other data elements that are returned from the API call, but not needed by the applications, are ignored.\n\nYou can see an example of DTO conversion, logging, and returning fake data in this sample app.\n\n<div>\n<img src=\"https://user-images.githubusercontent.com/887639/79865700-94f4bc00-83a9-11ea-9de6-9f204d5857c8.png\" width=\"500\" height=\"526\" alt=\"Mickey Mouse sample app\">\n</div>\n\nYou can [view the source code](https://codesandbox.io/s/sharp-chatelet-mje3w?file=/src/App.js) on CodeSandbox.\n\n### Load an Image as a Blob\n\nIn this example, we have a React Hook that loads an image as a Blob URL. It caches the URL to Blob URL mapping in session storage. Once loaded, any refresh of the page will instantaneously load the image from Blob URL.\n\n```js\nconst useBlobUrl = (imageUrl) => {\n  const [objectURL, setObjectURL] = useState('');\n\n  useEffect(() => {\n    let url = sessionStorage.getItem(imageUrl);\n\n    async function fetchData() {\n      if (!url) {\n        const { data } = await thwack.get(imageUrl, {\n          responseType: 'blob',\n        });\n        url = URL.createObjectURL(data);\n        sessionStorage.setItem(imageUrl, url);\n      }\n      setObjectURL(url);\n    }\n\n    fetchData();\n  }, [imageUrl]);\n\n  return objectURL;\n};\n```\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRight now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint to a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n### React Native sample app\n\nAlong with `use-thwack`, writing a data fetching app for React Native couldn't be easier.\n\nView the entire app [running on Expo](#).\n\n<img alt=\"good dog app\" src=\"https://user-images.githubusercontent.com/887639/80429496-3dc77d80-88ba-11ea-84e2-ebe9a2d69cc8.png\" height=\"800\" width=\"380\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nLicense\n</h2>\n\nLicensed under [MIT](LICENSE)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nContributors ✨\n</h2>\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://kojikanao.netlify.com/\"><img src=\"https://avatars0.githubusercontent.com/u/474225?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Koji</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/tomByrer\"><img src=\"https://avatars2.githubusercontent.com/u/1308419?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tom Byrer</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=tomByrer\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://iansutherland.ca/\"><img src=\"https://avatars2.githubusercontent.com/u/433725?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ian Sutherland</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=iansu\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https:///www.blakeyoder.com\"><img src=\"https://avatars0.githubusercontent.com/u/5393338?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Blake Yoder</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=blakeyoder\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://www.ryanhinchey.co\"><img src=\"https://avatars0.githubusercontent.com/u/3943764?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ryan Hinchey</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=ryhinchey\" title=\"Documentation\">📖</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.5.0-beta.1","_nodeVersion":"10.19.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-BTYUPFsjjmhUiL2nXypnse01F2GlWTAkJLyArsoLEE3ZZFzZXvrzazMjRm87JmW1FwsWy7tIixbTbQJf63A3og==","shasum":"cbe13ef3d06fd696ad585c758d6c11c3be8c0123","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.5.0-beta.1.tgz","fileCount":12,"unpackedSize":137690,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeqB6TCRA9TVsSAnZWagAArnEP/1KJd685ls+6KMrPgVqQ\n7UAWyUq4bHjdlN1+uwcDMRFfgogk7711eQgqO0p2tPXEvrt3SxGBLll3Uicg\nPNvxDBo5TJeWY9A7AfqpVhFc4IfMvjo91SNTyIIZT1UUyevO2FYJ4paFe2j4\nWAdPmLx35+aS/mjk1EJcDAUX8N9+WrN2hIKLrxSHmOXHA8gvpBxN2CugsRqw\nJNzzI7u/kWd0oUAoM9riaWn8qn563cu1E6cHCzA9QcR5b7tUOSOVloROxaGi\nYOHchOKHHxSlMRjmuLsWfGcGa2Xe6EwZUTX8JBYcB4vBfMpnrK1iGfO7DU89\nmYnqz1dyTlci572CureZU1lYscHitP2/72W9LTsIfn0IvQNnrupckTgC/DAh\n0bdKZ1nWk8UR/HfGlIX5KyStZK1fJihY7t4m+Sc+aHz6M3zfgpRItInEJBMm\nlIHqQEx8l4L01Xr60H/zs63o9vLNKMTxraPvTlE9ZWYDH6PpmAPgBnmXUZBe\nYJNxxSRG5Ru8Fj2ZhxQBX3ZGLmfb7awUuZc2fEDg5U2fW00+vccHRUzuzOvU\npeWo5J0WKYzd/SXSJ0Zti8oFR5bU60+wex2OoH4/pgOQvygFxaRrqrlDG6M9\nz/Ofh0ZkK6tMTVDPq7jYsHca1pttnj47P0con6qCCv89S9TiHQno2mlCCJfU\nOsw0\r\n=QKyh\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQC69alrsXt4ri8OPP5Yxex/kDSd3K2nyuSkOMUeJeN22AIhAOJy3cO6SrwH2m3Q7FxhNF4mZgAQzx2iDMdUmf7MEpw4"}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.5.0-beta.1_1588076178572_0.7956846310523413"},"_hasShrinkwrap":false},"0.5.0":{"name":"thwack","publishConfig":{"access":"public","tag":"latest"},"version":"0.5.0","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/default/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && rimraf dist && npm t","build":"microbundle"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise","nodejs","react-native"],"engines":{"node":">=10"},"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"@thwack/resolve":"^1.0.0","core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.9.0","@babel/preset-env":"^7.9.5","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.4.0","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.1.0","eslint-plugin-import":"^2.20.2","eslint-plugin-jest":"^23.8.2","jest":"^25.4.0","microbundle":"^0.11.0","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"2a7bbc92c21bc158f7b20938eb5f52eca2529c70","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.5.0","_nodeVersion":"10.20.1","_npmVersion":"6.14.4","dist":{"integrity":"sha512-pv36ZiM/r+5IbEXG3bTBTeigHLGJQP6pkFwZ2Bufq3IkcFHFokS8/OfEIkvi5P3OjYrUJU2I3O8x32w9M9SFQQ==","shasum":"6a792c84b171760cb169aed071b379c584fc939c","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.5.0.tgz","fileCount":12,"unpackedSize":137876,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeqDoOCRA9TVsSAnZWagAAqd4QAJmeosLbN2W2peKSHh0h\n3kuju0DVBQ70eSWwt1BjXgks/ubcJ/zcvIx1SAnc+vq/ZNDHdYQd9rjZ7KK9\n5wIBC1SPRR7yxVFi9qmc79xjuU7+KY/SA5c2O+LoJTAtk78kHSARi5S9qvfk\nLuBcLLKmwlKSogvBsg1g0IHBOP7D/q2OUhPZt/Ak24Xn+8WDmTkCuOefyzkr\ntBZ2AHEY+Mf9JfKOqF5zijA+yYMN5JsW7B5/xUzjDrFtDPcYXR4Dqo0pHFl6\nte8c6A1Cn248z1AjBqWOt0ust+p7VgGCygiYyzcOnTHTCrO0HKLX4HswkzFw\nzc8EBe3aFPtREkAV5AmArhmpeSrvY6/A302ZbbBD067b8JX9gJRzOsoYI68X\n1bCMJaC7hktMaa3NsDqPl1NbIWJmyuTK/Q64GOpFEhPpHxTxK9kGLi756OFD\n0lAB20kiLH9tKj1loE/zuJarBu46rcemZ82cHiGc8A8fTnlpCphMdMCH4fWN\n+YuM2bjoLURNriXxaIKXoygM+eMdmFciiH8r7AN2HGlvMLaQuxMfSkdNGsul\npcvhHD4aUdlQ3Zwfi61Tz4eqTzmjUCHELPLmyqjU2mZsZ2S4UrCC4dIrtaYi\nY9lA/AzRPcO21VpSGe34bFCA6IvxzwveXNzEKTVMVraNUBLCrLV2m54ET+VW\nHco3\r\n=AaO0\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCV1eu+fSD5+HFBVWE0VraeBwMF8KpYD90cqyop6VOTwgIhAIQTIj09dvkhaKEXpHUvNr58vjNfTuAmXcg095QC3T0M"}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.5.0_1588083213307_0.7482780692783573"},"_hasShrinkwrap":false},"0.6.0":{"name":"thwack","publishConfig":{"access":"public","tag":"latest"},"version":"0.6.0","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/default/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && rimraf dist && npm t","build":"microbundle"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise","nodejs","react-native"],"engines":{"node":">=10"},"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"@thwack/resolve":"^1.0.0","core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.10.4","@babel/preset-env":"^7.10.4","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.5.1","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.2.0","eslint-plugin-import":"^2.22.0","eslint-plugin-jest":"^23.17.1","jest":"^25.5.4","microbundle":"^0.12.2","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"f7ff05751dcbc02e97418181a216931408c88a62","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.6.0","_nodeVersion":"10.21.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-RbtE42skZ81QPt+jXkK2xzutIA++i1bBNszeM2T6BsHjXYEQlS/7ulBLhf5U85czJmiiZKL/Vno0+wP6r6D8Ag==","shasum":"404841b05691a5f9ecb0be82541c1f0d70bb0e9f","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.6.0.tgz","fileCount":16,"unpackedSize":169997,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJe/LrgCRA9TVsSAnZWagAAtkYP/0NczWJYRRKAh/Fy1QpN\nrXPFQOmP3OsKQbFfp13fTNqat3qXLLm+b5yNfXCnkjf+XgOnwWf8D+hMXHgF\nO/6RZxxayHpHNCY2HcCQ37p0It6rhzwVXsZYuSuVGUfkAaGoehjrZii2jbpq\nrwrBX8JrPDBPmy1Cmx1nuIbCN+MgxKlw9VamizdA83J+bDelnut6igFwV/Sj\n2yRKOS7EnG54TPFZNv4vhdxdJFiIO8W0t7wEWQ8q9+f8MOn/+xqWCbPk9Xfc\nGbPvQkaALOV2UP2TSd4V+9sRMgUQ7IC5iaIOghg+Ht+wrRZAU18M78AOnJnG\nTB3qSX3lJ2FB+kbRLlftMdnt/1oaJO373tbgNLK+YvC390SVoe9Qd2vLlvB8\na1ffuQ5XCgApdUdS04i8Zj04hgLSKjxhuBf3ocMwmmLJux1clTBd2bHgG/Xu\nGNlkPS4F2FrJvdjNbVfRGpr/2ovGj5RVlVf3pRcgdQnJhYnm9UFnUPrZbwFp\n1nk1044h96aTEvJRaUFgFJjplGhD23755f1gdzR3H8B6+VOTDjY+LfvOLyzN\np5/ebhNkymrHcKen6gH7a0S/z8C8l7ljsn4xA4ezrpL04pid4tqFT+HsUAb3\nH90w4y3r1tzB3YprbzTKYZgPBWFU3QlPhWNpBCDah6hb/IILllUx0t1qEDi+\nDWXn\r\n=OcZf\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIA5bvA+epbn45qjIQzmSs7Wc2Q4crRDc62GDnV9q0zntAiEA8EBb8ER66TmcWtLzSrHkIlosbPQy1BOmLWWd9xQnbFM="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.6.0_1593621216232_0.9641871063125773"},"_hasShrinkwrap":false},"0.6.1":{"name":"thwack","publishConfig":{"access":"public","tag":"latest"},"version":"0.6.1","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/default/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && rimraf dist && npm t","build":"microbundle"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise","nodejs","react-native"],"engines":{"node":">=10"},"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"@thwack/resolve":"^1.0.0","core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.10.5","@babel/preset-env":"^7.10.4","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.5.1","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.2.0","eslint-plugin-import":"^2.22.0","eslint-plugin-jest":"^23.18.0","jest":"^25.5.4","microbundle":"^0.12.3","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"08bc425cab601d1498f506c70affa6e8976a35d9","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.6.1","_nodeVersion":"10.22.0","_npmVersion":"6.14.6","dist":{"integrity":"sha512-yMELIIh98LJ6CKceKiJCis3JQ5TXL+dwtPwhbvUo+DmqGP2KkFTrmQ8JLgt/weZGw4DXWlNWEJKXHpB7jL9J/g==","shasum":"9d6e4628e0188d9016eebf8082cb30b28595cbda","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.6.1.tgz","fileCount":16,"unpackedSize":170776,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfGHL6CRA9TVsSAnZWagAAdxkQAKC3WPyorJSXzndeop+w\nNw6XLIowmOQG2X3MV+yKSPOOdNCsW5W5XFjThQPbqBUXyrFoCfnNI0yOctdh\niHM0jcvG93VC+ccmZVGr/kZUFFeow/G1UHEx89uxdl+qbt4JccWEL7vwWjlN\nSJygo9W+jwH57+m7W1qjUZrWVoY6cEf5+NPDNVOmFgo4tRRygRelQVm1dSUO\nwYv9lRixe+d4HaZ1DoWxs3lRY/fNoI9lNUJ3hwuAF2U1lmIME4OrTSjhCoxW\nhLZavpMCQYYXmvGTFzBK2dRlUFf61W6m6ItQP3/3QgKyO9XtTH+8HktlaitO\niZVW8/xkUcZDvPbFwKHKOxYYbSymFAfDJoGr02//TUd2DhZeCRnFaBBaGZpr\nhkoMIdtWwq1QxlsOwU+SVL08FjA0IHDJ/SikEWRtxiCIr+grghALpekGi1iC\ngLZDUvAo2GuaTkLWg0ZsEG7VpNnSMP95dOdGiElOSpdNpspQqlwpolT7rOkQ\nMXvkQ4Nj12YT3ikLC3ESLrBOx54Cvd1ZYVTiVCGWuOpaeYIP1A6bkoMKOsPV\nDD7bfeXZ3IFcFAUw7ZERRaDrKrS3243doBPwLNxTraNNkTs/CsmyAHU8ISDK\nGANB+aU2BGCP2wdhKuP6wqCEnlsCnR8965zmLWOYpP5DreEUy5eVq0Z/3/Xx\n3T76\r\n=R3AX\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCICLSl/0KQGpL1TXUssfg94oS45F+1ztS+MmiQl6/jWEjAiEAh5H7tTfw3yBUshIQ69OKOeq9ybHdRmAlA9AvWJOsRik="}]},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.6.1_1595437818208_0.0714305139333209"},"_hasShrinkwrap":false},"0.7.0-beta.1":{"name":"thwack","publishConfig":{"access":"public","tag":"next"},"version":"0.7.0-beta.1","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/default/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && rimraf dist && npm t","build":"microbundle"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise","nodejs","react-native"],"engines":{"node":">=10"},"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"@thwack/resolve":"^1.0.0","core-js":"^3.6.5","node-fetch":"^2.6.0"},"devDependencies":{"@babel/core":"^7.10.5","@babel/preset-env":"^7.10.4","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.5.1","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.2.0","eslint-plugin-import":"^2.22.0","eslint-plugin-jest":"^23.18.0","jest":"^25.5.4","microbundle":"^0.12.3","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack)\n[![All Contributors](https://img.shields.io/badge/all_contributors-11-orange.svg?style=flat-square)](#contributors-)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern — Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n- 🅰️ Typed — Easier inclusion for TypeScript projects\n- ✨ Support for NodeJS 10 and 12\n- 📱 Support for React Native\n\n> Note: Read [important note](#breaking-change) about a breaking change in v0.7\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nBreaking Change in v0.7?<a name=\"breaking-change\"></a>\n</h2>\n\nStarting with v0.7, a Thwack listener no longer returns new information, but instead calls `event.setPayload()` with the new data. This is because TypeScript does not allow return data from event listeners.\n\nOld:\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  const resp = new thwack.ThwackResponse({ data: fakeData }, options);\n  return resp;\n});\n```\n\nNew:\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  const resp = new thwack.ThwackResponse({ data: fakeData }, options);\n  event.setPayload(resp);\n});\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\n~~Thwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.~~\n\nScratch that. Thwack provides the same level of power as Axios with a much smaller footprint. And Thwack's promise based event system is easier to use.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nMethods\n</h2>\n\nThe following methods are available on all Thwack instances.\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n  Thwacks URL resolution is [RFC-3986](https://tools.ietf.org/html/rfc3986#appendix-B) compliant. Axios's is not. It's powered by [`@thwack/resolve`](https://www.npmjs.com/package/@thwack/resolve).\n\n### Event listeners\n\nThwack supports the following event types: `request`, `response`, `data`, and `error`.\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nStatic Methods\n</h2>\n\n### Concurrency\n\nThwack has the following helper functions for making simultaneous requests. They are mostly for Axios compatibility. See the \"[How To](#how-to)\" section below for example usage.\n\n- `thwack.all(Promise<ThwackResponse>[])`\n\n- `thwack.spread(callback<results>)`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Must be an absolute URL or `undefined`. Defaults to the `origin` + `pathname` of the current web page if running in a browser or `undefined` on Node or React Native.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `defaults`\n\nThis allows you to read/set the default options for this instance and, in effect, any child instances.\n\nExample:\n\n```js\nthwack.defaults.baseURL = 'https://example.com/api';\n```\n\nFor an instance, `defaults` is the same object passed to `create`. For example, the following will output \"https://example.com/api\".\n\n```js\nconst instance = thwack.create({\n  baseURL: 'https://example.com/api',\n});\nconsole.log(instance.defaults.baseURL);\n```\n\nAlso note that setting `defaults` on an instance (or even passing `options`) to an instance does NOT effect the parent. So for the following example, `thwack.defaults.baseURL` will still be \"https://api1.example.net/\".\n\n```js\nthwack.defaults.baseURL = 'https://api1.example.net/';\nconst instance = thwack.create();\ninstance.defaults.baseURL = 'https://example.com/api';\n\nconsole.log(thwack.defaults.baseURL);\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `maxDepth`\n\nThe maximum level of recursive requests that can be made in a callbck before Thwack throws an error. This is used to prevent an event callback from causing a recursive loop, This if it issues another `request` without proper safeguards in place. Default = 3.\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n|  `text/event-stream`  |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*/*`         |     `text`     |                    `response.text()`                     |\n\n> Note: `stream` is currently unsupported in React Native due to [#27741](https://github.com/facebook/react-native/issues/27741)\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between the resulting `content-type` from the response header and the parser type.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `*/*` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \"*/*\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could set the `baseURL` to your API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\nimport thwack from 'thwack';\n\nthwack.defaults.responseParserMap = { 'image/*': 'blob' };\n```\n\nAny URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nconst getBlobUrl = async (url) => {\n  const blob = (await thwack.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox](https://codesandbox.io/s/load-image-as-blob-410uq).\n\n> Note that you can use this technique for other things other than images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n### `validateStatus`\n\nThis optional function is used to determine what status codes Thwack uses to return a promise or throw. It is passed the response `status`. If this function returns truthy, the promise is resolved, else the promise is rejected.\n\nThe default function throws for any status not in the 2xx (i.e. 200-299)\n\n### `paramsSerializer`\n\nThis is an optional function which Thwack will call to serialize the `params`. For example, given an object `{a:1, b:2, foo: 'bar'}`, it should serialize to the string `a=1&b=2&foo=bar`.\n\nFor most people, the [default serializer](https://github.com/donavon/thwack/blob/master/src/core/utils/buildUrl/defaultParamSerializer.js) should work just fine. This is mainly for edge case and Axios compatibility.\n\n> Note that the default serializer alphabetizes the parameters, which is a good practice to follow. If, however, this doesn't work for your situation, you can roll your own serializer.\n\n### `resolver`\n\nThis is a function that you can provide to override the [default resolver](https://github.com/donavon/thwack-resolve) behavior. `resolver` takes two arguments: a `url` and a `baseURL` which must be undefined, or an absolute URL. There should be little reason for you to to replace the resolver, but this is your escape hatch in case you need to.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). This value is not effected by `validateStatus`.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with any parent instance(s), as well as with `defaults`.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch` or the `response` from a synthetic event callback.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parent's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\nHere is the event flow for all events. AS you can see, it is possible for your code to get into an endless loop, should your callback blindly issue a `request()` without checking to see if it's already done so, so take caution.\n\n![thwack events](https://user-images.githubusercontent.com/887639/79867660-aee3ce00-83ac-11ea-94fd-4078c1a36244.png)\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n### The `data` event\n\nThe event is fired after the body is streamed and parsed. It is fired only if the fetch returned a 2xx status code. Listeners will receive a `ThwackDataEvent` object with a `thwackResponse` key set to the response.\n\n### The `error` event\n\nThe event is fired after the body is streamed and parsed. It is fired if the fetch returned a non-2xx status code. Listeners will receive a `ThwackErrorEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nNodeJS\n</h2>\n\nThwack will work on NodeJS, but requires a polyfill for `window.fetch`. Luckily, there is a wonderful polyfill called [`node-fetch`](https://github.com/node-fetch/node-fetch) that you can use.\n\nIf you are using NodeJS version 10, you will also need a polyfill for `Array#flat` and `Object#fromEntries`. NodeJS version 11+ has these methods and does not require a polyfill.\n\nYou can either provide these polyfills yourself, or use one of the following convenience imports instead. If you are running NodeJS 11+, use:\n\n```js\nimport thwack from 'thwack/node'; // NodeJS version 12+\n```\n\nIf you are running on NodeJS 10, use:\n\n```js\nimport thwack from 'thwack/node10'; // NodeJS version 10\n```\n\nIf you wish to provide these polyfills yourself, then to use Thwack, you must import from `thwack/core` and set `fetch` as the default for `fetch` as so.\n\n```js\nimport thwack from 'thwack/code';\nthwack.defaults.fetch = global.fetch;\n```\n\nThis should be done in your app startup code, usually `index.js`.\n\n> Note: The `responseType` of `blob` is not supported on NodeJS.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nReact Native\n</h2>\n\nThwack is compatible with React Native and needs no additional polyfills. See below for a sample app written in React Native.\n\n> Note: React Native does not support `stream` due to [#27741](https://github.com/facebook/react-native/issues/27741)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nHow to\n</h2>\n\n### Multiple concurrent requests\n\nYou can use `thwack.all()` and `thwack.spread()` to make simultaneous requests. Data is then presented to your callback as one array.\n\nHere we display information for two GitHub users.\n\n```js\nfunction displayGitHubUsers() {\n  return thwack\n    .all([\n      thwack.get('https://api.github.com/users/donavon'),\n      thwack.get('https://api.github.com/users/revelcw'),\n    ])\n    .then(\n      thwack.spread((...results) => {\n        const output = results\n          .map(\n            ({ data }) => `${data.login} has ${data.public_repos} public repos`\n          )\n          .join('\\n');\n        console.log(output);\n      })\n    );\n}\n```\n\nNote the these are simply helper functions. If you are using `async`/`await` you can write this without the Thwack helpers using `Promise.all`.\n\n```js\nasync function displayGitHubUsers() {\n  const results = await Promise.all([\n    thwack.get('https://api.github.com/users/donavon'),\n    thwack.get('https://api.github.com/users/revelcw'),\n  ]);\n  const output = results\n    .map(({ data }) => `${data.login} has ${data.public_repos} public repos`)\n    .join('\\n');\n  console.log(output);\n}\n```\n\nYou can see this running live in the [CodeSandbox](https://codesandbox.io/s/thwack-allspread-demo-zx2nt?file=/src/index.js:140-642).\n\n(Demo inspired by [this blob post](https://blog.logrocket.com/how-to-make-http-requests-like-a-pro-with-axios/) on axios/fetch)\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options.\n\nIn the browser, you can use the built-in [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).\n\n```js\nimport thwack from 'thwack';\n\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn NodeJS, you can use something like [abort-controller](https://www.npmjs.com/package/abort-controller).\n\n```js\nimport thwack from 'thwack';\nimport AbortController from 'abort-controller';\n\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\nThe `status` in the `ThwackResponse` defaults to 200, so unless you need to mock a non-OK response, you only need to return `data`.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n    // tells Thwack to use the returned value instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above, the caller's request\n    // will be resolved to this `ThwackResponse` (defaults to status of 200 and ok)\n    const resp = new thwack.ThwackResponse(\n      {\n        data: {\n          name: 'Fake Username',\n          email: 'fakeuser@example.com',\n        },\n      },\n      options\n    );\n    event.setPayload(resp);\n  }\n});\n```\n\n### Convert DTO to Model\n\nOften it is desirable to convert a DTO (Data Transfer Object) into something easier to consume by the client. In this example below, we convert a complex DTO into `firstName`, `lastName`, `avatar`, and `email`. Other data elements that are returned from the API call, but not needed by the applications, are ignored.\n\nYou can see an example of DTO conversion, logging, and returning fake data in this sample app.\n\n<div>\n<img src=\"https://user-images.githubusercontent.com/887639/79865700-94f4bc00-83a9-11ea-9de6-9f204d5857c8.png\" width=\"500\" height=\"526\" alt=\"Mickey Mouse sample app\">\n</div>\n\nYou can [view the source code](https://codesandbox.io/s/sharp-chatelet-mje3w?file=/src/App.js) on CodeSandbox.\n\n### Load an Image as a Blob\n\nIn this example, we have a React Hook that loads an image as a Blob URL. It caches the URL to Blob URL mapping in session storage. Once loaded, any refresh of the page will instantaneously load the image from Blob URL.\n\n```js\nconst useBlobUrl = (imageUrl) => {\n  const [objectURL, setObjectURL] = useState('');\n\n  useEffect(() => {\n    let url = sessionStorage.getItem(imageUrl);\n\n    async function fetchData() {\n      if (!url) {\n        const { data } = await thwack.get(imageUrl, {\n          responseType: 'blob',\n        });\n        url = URL.createObjectURL(data);\n        sessionStorage.setItem(imageUrl, url);\n      }\n      setObjectURL(url);\n    }\n\n    fetchData();\n  }, [imageUrl]);\n\n  return objectURL;\n};\n```\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRight now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint to a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  const options = { ...event.options, url: newUrl }; // replace `options`]\n  event.setPayload(options);\n});\n```\n\n### React Native sample app\n\nAlong with `use-thwack`, writing a data fetching app for React Native couldn't be easier.\n\nView the entire app [running on Expo](https://snack.expo.io/@donavon/random-dog).\n\n<img alt=\"good dog app\" src=\"https://user-images.githubusercontent.com/887639/80429496-3dc77d80-88ba-11ea-84e2-ebe9a2d69cc8.png\" height=\"800\" width=\"380\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nLicense\n</h2>\n\nLicensed under [MIT](LICENSE)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nContributors ✨\n</h2>\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://kojikanao.netlify.com/\"><img src=\"https://avatars0.githubusercontent.com/u/474225?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Koji</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/tomByrer\"><img src=\"https://avatars2.githubusercontent.com/u/1308419?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tom Byrer</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=tomByrer\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://iansutherland.ca/\"><img src=\"https://avatars2.githubusercontent.com/u/433725?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ian Sutherland</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=iansu\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https:///www.blakeyoder.com\"><img src=\"https://avatars0.githubusercontent.com/u/5393338?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Blake Yoder</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=blakeyoder\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://www.ryanhinchey.co\"><img src=\"https://avatars0.githubusercontent.com/u/3943764?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ryan Hinchey</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=ryhinchey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/MiroDojkic\"><img src=\"https://avatars2.githubusercontent.com/u/9119913?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Miro Dojkic</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=MiroDojkic\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://github.com/santicevic\"><img src=\"https://avatars3.githubusercontent.com/u/45316219?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>santicevic</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=santicevic\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=santicevic\" title=\"Code\">💻</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","gitHead":"48ff2663640e2c7165b7c831a474b4a663eaa803","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.7.0-beta.1","_nodeVersion":"14.14.0","_npmVersion":"6.14.9","dist":{"integrity":"sha512-ZBdF8dVsvFJJjBgSUmlT47oot4nkGXBtpj05W+3QOGfzOiD2Ym3zEpOwXnW/086QOb8uYCyvS288ofZTLRkmRA==","shasum":"ef4911292c391bcc6921de9241b63726642b167d","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.7.0-beta.1.tgz","fileCount":16,"unpackedSize":169889,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJfu7HACRA9TVsSAnZWagAAGG4P/RPYTU6H6Ab4qs7eM67t\nFLUCosiuJHmg6lUHOUlSXzA9RkStjoBaRVsvnVOFiocW4jKuYl+rPOI7Xv0M\nIc8fJS0IqxFChGkjGyuxvFlXCfDecWod2iGIEJ9sNX5nTiGIrpy0DSB5vVbj\n+A6U1Y8K276/nauGEEZQD3GAA8wNCnwx4hOps3+2wcL5TM6NB9+C+Xi/uWe0\nomVqotQ9RwZkjLEDjwvkgJRkliGq7EfzbjOq4zJ/0DXDYEbjbZQZiP3vgEMg\nQYeitBDH00bssRIIL4NlCUQqQc3MoWhoXc32U0So5LUAj5kFhsqdkSOmCUoN\n/G8G7+qgjsbQfarfqlMaFOu/xlA64qkYlhhCcD1ui3Y9na8irY4lQFCf+E2x\nCwuriGu0qnxUk1X6ZwnrrpktHsKI1pE34gEMtCq50fE4ahMMwfyNCGYJORRz\nKZWePv2DHj6DbcSsGTA7Hl3+X21aGLEOUzcR4e7mOo94RxukBovxCS6dBvB+\n5l1XmpkzuasyjNPDifRCTjIoIuVJ2stPnCWiPf2jb/AsWro80K1XCAW4W7pQ\naiRIAdf0DxvWXUmiU14+06Ql9Q3otXjws16TqVePBU09uKQnj+chZMI5w2Zs\nT7fBfP3D/r0Nm6zuI4YOqugcWOK1gkARFCEkJ87hbamcDT0yQByJWitsGFO5\nwPoB\r\n=pRiM\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCaJOmaac7/onRVttmkFxP2c7J1i06kRECyzk/+sekjZgIgdnU2Yb2w4XWOYQ/IiwKOjPeAWMzd8Jbcn782yHSe3MI="}]},"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.7.0-beta.1_1606136255799_0.583858761799591"},"_hasShrinkwrap":false},"0.6.2":{"name":"thwack","publishConfig":{"access":"public","tag":"latest"},"version":"0.6.2","description":"A tiny modern data fetching solution.","main":"dist/thwack.js","umd:main":"dist/thwack.umd.js","module":"dist/thwack.m.js","source":"src/default/index.js","types":"types/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"scripts":{"prepublishOnly":"npm run build","lint":"eslint src","test":"jest --verbose --coverage --silent","test:watch":"jest --watch --runInBand --silent","prebuild":"npm run lint && rimraf dist && npm t","build":"microbundle"},"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise","nodejs","react-native"],"engines":{"node":">=10"},"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"dependencies":{"@thwack/resolve":"^1.0.0","core-js":"^3.6.5","node-fetch":"^2.6.1"},"devDependencies":{"@babel/core":"^7.10.5","@babel/preset-env":"^7.10.4","babel-core":"^7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^25.5.1","eslint":"^6.8.0","eslint-config-airbnb-base":"^14.2.0","eslint-plugin-import":"^2.22.0","eslint-plugin-jest":"^23.18.0","jest":"^25.5.4","microbundle":"^0.12.3","rimraf":"^3.0.2"},"jest":{"collectCoverageFrom":["**/src/**/*.js"],"coverageThreshold":{"global":{"branches":100,"functions":100,"lines":100,"statements":100}}},"gitHead":"66a3f605ac8add3283bfa26a56d3cc34f3e06e6c","bugs":{"url":"https://github.com/donavon/thwack/issues"},"homepage":"https://github.com/donavon/thwack#readme","_id":"thwack@0.6.2","_nodeVersion":"14.14.0","_npmVersion":"6.14.11","dist":{"integrity":"sha512-6yCf21Zb0NV4ayN/A7z4IsEqINJXn6INj7bEvZQsRWQacWSp47raMQKaQiNu3ttTmMqN/P/qjY3j24xbYg30qA==","shasum":"bd0542a9c74d51c5cce0d47df4efb655fac10a0b","tarball":"https://registry.npmjs.org/thwack/-/thwack-0.6.2.tgz","fileCount":16,"unpackedSize":170808,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJgY7G1CRA9TVsSAnZWagAA6TQP/RgPBeeE5ltUpk1jkZtJ\nsGWFXjQ7hDmjXMiqfsfaOXKIDrgg58NjQzQP2vDRPjes+ocQ1VN9D2FH56qD\nRVbJe6fkznMHI0ypAFR/pB7TEDXyYOMKPInx3NfMUtBbDrTWLTDyu0Otdpb6\n+Rj/Vq9yc09OrxIj02DMoxry9kMpxvtUxV/4D2CixqulIJR+G9nGm2wuJ7LU\nh8nsZTM2aUjBELVa1LJo676MmxCSjRTn5qr9pMAGWMJGuPDsEZeNsC9zSv3A\nSkwtHcaDd7kGV51HlB1mk+ZipVP+waB6OMepbsQsWtsom/0hZQqmitQ4sTG3\nI85LUYjUImAhS8IytrY6GQ4opabfninIm6dkhLojD6TOaltfX6asahsM14QD\nFqqr3izUT1V20UTtF7OxeZNIfAk7dbdZStiFtZI1VEqO1vpA/gZW88R/kqNK\n6Uh2sVgBzO4O9UVOcp9JIqEK/wwxN5IU6Wi2ZHgzwG6B3+IP8asb/JuRsTs7\nkFFE/jNceHmPJP/E3mqgooGWk1wLzW8aXOTfO2WuAgm5oDV0E1FFyGSSsCKW\n8zxlqMu7kSYUNaY8x1buZ5O12K4gNSL7t2R4WZtandGWjKHeUbvPtSMenc39\n1bcBQ0mjkh8DBQFpYprjtbvIGWLGAMZUsITsxCV6r0P6rGMMRjb6ae+PwJvt\nLHun\r\n=5HrQ\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCdeufJbKDSMfA6hHn15kYqYbGRyu25TxGDFpwH9Q20xQIhAMJ/M6p3WZSgGC+QcnzForCQMV9BPGb2CiUoY7oMpkcy"}]},"_npmUser":{"name":"donavon","email":"github@donavon.com"},"directories":{},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/thwack_0.6.2_1617146292886_0.475422334933461"},"_hasShrinkwrap":false}},"time":{"created":"2020-04-10T00:08:23.034Z","0.0.0":"2020-04-10T00:08:23.138Z","modified":"2022-05-21T19:46:26.504Z","0.1.0-alpha.0":"2020-04-10T18:04:34.241Z","0.1.0-alpha.1":"2020-04-10T19:58:03.038Z","0.1.0-alpha.2":"2020-04-10T22:57:41.343Z","0.1.0-alpha.3":"2020-04-10T23:02:13.037Z","0.1.0-alpha.4":"2020-04-11T14:03:57.428Z","0.1.0-alpha.5":"2020-04-11T18:28:28.915Z","0.1.0-alpha.6":"2020-04-12T07:55:30.559Z","0.1.0-alpha.7":"2020-04-14T01:12:13.508Z","0.1.0-alpha.8":"2020-04-14T17:18:22.307Z","0.1.0-alpha.9":"2020-04-14T22:23:53.945Z","0.1.0-alpha.10":"2020-04-15T13:14:41.894Z","0.1.0-beta.1":"2020-04-15T16:38:47.308Z","0.1.0":"2020-04-15T20:12:54.018Z","0.1.1-beta.1":"2020-04-16T19:22:07.805Z","0.1.1-beta.2":"2020-04-16T21:25:57.638Z","0.2.0-beta.1":"2020-04-20T15:32:39.836Z","0.2.0-beta.2":"2020-04-20T16:39:15.750Z","0.2.0-beta.3":"2020-04-20T17:22:40.621Z","0.2.0-beta.4":"2020-04-20T19:26:43.090Z","0.2.0":"2020-04-21T13:09:32.434Z","0.2.1":"2020-04-21T13:30:06.397Z","0.2.2":"2020-04-21T21:39:32.055Z","0.2.3":"2020-04-22T04:56:37.785Z","0.2.4":"2020-04-22T14:40:28.015Z","0.2.5-beta.1":"2020-04-22T15:47:52.979Z","0.2.5-beta.2":"2020-04-22T16:19:48.624Z","0.2.5-beta.3":"2020-04-22T17:23:04.796Z","0.3.0":"2020-04-22T17:58:26.430Z","0.3.1":"2020-04-22T21:01:56.559Z","0.4.0-beta.1":"2020-04-23T06:32:13.498Z","0.4.0":"2020-04-23T15:08:25.270Z","0.4.1-beta.1":"2020-04-23T16:24:03.952Z","0.4.1-beta.2":"2020-04-23T17:02:16.853Z","0.4.1-beta.3":"2020-04-24T19:30:29.204Z","0.4.1-beta.4":"2020-04-26T19:42:49.527Z","0.4.1-beta.5":"2020-04-26T19:54:40.812Z","0.4.1-beta.6":"2020-04-27T13:42:29.016Z","0.4.1-beta.7":"2020-04-27T13:55:07.890Z","0.4.1-beta.8":"2020-04-27T14:54:39.519Z","0.5.0-beta.1":"2020-04-28T12:16:18.746Z","0.5.0":"2020-04-28T14:13:33.565Z","0.6.0":"2020-07-01T16:33:36.388Z","0.6.1":"2020-07-22T17:10:18.323Z","0.7.0-beta.1":"2020-11-23T12:57:35.986Z","0.6.2":"2021-03-30T23:18:13.084Z"},"maintainers":[{"name":"donavon","email":"github@donavon.com"}],"keywords":["utils","lib","fetch","axios","xmlhttprequest","xhr","http","ajax","promise","nodejs","react-native"],"author":{"name":"Donavon West","email":"github@donavon.com","url":"https://github.com/donavon"},"license":"MIT","readme":"<p align=\"center\">\n  <img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79361317-23cd8880-7f13-11ea-9a80-94f1d7e2eb93.png\" width=\"640\">\n</p>\n\n<h1 align=\"center\">\nThwack. A tiny modern data fetching solution\n</h1>\n\n[![npm version](https://badge.fury.io/js/thwack.svg)](https://badge.fury.io/js/thwack)\n[![Build Status](https://travis-ci.com/donavon/thwack.svg?branch=master)](https://travis-ci.com/donavon/thwack)\n[![All Contributors](https://img.shields.io/badge/all_contributors-11-orange.svg?style=flat-square)](#contributors-)\n[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20Thwack%21%20A%20tiny%20modern%20data%20fetching%20solution.&url=https://github.com/donavon/thwack&via=donavon&hashtags=javascript)\n[![Github stars](https://img.shields.io/badge/%E2%AD%90%EF%B8%8F-it%20on%20GitHub-blue)](https://github.com/donavon/thwack/stargazers)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nTL;DR\n</h2>\n\nThwack is:\n\n- 💻 Modern — Thwack is an HTTP data fetching solution built for modern browsers\n- 🔎 Small — Thwack is only ~1.5k gzipped\n- 👩‍🏫 Smarter — Built with modern JavaScript\n- 😘 Familiar — Thwack uses an Axios-like interface\n- 🅰️ Typed — Easier inclusion for TypeScript projects\n- ✨ Support for NodeJS 10 and 12\n- 📱 Support for React Native\n\n> This README is a work in progress. You can also ask me a question [on Twitter](https://twitter.com/donavon).\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstallation\n</h2>\n\n```bash\n$ npm i thwack\n```\n\nor\n\n```bash\n$ yarn add thwack\n```\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nWhy Thwack over Axios?\n</h2>\n\nAxios was great when it was released back in the day. It gave us a promise based wrapper around `XMLHttpRequest`, which was difficult to use. But that was a long time ago and times have changed — browsers have gotten smarter. Maybe it's time for your data fetching solution to keep up?\n\nThwack was built from the ground up with modern browsers in mind. Because of this, it doesn't have the baggage that Axios has. Axios weighs in at around ~5k gzipped. Thwack, on the other hand, is a slender ~1.5k.\n\nThey support the same API, but there are some differences — mainly around `options` — but for the most part, they should be able to be used interchangeably for many applications.\n\n~~Thwack doesn't try to solve every problem, like Axios does, but instead provides the solution for 98% of what users _really_ need. This is what gives Thwack its feather-light footprint.~~\n\nScratch that. Thwack provides the same level of power as Axios with a much smaller footprint. And Thwack's promise based event system is easier to use.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nMethods\n</h2>\n\nThe following methods are available on all Thwack instances.\n\n### Data fetching\n\n- `thwack(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.request(options: ThwackOptions): Promise<ThwackResponse>`\n- `thwack.get(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.delete(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.head(url: string [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.post(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.put(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n- `thwack.patch(url: string, data:any [,options: ThwackOptions]): Promise<ThwackResponse>;`\n\n### Utility\n\n- `thwack.create(options: ThwackOptions): ThwackInstance;`\n\n  The `create` method creates (da!) a new child instance of the current Thwack instance with the given `options`.\n\n- `thwack.getUri(options: ThwackOptions): string;`\n\n  Thwacks URL resolution is [RFC-3986](https://tools.ietf.org/html/rfc3986#appendix-B) compliant. Axios's is not. It's powered by [`@thwack/resolve`](https://www.npmjs.com/package/@thwack/resolve).\n\n### Event listeners\n\nThwack supports the following event types: `request`, `response`, `data`, and `error`.\n\nFor more information on Thwack's event system, see [Thwack events](#thwack-events) below.\n\n- `thwack.addEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n- `thwack.removeEventListener(type: string, callback: (event:ThwackEvent) => Promise<any> ): void;`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nStatic Methods\n</h2>\n\n### Concurrency\n\nThwack has the following helper functions for making simultaneous requests. They are mostly for Axios compatibility. See the \"[How To](#how-to)\" section below for example usage.\n\n- `thwack.all(Promise<ThwackResponse>[])`\n\n- `thwack.spread(callback<results>)`\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackOptions\n</h2>\n\nThe `options` argument has the following properties.\n\n### `url`\n\nThis is either a fully qualified or a relative URL.\n\n### `baseURL`\n\nDefines a base URL that will be used to build a fully qualified URL from `url` above. Must be an absolute URL or `undefined`. Defaults to the `origin` + `pathname` of the current web page if running in a browser or `undefined` on Node or React Native.\n\nFor example, if you did this:\n\n```js\nthwack('foo', {\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/foo\n```\n\n### `method`\n\nA string containing one of the following HTTP methods: `get`, `post`, `put`, `patch`, `delete`, or `head`.\n\n### `data`\n\nIf the `method` is `post`, `put`, or `patch`, this is the data that will be used to build the request body.\n\n### `headers`\n\nThis is where you can place any optional HTTP request headers. Any header you specify here are merged in with any instance header values.\n\nFor example, if we set a Thwack instance like this:\n\n```js\nconst api = thwack.create({\n  headers: {\n    'x-app-name': 'My Awesome App',\n  },\n});\n```\n\nThen later, when you use the instance, you make a call like this:\n\n```js\nconst { data } = await api.get('foo', {\n  headers: {\n    'some-other-header': 'My Awesome App',\n  },\n});\n```\n\nThe headers that would be sent are:\n\n```\nx-app-name: My Awesome App\nsome-other-header': 'My Awesome App'\n\n```\n\n### `defaults`\n\nThis allows you to read/set the default options for this instance and, in effect, any child instances.\n\nExample:\n\n```js\nthwack.defaults.baseURL = 'https://example.com/api';\n```\n\nFor an instance, `defaults` is the same object passed to `create`. For example, the following will output \"https://example.com/api\".\n\n```js\nconst instance = thwack.create({\n  baseURL: 'https://example.com/api',\n});\nconsole.log(instance.defaults.baseURL);\n```\n\nAlso note that setting `defaults` on an instance (or even passing `options`) to an instance does NOT effect the parent. So for the following example, `thwack.defaults.baseURL` will still be \"https://api1.example.net/\".\n\n```js\nthwack.defaults.baseURL = 'https://api1.example.net/';\nconst instance = thwack.create();\ninstance.defaults.baseURL = 'https://example.com/api';\n\nconsole.log(thwack.defaults.baseURL);\n```\n\n### `params`\n\nThis is an optional object that contains the key/value pairs that will be used to build the fetch URL. Is there are any `:key` segments of the `baseURL` or the `url`, they will be replaced with the value of the matching key. For example, if you did this:\n\n```js\nthwack('orders/:id', {\n  params: { id: 123 },\n  baseURL: 'http://example.com',\n});\n```\n\nthe fetched URL will be:\n\n```\nhttp://example.com/orders/123\n```\n\nIf you don't specify a `:name`, or there are more `param`s than there are `:name`s, then the remaining key/values will be set as search parameters (i.e. `?key=value`).\n\n### `maxDepth`\n\nThe maximum level of recursive requests that can be made in a callbck before Thwack throws an error. This is used to prevent an event callback from causing a recursive loop, This if it issues another `request` without proper safeguards in place. Default = 3.\n\n### `responseType`\n\nBy default, Thwack will automatically determine how to decode the response data based on the value of the response header `content-type`. However, if the server responds with an incorrect value, you can override the parser by setting `responseType`. Valid values are `arraybuffer`, `document` (i.e. `formdata`), `json`, `text`, `stream`, and `blob`. Defaults to automatic.\n\nWhat is returned by Thwack is determined by the following table. The \"fetch method\" column is what is resolved in `data`. If you do not specify a `responseType`, Thwack will automatically determine the fetch method based on `content-type` and the `responseParserMap` table (see below).\n\n|     Content-Type      | `responseType` |                      `fetch` method                      |\n| :-------------------: | :------------: | :------------------------------------------------------: |\n|  `application/json`   |     `json`     |                    `response.json()`                     |\n| `multipart/form-data` |   `formdata`   |                  `response.formData()`                   |\n| `text/event-stream` |    `stream`    | passes back `response.body` as `data` without processing |\n|                       |     `blob`     |                    `response.blob()`                     |\n|                       | `arraybuffer`  |                 `response.arrayBuffer()`                 |\n|         `*/*`         |     `text`     |                    `response.text()`                     |\n\n> Note: `stream` is currently unsupported in React Native due to [#27741](https://github.com/facebook/react-native/issues/27741)\n\n### `responseParserMap`\n\nAnother useful way to determine which response parser to use is with `responseParserMap`. It allows you to set up a mapping between the resulting `content-type` from the response header and the parser type.\n\nThwack uses the following map as the default, which allows `json` and `formdata` decoding. If there are no matches, the response parser defaults to `text`. You may specify a default by setting the special `*/*` key.\n\n```json\n{\n  \"application/json\": \"json\",\n  \"multipart/form-data\": \"formdata\",\n  \"*/*\": \"text\"\n};\n```\n\nAny value you specify in `responseParserMap` is merged into the default map. That is to say that you can override the defaults and/or add new values.\n\nLet's say, for example, you would like to download an image into a blob. You could set the `baseURL` to your API endpoint and a `responseParserMap` that will download images of any type as blobs, but will still allow `json` downloads (as this is the default for a `content-type: application/json`).\n\n```js\nimport thwack from 'thwack';\n\nthwack.defaults.responseParserMap = { 'image/*': 'blob' };\n```\n\nAny URL that you download with an `image/*` content type (e.g. `image/jpeg`, `image/png`, etc) will be parsed with the `blob` parser.\n\n```js\nconst getBlobUrl = async (url) => {\n  const blob = (await thwack.get(url)).data;\n  const objectURL = URL.createObjectURL(blob);\n  return objectURL;\n};\n```\n\nSee this example running on [CodeSandbox](https://codesandbox.io/s/load-image-as-blob-410uq).\n\n> Note that you can use this technique for other things other than images.\n\nAs you can see, using `responseParserMap` is a great way to eliminate the need to set `responseType` for different Thwack calls.\n\n### `validateStatus`\n\nThis optional function is used to determine what status codes Thwack uses to return a promise or throw. It is passed the response `status`. If this function returns truthy, the promise is resolved, else the promise is rejected.\n\nThe default function throws for any status not in the 2xx (i.e. 200-299)\n\n### `paramsSerializer`\n\nThis is an optional function which Thwack will call to serialize the `params`. For example, given an object `{a:1, b:2, foo: 'bar'}`, it should serialize to the string `a=1&b=2&foo=bar`.\n\nFor most people, the [default serializer](https://github.com/donavon/thwack/blob/master/src/core/utils/buildUrl/defaultParamSerializer.js) should work just fine. This is mainly for edge case and Axios compatibility.\n\n> Note that the default serializer alphabetizes the parameters, which is a good practice to follow. If, however, this doesn't work for your situation, you can roll your own serializer.\n\n### `resolver`\n\nThis is a function that you can provide to override the [default resolver](https://github.com/donavon/thwack-resolve) behavior. `resolver` takes two arguments: a `url` and a `baseURL` which must be undefined, or an absolute URL. There should be little reason for you to to replace the resolver, but this is your escape hatch in case you need to.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponse\n</h2>\n\n### `status`\n\nA `number` representing the 3 digit [HTTP status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) that was received.\n\n- 1xx - Informational response\n- 2xx - Success\n- 3xx - Redirection\n- 4xx - Client errors\n- 5xx - Server errors\n\n### `ok`\n\nA `boolean` set to true is the `status` code in the 2xx range (i.e. a success). This value is not effected by `validateStatus`.\n\n### `statusText`\n\nA `string` representing the text of the `status` code. You should use the `status` code (or `ok`) in any program logic.\n\n### `headers`\n\nA key/value object with the returned HTTP headers. Any duplicate headers will be concatenated into a single header separated by semicolons.\n\n### `data`\n\nThis will hold the returned body of the HTTP response after it has been streamed and converted. The only exception is if you used the `responseType` of `stream`, in which case `data` is set directly to the `body` element.\n\nIf a `ThwackResponseError` was thrown, `data` will be the plain text representation of the response body.\n\n### `options`\n\nThe complete `options` object that processed the request. This `options` will be fully merged with any parent instance(s), as well as with `defaults`.\n\n### `response`\n\nThe complete HTTP `Response` object as returned by `fetch` or the `response` from a synthetic event callback.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwackResponseError\n</h2>\n\nIf the response from a Thwack request results in a non-2xx `status` code (e.g. 404 Not Found) then a `ThwackResponseError` is thrown.\n\n> Note: It is possible that other types of errors could be thrown (e.g. a bad event listener callback), so it is a best practice to interrogate the caught error to see if it is of type `ThwackResponseError`.\n\n```js\ntry {\n  const { data } = await thwack.get(someUrl)\n} catch (ex) {\n  if (ex instanceof thwack.ThwackResponseError)\n    const { status, message } = ex;\n    console.log(`Thwack status ${status}: ${message}`);\n  } else {\n    throw ex; // If not, rethrow the error\n  }\n}\n```\n\nA `ThwackResponseError` has all of the properties of a normal JavaScript `Error` plus a `thwackResponse` property with the same properties as a success status.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nInstances\n</h2>\n\nInstances created in Thwack are based on the parent instance. A parent's default options pass down through the instances. This can come in handy for setting up options in the parent that can affect the children, such as `baseURL`,\n\nInversely, parents can use `addEventListener` to monitor their children (see the [How to log every API call](#how-to-log-every-api-call) below for an example of this).\n\n<img alt=\"flow char\" src=\"https://user-images.githubusercontent.com/887639/79186980-06040480-7de9-11ea-8362-a5b187d231b8.png\" width=\"476\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nThwack events\n</h2>\n\nCombined with instances, the Thwack event system is what makes Thwack extremely powerful. With it, you can listen for different events.\n\nHere is the event flow for all events. AS you can see, it is possible for your code to get into an endless loop, should your callback blindly issue a `request()` without checking to see if it's already done so, so take caution.\n\n![thwack events](https://user-images.githubusercontent.com/887639/79867660-aee3ce00-83ac-11ea-94fd-4078c1a36244.png)\n\n### The `request` event\n\nWhenever any part of the application calls one of the data fetching methods, a `request` event is fired. Any listeners will get a `ThwackRequestEvent` object which has the `options` of the call in `event.options`. These event listeners can do something as simple as ([log the event](#log-every-request)) or as complicated as preventing the request and returning a response with ([mock data](#return-mock-data))\n\n```js\n// callback will be called for every request made in Thwack\nthwack.addEventListener('request', callback);\n```\n\n> Note that callbacks can be `async` allowing you to defer Thwack so that you might, for example, go out and fetch data a different URL before proceeding.\n\n### The `response` event\n\nThe event is fired _after_ the HTTP headers are received, but _before_ the body is streamed and parsed. Listeners will receive a `ThwackResponseEvent` object with a `thwackResponse` key set to the response.\n\n### The `data` event\n\nThe event is fired after the body is streamed and parsed. It is fired only if the fetch returned a 2xx status code. Listeners will receive a `ThwackDataEvent` object with a `thwackResponse` key set to the response.\n\n### The `error` event\n\nThe event is fired after the body is streamed and parsed. It is fired if the fetch returned a non-2xx status code. Listeners will receive a `ThwackErrorEvent` object with a `thwackResponse` key set to the response.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nNodeJS\n</h2>\n\nThwack will work on NodeJS, but requires a polyfill for `window.fetch`. Luckily, there is a wonderful polyfill called [`node-fetch`](https://github.com/node-fetch/node-fetch) that you can use.\n\nIf you are using NodeJS version 10, you will also need a polyfill for `Array#flat` and `Object#fromEntries`. NodeJS version 11+ has these methods and does not require a polyfill.\n\nYou can either provide these polyfills yourself, or use one of the following convenience imports instead. If you are running NodeJS 11+, use:\n\n```js\nimport thwack from 'thwack/node'; // NodeJS version 12+\n```\n\nIf you are running on NodeJS 10, use:\n\n```js\nimport thwack from 'thwack/node10'; // NodeJS version 10\n```\n\nIf you wish to provide these polyfills yourself, then to use Thwack, you must import from `thwack/core` and set `fetch` as the default for `fetch` as so.\n\n```js\nimport thwack from 'thwack/code';\nthwack.defaults.fetch = global.fetch;\n```\n\nThis should be done in your app startup code, usually `index.js`.\n\n> Note: The `responseType` of `blob` is not supported on NodeJS.\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nReact Native\n</h2>\n\nThwack is compatible with React Native and needs no additional polyfills. See below for a sample app written in React Native.\n\n> Note: React Native does not support `stream` due to [#27741](https://github.com/facebook/react-native/issues/27741)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nHow to\n</h2>\n\n### Multiple concurrent requests\n\nYou can use `thwack.all()` and `thwack.spread()` to make simultaneous requests. Data is then presented to your callback as one array.\n\nHere we display information for two GitHub users.\n\n```js\nfunction displayGitHubUsers() {\n  return thwack\n    .all([\n      thwack.get('https://api.github.com/users/donavon'),\n      thwack.get('https://api.github.com/users/revelcw'),\n    ])\n    .then(\n      thwack.spread((...results) => {\n        const output = results\n          .map(\n            ({ data }) => `${data.login} has ${data.public_repos} public repos`\n          )\n          .join('\\n');\n        console.log(output);\n      })\n    );\n}\n```\n\nNote the these are simply helper functions. If you are using `async`/`await` you can write this without the Thwack helpers using `Promise.all`.\n\n```js\nasync function displayGitHubUsers() {\n  const results = await Promise.all([\n    thwack.get('https://api.github.com/users/donavon'),\n    thwack.get('https://api.github.com/users/revelcw'),\n  ]);\n  const output = results\n    .map(({ data }) => `${data.login} has ${data.public_repos} public repos`)\n    .join('\\n');\n  console.log(output);\n}\n```\n\nYou can see this running live in the [CodeSandbox](https://codesandbox.io/s/thwack-allspread-demo-zx2nt?file=/src/index.js:140-642).\n\n(Demo inspired by [this blob post](https://blog.logrocket.com/how-to-make-http-requests-like-a-pro-with-axios/) on axios/fetch)\n\n### Cancelling a request\n\nUse an `AbortController` to cancel requests by passing its `signal` in the `thwack` options.\n\nIn the browser, you can use the built-in [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).\n\n```js\nimport thwack from 'thwack';\n\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn NodeJS, you can use something like [abort-controller](https://www.npmjs.com/package/abort-controller).\n\n```js\nimport thwack from 'thwack';\nimport AbortController from 'abort-controller';\n\nconst controller = new AbortController();\nconst { signal } = controller;\n\nthwack(url, { signal }).then(handleResponse).catch(handleError);\n\ncontroller.abort();\n```\n\nIn case you want to perform some action on request cancellation, you can listen to the `abort` event on `signal` too:\n\n```js\nsignal.addEventListener('abort', handleAbort);\n```\n\n### Log every request\n\nAdd an `addEventListener('request', callback)` and log each request to the console.\n\n```js\nimport thwack from 'thwack';\n\nthwack.addEventListener('request', (event) => {\n  console.log('hitting URL', thwack.getUri(event.options));\n});\n```\n\nIf you are using React, here is a Hook that you can \"use\" in your App that will accomplish the same thing.\n\n```js\nimport { useEffect } from 'react';\nimport thwack from 'thwack';\n\nconst logUrl = (event) => {\n  const { options } = event;\n  const fullyQualifiedUrl = thwack.getUri(options);\n  console.log(`hitting ${fullyQualifiedUrl}`);\n};\n\nconst useThwackLogger = () => {\n  useEffect(() => {\n    thwack.addEventListener('request', logUrl);\n    return () => thwack.removeEventListener('request', logUrl);\n  }, []);\n};\n\nexport default useThwackLogger;\n```\n\nHere is a code snippet on how to use it.\n\n```js\nconst App = () ={\n  useThwackLogger()\n\n  return (\n    <div>\n      ...\n    </div>\n  )\n}\n```\n\n### Return mock data\n\nLet's say you have an app that has made a request for some user data. If the app is hitting a specific URL (say `users`) and querying for a particular user ID (say `123`), you would like to prevent the request from hitting the server and instead mock the results.\n\nThe `status` in the `ThwackResponse` defaults to 200, so unless you need to mock a non-OK response, you only need to return `data`.\n\n```js\nthwack.addEventListener('request', async (event) => {\n  const { options } = event;\n  if (options.url === 'users' && options.params.id === 123) {\n    // tells Thwack to use the returned value instead of handling the event itself\n    event.preventDefault();\n\n    // stop other listeners (if any) from further processing\n    event.stopPropagation();\n\n    // because we called `preventDefault` above, the caller's request\n    // will be resolved to this `ThwackResponse` (defaults to status of 200 and ok)\n    return new thwack.ThwackResponse(\n      {\n        data: {\n          name: 'Fake Username',\n          email: 'fakeuser@example.com',\n        },\n      },\n      options\n    );\n  }\n});\n```\n\n### Convert DTO to Model\n\nOften it is desirable to convert a DTO (Data Transfer Object) into something easier to consume by the client. In this example below, we convert a complex DTO into `firstName`, `lastName`, `avatar`, and `email`. Other data elements that are returned from the API call, but not needed by the applications, are ignored.\n\nYou can see an example of DTO conversion, logging, and returning fake data in this sample app.\n\n<div>\n<img src=\"https://user-images.githubusercontent.com/887639/79865700-94f4bc00-83a9-11ea-9de6-9f204d5857c8.png\" width=\"500\" height=\"526\" alt=\"Mickey Mouse sample app\">\n</div>\n\nYou can [view the source code](https://codesandbox.io/s/sharp-chatelet-mje3w?file=/src/App.js) on CodeSandbox.\n\n### Load an Image as a Blob\n\nIn this example, we have a React Hook that loads an image as a Blob URL. It caches the URL to Blob URL mapping in session storage. Once loaded, any refresh of the page will instantaneously load the image from Blob URL.\n\n```js\nconst useBlobUrl = (imageUrl) => {\n  const [objectURL, setObjectURL] = useState('');\n\n  useEffect(() => {\n    let url = sessionStorage.getItem(imageUrl);\n\n    async function fetchData() {\n      if (!url) {\n        const { data } = await thwack.get(imageUrl, {\n          responseType: 'blob',\n        });\n        url = URL.createObjectURL(data);\n        sessionStorage.setItem(imageUrl, url);\n      }\n      setObjectURL(url);\n    }\n\n    fetchData();\n  }, [imageUrl]);\n\n  return objectURL;\n};\n```\n\nSee this example on [CodeSandbox](https://codesandbox.io/s/thwack-demo-load-image-as-blob-x0rnl?file=/src/ImageBlob/useBlobUrl.js)\n\n### Selective routing\n\nRight now you have a REST endpoint at `https://api.example.com`. Suppose you've published a new REST endpoint to a different URL and would like to start slowly routing 2% of network traffic to these new servers.\n\n> Note: normally this would be handled by your load balancer on the back-end. It's shown here for demonstration purposes only.\n\nWe could accomplish this by replacing `options.url` in the request event listener as follows.\n\n```js\nthwack.addEventListener('request', (event) => {\n  if (Math.random() >= 0.02) {\n    return;\n  }\n\n  // the code will be executed for approximately 2% of the requests\n  const { options } = event;\n  const oldUrl = thwack.getUri(options);\n  const url = new URL('', oldUrl);\n  url.origin = 'https://api2.example.com'; // point the origin at the new servers\n  const newUrl = url.href; // Get the fully qualified URL\n  event.options = { ...event.options, url: newUrl }; // replace `options`]\n});\n```\n\n### React Native sample app\n\nAlong with `use-thwack`, writing a data fetching app for React Native couldn't be easier.\n\nView the entire app [running on Expo](https://snack.expo.io/@donavon/random-dog).\n\n<img alt=\"good dog app\" src=\"https://user-images.githubusercontent.com/887639/80429496-3dc77d80-88ba-11ea-84e2-ebe9a2d69cc8.png\" height=\"800\" width=\"380\">\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nCredits\n</h2>\n\nThwack is **heavily** inspired by the [Axios](https://github.com/Axios/Axios). Thanks [Matt](https://twitter.com/mzabriskie)!\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nLicense\n</h2>\n\nLicensed under [MIT](LICENSE)\n\n<h2>\n<img alt=\"Thwack logo\" src=\"https://user-images.githubusercontent.com/887639/79779619-a8037f80-8308-11ea-8c4d-e7193fa15ae8.png\" width=\"22\">\nContributors ✨\n</h2>\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://donavon.com\"><img src=\"https://avatars3.githubusercontent.com/u/887639?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Donavon West</b></sub></a><br /><a href=\"#infra-donavon\" title=\"Infrastructure (Hosting, Build-Tools, etc)\">🚇</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Tests\">⚠️</a> <a href=\"#example-donavon\" title=\"Examples\">💡</a> <a href=\"#ideas-donavon\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#maintenance-donavon\" title=\"Maintenance\">🚧</a> <a href=\"https://github.com/donavon/thwack/pulls?q=is%3Apr+reviewed-by%3Adonavon\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"#tool-donavon\" title=\"Tools\">🔧</a> <a href=\"https://github.com/donavon/thwack/commits?author=donavon\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://jeremytice.com\"><img src=\"https://avatars0.githubusercontent.com/u/1740479?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Tice</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=jetpacmonkey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/yurm04\"><img src=\"https://avatars0.githubusercontent.com/u/4642404?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Yuraima Estevez</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=yurm04\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/bargar\"><img src=\"https://avatars2.githubusercontent.com/u/1666818?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Jeremy Bargar</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=bargar\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/brookescarlett\"><img src=\"https://avatars1.githubusercontent.com/u/26016393?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Brooke Scarlett Yalof</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=brookescarlett\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://twitter.com/karlhorky\"><img src=\"https://avatars2.githubusercontent.com/u/1935696?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Karl Horky</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=karlhorky\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://kojikanao.netlify.com/\"><img src=\"https://avatars0.githubusercontent.com/u/474225?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Koji</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=koji\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/tomByrer\"><img src=\"https://avatars2.githubusercontent.com/u/1308419?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Tom Byrer</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=tomByrer\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://iansutherland.ca/\"><img src=\"https://avatars2.githubusercontent.com/u/433725?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ian Sutherland</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=iansu\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https:///www.blakeyoder.com\"><img src=\"https://avatars0.githubusercontent.com/u/5393338?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Blake Yoder</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=blakeyoder\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://www.ryanhinchey.co\"><img src=\"https://avatars0.githubusercontent.com/u/3943764?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ryan Hinchey</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=ryhinchey\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/MiroDojkic\"><img src=\"https://avatars2.githubusercontent.com/u/9119913?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>Miro Dojkic</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=MiroDojkic\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://github.com/santicevic\"><img src=\"https://avatars3.githubusercontent.com/u/45316219?v=4\" width=\"100px;\" alt=\"\"/><br /><sub><b>santicevic</b></sub></a><br /><a href=\"https://github.com/donavon/thwack/commits?author=santicevic\" title=\"Documentation\">📖</a> <a href=\"https://github.com/donavon/thwack/commits?author=santicevic\" title=\"Code\">💻</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","readmeFilename":"README.md","description":"A tiny modern data fetching solution.","homepage":"https://github.com/donavon/thwack#readme","repository":{"type":"git","url":"git+https://github.com/donavon/thwack.git"},"bugs":{"url":"https://github.com/donavon/thwack/issues"}}