From 69c1e35a1b353a1ecb0c1d4ca525903981a95260 Mon Sep 17 00:00:00 2001 From: Julien Elbaz Date: Mon, 27 Jun 2022 18:43:56 +0200 Subject: [PATCH] :art: Improved packaging and bundling --- package.json | 61 +- rollup.config.js | 72 +- scripts/mockServer.cjs | 2 + scripts/mockServer.js | 2 - src/addons/abort.ts | 8 +- src/addons/formData.ts | 12 +- src/addons/formUrl.ts | 10 +- src/addons/index.ts | 10 + src/addons/perfs.ts | 10 +- src/addons/queryString.ts | 8 +- src/config.ts | 2 +- src/core.ts | 16 +- src/index.all.ts | 19 + src/index.ts | 4 +- src/index.umd.ts | 11 - src/middleware.ts | 4 +- src/resolver.ts | 8 +- src/types.ts | 10 +- src/utils.ts | 2 +- test/browser/SpecRunner.html | 6 +- test/browser/spec/index.js | 931 +++++++++++++------------ test/browser/src/wretch.all.min.js | 2 + test/browser/src/wretch.all.min.js.map | 1 + test/browser/src/wretch.min.js | 2 - test/browser/src/wretch.min.js.map | 1 - test/mock.cjs | 199 ++++++ test/mock.js | 199 ------ tsconfig.json | 2 +- 28 files changed, 866 insertions(+), 748 deletions(-) create mode 100644 scripts/mockServer.cjs delete mode 100644 scripts/mockServer.js create mode 100644 src/addons/index.ts create mode 100644 src/index.all.ts delete mode 100644 src/index.umd.ts create mode 100644 test/browser/src/wretch.all.min.js create mode 100644 test/browser/src/wretch.all.min.js.map delete mode 100644 test/browser/src/wretch.min.js delete mode 100644 test/browser/src/wretch.min.js.map create mode 100644 test/mock.cjs delete mode 100644 test/mock.js diff --git a/package.json b/package.json index 7214bdb..f79f76d 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,59 @@ "bugs": { "url": "https://github.com/elbywan/wretch/issues" }, - "main": "./dist/bundle/wretch.js", + "type": "module", + "main": "./dist/bundle/wretch.min.cjs", "module": "./dist/index.js", - "jsnext:main": "./dist/index.js", + "browser": "./dist/bundle/wretch.min.js", "types": "./dist/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "dist/*", + "dist/*/index.d.ts", + "dist/index.d.ts" + ] + } + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "node": { + "require": "./dist/bundle/wretch.min.cjs", + "import": "./dist/bundle/wretch.min.mjs" + }, + "browser": "./dist/bundle/wretch.min.js", + "import": "./dist/index.js" + }, + "./all": { + "types": "./dist/index.all.d.ts", + "node": { + "require": "./dist/bundle/wretch.all.min.cjs", + "import": "./dist/bundle/wretch.all.min.mjs" + }, + "browser": "./dist/bundle/wretch.all.min.js", + "import": "./dist/index.all.js" + }, + "./addons/*": { + "types": "./dist/addons/*.d.ts", + "node": { + "require": "./dist/bundle/addons/*.min.cjs", + "import": "./dist/bundle/addons/*.min.mjs" + }, + "browser": "./dist/bundle/addons/*.min.js", + "import": "./dist/addons/*.js" + }, + "./package.json": "./package.json", + "./*.mjs": { + "default": "./dist/*.mjs" + }, + "./*.cjs": { + "default": "./dist/*.cjs" + }, + "./*": { + "default": "./dist/*.js" + } + }, "files": [ "dist" ], @@ -28,10 +77,10 @@ "lint": "tslint -p tsconfig.json -t codeFrame", "fix": "tslint --fix -p tsconfig.json -t codeFrame", "prebuild": "rimraf dist && rimraf coverage && npm run lint", - "build": "tsc -p . && rollup -c && rollup -c --format esm -o dist/bundle/wretch.esm.js", - "mock": "node scripts/mockServer.js", + "build": "tsc -p . && rollup -c", + "mock": "node scripts/mockServer.cjs", "test": "concurrently --success first -k jest \"npm run mock\"", - "test-browsers": "rollup -c -o test/browser/src/wretch.min.js && concurrently -s first -k browserstack-runner \"npm run mock\"", + "test-browsers": "concurrently -s first -k browserstack-runner \"npm run mock\"", "changelog": "conventional-changelog -p wretch -i CHANGELOG.md -s -r 0" }, "author": "Julien Elbaz", @@ -83,7 +132,7 @@ "src/*.{js,ts}" ], "coveragePathIgnorePatterns": [ - "src/index.umd.ts" + "src/index.all.ts" ] } } \ No newline at end of file diff --git a/rollup.config.js b/rollup.config.js index 3ade7a8..073a932 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -2,17 +2,9 @@ import typescript from "rollup-plugin-typescript" import { terser } from "rollup-plugin-terser" import nodeResolve from "rollup-plugin-node-resolve" -export default { - input: "./src/index.umd.ts", - output: [ - { - file: "dist/bundle/wretch.js", - format: "umd", - name: "wretch", - exports: "default", - sourcemap: true - } - ], +const addons = ["abort", "formData", "formUrl", "perfs", "queryString"] + +const common = { plugins: [ typescript({ typescript: require("typescript"), @@ -26,4 +18,60 @@ export default { }) ], external: ["url"] -} \ No newline at end of file +} + +const formats = ["umd", "cjs", "esm"] +const outputs = output => formats.map(format => ({ + ...output, + format, + file: + format === "cjs" ? output.file.replace(".js", ".cjs") : + format === "esm" ? output.file.replace(".js", ".mjs") : + output.file +})) + +export default [ + { + input: "./src/index.ts", + output: outputs({ + file: "dist/bundle/wretch.min.js", + format: "umd", + name: "wretch", + exports: "default", + sourcemap: true + }), + ...common + }, + { + input: "./src/index.all.ts", + output: [ + ...outputs({ + file: "dist/bundle/wretch.all.min.js", + format: "umd", + name: "wretch", + exports: "default", + sourcemap: true + }), + // For tests + { + file: "test/browser/src/wretch.all.min.js", + format: "umd", + name: "wretch", + exports: "default", + sourcemap: true + } + ], + ...common + }, + ...addons.map(addon => ({ + input: `./src/addons/${addon}.ts`, + output: outputs({ + file: `dist/bundle/addons/${addon}.min.js`, + format: "umd", + name: `wretchAddon${addon.charAt(0).toLocaleUpperCase() + addon.slice(1)}`, + exports: "default", + sourcemap: true + }), + ...common + })) +] \ No newline at end of file diff --git a/scripts/mockServer.cjs b/scripts/mockServer.cjs new file mode 100644 index 0000000..77192b1 --- /dev/null +++ b/scripts/mockServer.cjs @@ -0,0 +1,2 @@ +const server = require("../test/mock.cjs") +server.launch(9876) \ No newline at end of file diff --git a/scripts/mockServer.js b/scripts/mockServer.js deleted file mode 100644 index 61912a4..0000000 --- a/scripts/mockServer.js +++ /dev/null @@ -1,2 +0,0 @@ -const server = require("../test/mock") -server.launch(9876) \ No newline at end of file diff --git a/src/addons/abort.ts b/src/addons/abort.ts index 84a4a7c..4a880a3 100644 --- a/src/addons/abort.ts +++ b/src/addons/abort.ts @@ -1,7 +1,7 @@ -import type { WretchResponseChain } from "../resolver" -import type { Wretch, WretchAddon, WretchErrorCallback } from "../types" +import type { WretchResponseChain } from "../resolver.js" +import type { Wretch, WretchAddon, WretchErrorCallback } from "../types.js" -interface AbortWretch { +export interface AbortWretch { /** * Associates a custom signal with the request. * @param controller - An AbortController @@ -9,7 +9,7 @@ interface AbortWretch { signal: (this: T & Wretch, controller: AbortController) => this } -interface AbortResolver { +export interface AbortResolver { /** * Aborts the request after a fixed time. * diff --git a/src/addons/formData.ts b/src/addons/formData.ts index e7543e9..b24a30a 100644 --- a/src/addons/formData.ts +++ b/src/addons/formData.ts @@ -1,6 +1,6 @@ -import type { Wretch } from "../core" -import type { Config } from "../config" -import type { WretchAddon } from "../types" +import type { Wretch } from "../core.js" +import type { Config } from "../config.js" +import type { WretchAddon } from "../types.js" function convertFormData( formObject: object, @@ -36,7 +36,7 @@ function convertFormData( return formData } -interface FormData { +export interface FormDataAddon { /** * Converts the javascript object to a FormData and sets the request body. * @param formObject - An object which will be converted to a FormData @@ -44,7 +44,7 @@ interface FormData { * Can be set as an array of string to exclude specific keys. * @see https://github.com/elbywan/wretch/issues/68 for more details. */ - formData(this: T & Wretch, formObject: object, recursive?: string[] | boolean): this + formData(this: T & Wretch, formObject: object, recursive?: string[] | boolean): this } /** @@ -56,7 +56,7 @@ interface FormData { * wretch().addon(FormDataAddon) * ``` */ -const formData: WretchAddon = { +const formData: WretchAddon = { wretch: { formData(formObject, recursive = false) { return this.body(convertFormData(formObject, recursive, this._config)) diff --git a/src/addons/formUrl.ts b/src/addons/formUrl.ts index 45f9eb2..b2d27d7 100644 --- a/src/addons/formUrl.ts +++ b/src/addons/formUrl.ts @@ -1,5 +1,5 @@ -import type { Wretch } from "../core" -import type { WretchAddon } from "../types" +import type { Wretch } from "../core.js" +import type { WretchAddon } from "../types.js" function encodeQueryValue(key: string, value: unknown) { return encodeURIComponent(key) + @@ -22,14 +22,14 @@ function convertFormUrl(formObject: object) { .join("&") } -interface FormUrl { +export interface FormUrlAddon { /** * Converts the input to an url encoded string and sets the content-type header and body. * If the input argument is already a string, skips the conversion part. * * @param input - An object to convert into an url encoded string or an already encoded string */ - formUrl(this: T & Wretch, input: (object | string)): this + formUrl(this: T & Wretch, input: (object | string)): this } /** @@ -41,7 +41,7 @@ interface FormUrl { * wretch().addon(FormUrlAddon) * ``` */ -const formUrl: WretchAddon = { +const formUrl: WretchAddon = { wretch: { formUrl(input) { return this diff --git a/src/addons/index.ts b/src/addons/index.ts new file mode 100644 index 0000000..e783c7e --- /dev/null +++ b/src/addons/index.ts @@ -0,0 +1,10 @@ +export { default as abortAddon } from "./abort.js" +export type { AbortWretch, AbortResolver } from "./abort.js" +export { default as formDataAddon } from "./formData.js" +export type { FormDataAddon } from "./formData.js" +export { default as formUrlAddon } from "./formUrl.js" +export type { FormUrlAddon } from "./formUrl.js" +export { default as perfsAddon } from "./perfs.js" +export type { PerfsAddon } from "./perfs.js" +export { default as queryStringAddon } from "./queryString.js" +export type { QueryStringAddon } from "./queryString.js" \ No newline at end of file diff --git a/src/addons/perfs.ts b/src/addons/perfs.ts index 8c69cb0..9bfcc4b 100644 --- a/src/addons/perfs.ts +++ b/src/addons/perfs.ts @@ -1,5 +1,5 @@ -import type { WretchResponseChain } from "../resolver" -import type { WretchAddon } from "../types" +import type { WretchResponseChain } from "../resolver.js" +import type { WretchAddon } from "../types.js" const onMatch = (entries, name, callback, _performance) => { if (!entries.getEntriesByName) @@ -57,13 +57,13 @@ const utils = { } } -interface Perfs { +export interface PerfsAddon { /** * Performs a callback on the API performance timings of the request. * * Warning: Still experimental on browsers and node.js */ - perfs: (this: C & WretchResponseChain, cb?: (timing: any) => void) => this, + perfs: (this: C & WretchResponseChain, cb?: (timing: any) => void) => this, } /** @@ -75,7 +75,7 @@ interface Perfs { * wretch().addon(PerfsAddon()) * ``` */ -const perfs: () => WretchAddon = () => ({ +const perfs: () => WretchAddon = () => ({ resolver: { perfs(cb) { this.fetchRequest.then(res => utils.observe(res.url, cb, this.wretchRequest._config)).catch(() => {/* swallow */ }) diff --git a/src/addons/queryString.ts b/src/addons/queryString.ts index b6795d6..99569fe 100644 --- a/src/addons/queryString.ts +++ b/src/addons/queryString.ts @@ -1,4 +1,4 @@ -import type { Wretch, Config, WretchAddon } from "../types" +import type { Wretch, Config, WretchAddon } from "../types.js" const appendQueryParams = (url: string, qp: object | string, replace: boolean, config: Config) => { let queryString: string @@ -29,7 +29,7 @@ const appendQueryParams = (url: string, qp: object | string, replace: boolean, c return url + "&" + queryString } -interface QueryString { +export interface QueryStringAddon { /** * Converts a javascript object to query parameters, * then appends this query string to the current url. @@ -51,7 +51,7 @@ interface QueryString { * * @param qp - An object which will be converted, or a string which will be used verbatim. */ - query(this: T & Wretch, qp: object | string, replace?: boolean): this + query(this: T & Wretch, qp: object | string, replace?: boolean): this } /** @@ -63,7 +63,7 @@ interface QueryString { * wretch().addon(QueryAddon) * ``` */ -const queryString: WretchAddon = { +const queryString: WretchAddon = { wretch: { query(qp, replace = false) { return this.clone({ url: appendQueryParams(this._url, qp, replace, this._config) }) diff --git a/src/config.ts b/src/config.ts index dcaddb1..d7410b6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,4 +1,4 @@ -import { mix } from "./utils" +import { mix } from "./utils.js" declare const global diff --git a/src/core.ts b/src/core.ts index 665b852..a1720c4 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1,11 +1,11 @@ -import { mix, extractContentType, isLikelyJsonMime } from "./utils" -import * as Constants from "./constants" -import { resolver } from "./resolver" -import config from "./config" -import type { Config } from "./config" -import type { WretchError, WretchOptions, WretchDeferredCallback, WretchAddon } from "./types" -import type { WretchResponseChain } from "./resolver" -import type { ConfiguredMiddleware } from "./middleware" +import { mix, extractContentType, isLikelyJsonMime } from "./utils.js" +import * as Constants from "./constants.js" +import { resolver } from "./resolver.js" +import config from "./config.js" +import type { Config } from "./config.js" +import type { WretchError, WretchOptions, WretchDeferredCallback, WretchAddon } from "./types.js" +import type { WretchResponseChain } from "./resolver.js" +import type { ConfiguredMiddleware } from "./middleware.js" /** * The Wretch object used to perform easy fetch requests. diff --git a/src/index.all.ts b/src/index.all.ts new file mode 100644 index 0000000..1236f0d --- /dev/null +++ b/src/index.all.ts @@ -0,0 +1,19 @@ +import { setDefaults, setErrorType, setPolyfills } from "./config.js" +import { core } from "./core.js" +import * as Addons from "./addons/index.js" + +function factory(url = "", options = {}) { + return core + .clone({ url, options }) + .addon(Addons.abortAddon()) + .addon(Addons.formDataAddon) + .addon(Addons.formUrlAddon) + .addon(Addons.perfsAddon()) + .addon(Addons.queryStringAddon) +} + +factory["default"] = factory +factory.defaults = setDefaults +factory.errorType = setErrorType +factory.polyfills = setPolyfills +export default factory \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 60b9874..2f73428 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ -import { setDefaults, setErrorType, setPolyfills } from "./config" -import { core } from "./core" +import { setDefaults, setErrorType, setPolyfills } from "./config.js" +import { core } from "./core.js" export type { Wretch, diff --git a/src/index.umd.ts b/src/index.umd.ts deleted file mode 100644 index ceea17e..0000000 --- a/src/index.umd.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { setDefaults, setErrorType, setPolyfills } from "./config" -import { core } from "./core" -function factory(url = "", options = {}) { - return core.clone({ url, options }) -} - -factory["default"] = factory -factory.defaults = setDefaults -factory.errorType = setErrorType -factory.polyfills = setPolyfills -export default factory \ No newline at end of file diff --git a/src/middleware.ts b/src/middleware.ts index 24d7dd5..ab1a897 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,5 +1,5 @@ -import { WretchOptions } from "./types" -import { WretchResponse } from "./types" +import { WretchOptions } from "./types.js" +import { WretchResponse } from "./types.js" export type Middleware = (options?: { [key: string]: any }) => ConfiguredMiddleware export type ConfiguredMiddleware = (next: FetchLike) => FetchLike diff --git a/src/resolver.ts b/src/resolver.ts index 27f8b86..7fea0b7 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -1,7 +1,7 @@ -import { Wretch } from "./core" -import { middlewareHelper } from "./middleware" -import { mix } from "./utils" -import type { WretchResponse, WretchErrorCallback } from "./types" +import { Wretch } from "./core.js" +import { middlewareHelper } from "./middleware.js" +import { mix } from "./utils.js" +import type { WretchResponse, WretchErrorCallback } from "./types.js" class WretchErrorWrapper { constructor(public error: any) { } diff --git a/src/types.ts b/src/types.ts index b800963..b351689 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,9 +1,9 @@ -import type { Wretch } from "./core" +import type { Wretch } from "./core.js" -export type { Wretch } from "./core" -export type { Config } from "./config" -export type { ConfiguredMiddleware, FetchLike, Middleware } from "./middleware" -export type { WretchResponseChain } from "./resolver" +export type { Wretch } from "./core.js" +export type { Config } from "./config.js" +export type { ConfiguredMiddleware, FetchLike, Middleware } from "./middleware.js" +export type { WretchResponseChain } from "./resolver.js" export type WretchOptions = Record export type WretchError = Error & { status: number, response: WretchResponse, text?: string, json?: any } export type WretchErrorCallback = (error: WretchError, originalRequest: Wretch) => any diff --git a/src/utils.ts b/src/utils.ts index a1c0de7..e7d3de5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,4 @@ -import * as Constants from "./constants" +import * as Constants from "./constants.js" export function extractContentType(headers: HeadersInit = {}): string | undefined { return Object.entries(headers).find(([k]) => diff --git a/test/browser/SpecRunner.html b/test/browser/SpecRunner.html index 0b9f50b..8a8a3f6 100644 --- a/test/browser/SpecRunner.html +++ b/test/browser/SpecRunner.html @@ -1,5 +1,6 @@ + Jasmine Spec Runner v3.1.0 @@ -10,10 +11,11 @@ - + - + + \ No newline at end of file diff --git a/test/browser/spec/index.js b/test/browser/spec/index.js index d18897d..dfaa3e2 100644 --- a/test/browser/spec/index.js +++ b/test/browser/spec/index.js @@ -2,476 +2,477 @@ const _PORT = 9876 const _URL = 'http://localhost:' + _PORT const allRoutes = (obj, type, action, opts, body) => Promise.all([ - obj.get(opts)[type](_ => _).then(action), - obj.put(body, opts)[type](action), - obj.patch(body, opts)[type](action), - obj.post(body, opts)[type](action), - obj.delete(opts)[type](action), + obj.get(opts)[type](_ => _).then(action), + obj.put(body, opts)[type](action), + obj.patch(body, opts)[type](action), + obj.post(body, opts)[type](action), + obj.delete(opts)[type](action), ]) const isSafari = - navigator.userAgent.indexOf('Safari') >= 0 && - navigator.userAgent.indexOf('Chrome') < 0 - -describe("Wretch", function() { - - it("should perform crud requests and parse a text response", async function() { - const init = wretch(`${_URL}/text`) - const test = _ => expect(_).toBe("A text string") - await allRoutes(init, "text", test) - await allRoutes(init, "text", test, {}, {}) - }) - - it("should perform crud requests and parse a json response", async function() { - const test = _ => expect(_).toEqual({ a: "json", object: "which", is: "stringified" }) - const init = wretch(`${_URL}/json`) - await allRoutes(init, "json", test) - await allRoutes(init, "json", test, {}, {}) + navigator.userAgent.indexOf('Safari') >= 0 && + navigator.userAgent.indexOf('Chrome') < 0 + +describe("Wretch", function () { + + it("should perform crud requests and parse a text response", async function () { + const init = wretch(`${_URL}/text`) + const test = _ => expect(_).toBe("A text string") + await allRoutes(init, "text", test) + await allRoutes(init, "text", test, {}, {}) + }) + + it("should perform crud requests and parse a json response", async function () { + const test = _ => expect(_).toEqual({ a: "json", object: "which", is: "stringified" }) + const init = wretch(`${_URL}/json`) + await allRoutes(init, "json", test) + await allRoutes(init, "json", test, {}, {}) + }) + + it("should perform crud requests and parse a blob response", async function () { + const test = _ => expect(_.size).toBe(58921) + const init = wretch(`${_URL}/blob`) + await allRoutes(init, "blob", test) + await allRoutes(init, "blob", test, {}, {}) + }) + + it("should perform crud requests and parse an arrayBuffer response", async function () { + const compareBuffers = function (buf1, buf2) { + if (buf1.byteLength !== buf2.byteLength) + return false + for (let i = 0; i < buf1.byteLength; i++) { + if (buf1[i] != buf2[i]) + return false + } + return true + } + const test = arrayBuffer => { + const view = new Uint8Array(arrayBuffer) + const test = new Uint8Array([0x00, 0x01, 0x02, 0x03]) + expect(compareBuffers(view, test)).toBe(true) + } + const init = wretch(`${_URL}/arrayBuffer`) + await allRoutes(init, "arrayBuffer", test) + await allRoutes(init, "arrayBuffer", test, {}) + }) + + it("should perform a plain text round trip", async function () { + const text = "hello, server !" + const roundTrip = await wretch(`${_URL}/text/roundTrip`).content("text/plain").body(text).post().text() + expect(roundTrip).toBe(text) + // Using shorthand + const roundTrip2 = await wretch(`${_URL}/text/roundTrip`).content("text/plain").post(text).text() + expect(roundTrip2).toBe(text) + }) + + it("should perform a json round trip", async function () { + const jsonObject = { a: 1, b: 2, c: 3 } + const roundTrip = await wretch(`${_URL}/json/roundTrip`).json(jsonObject).post().json() + expect(roundTrip).toEqual(jsonObject) + // Using shorthand + const roundTrip2 = await wretch(`${_URL}/json/roundTrip`).post(jsonObject).json() + expect(roundTrip2).toEqual(jsonObject) + // Ensure that calling .json with the shorthand works + const roundTrip3 = await wretch(`${_URL}/json/roundTrip`).json({}).post(jsonObject).json() + expect(roundTrip3).toEqual(jsonObject) + // Ensure that it preserves any content type set previously + try { + await wretch(`${_URL}/json/roundTrip`).content("bad/content").post(jsonObject).json() + fail("should have thrown") + } catch (e) { } + // Ensure that the charset is preserved. + const headerWithCharset = "application/json; charset=utf-16" + expect(wretch().content(headerWithCharset).json({})._options.headers['Content-Type']).toBe(headerWithCharset) + }) + + it("should perform an url encoded form data round trip", async function () { + const reference = "a=1&b=2&%20c=%203&d=%7B%22a%22%3A1%7D" + const jsonObject = { "a": 1, "b": 2, " c": " 3", "d": { a: 1 } } + let roundTrip = await wretch(`${_URL}/urlencoded/roundTrip`).formUrl(reference).post().text() + expect(roundTrip).toBe(reference) + roundTrip = await wretch(`${_URL}/urlencoded/roundTrip`).formUrl(jsonObject).post().text() + expect(roundTrip).toEqual(reference) + // Ensure that calling .json with the shorthand works + const roundTrip3 = await wretch(`${_URL}/json/roundTrip`).json({}).post(jsonObject).json() + expect(roundTrip3).toEqual(jsonObject) + // Ensure that it preserves any content type set previously + try { + await wretch(`${_URL}/json/roundTrip`).content("bad/content").post(jsonObject).json() + fail("should have thrown") + } catch (e) { } + }) + + it("should send a FormData object", async function () { + const form = { + hello: "world", + duck: "Muscovy", + duckProperties: { + beak: { + color: "yellow" + }, + nbOfLegs: 2 + } + } + const decoded = await wretch(`${_URL}/formData/decode`) + .formData(form, ["duckImage"]) + .post() + .json() + expect(decoded).toEqual({ + hello: "world", + duck: "Muscovy", + "duckProperties[beak][color]": "yellow", + "duckProperties[nbOfLegs]": "2" }) - - it("should perform crud requests and parse a blob response", async function() { - const test = _ => expect(_.size).toBe(58921) - const init = wretch(`${_URL}/blob`) - await allRoutes(init, "blob", test) - await allRoutes(init, "blob", test, {}, {}) - }) - - it("should perform crud requests and parse an arrayBuffer response", async function() { - const compareBuffers = function (buf1, buf2) { - if (buf1.byteLength !== buf2.byteLength) - return false - for (let i = 0; i < buf1.byteLength; i++) { - if (buf1[i] != buf2[i]) - return false - } - return true - } - const test = arrayBuffer => { - const view = new Uint8Array(arrayBuffer) - const test = new Uint8Array([ 0x00, 0x01, 0x02, 0x03 ]) - expect(compareBuffers(view, test)).toBe(true) - } - const init = wretch(`${_URL}/arrayBuffer`) - await allRoutes(init, "arrayBuffer", test) - await allRoutes(init, "arrayBuffer", test, {}) - }) - - it("should perform a plain text round trip", async function() { - const text = "hello, server !" - const roundTrip = await wretch(`${_URL}/text/roundTrip`).content("text/plain").body(text).post().text() - expect(roundTrip).toBe(text) - // Using shorthand - const roundTrip2 = await wretch(`${_URL}/text/roundTrip`).content("text/plain").post(text).text() - expect(roundTrip2).toBe(text) - }) - - it("should perform a json round trip", async function() { - const jsonObject = { a: 1, b: 2, c: 3 } - const roundTrip = await wretch(`${_URL}/json/roundTrip`).json(jsonObject).post().json() - expect(roundTrip).toEqual(jsonObject) - // Using shorthand - const roundTrip2 = await wretch(`${_URL}/json/roundTrip`).post(jsonObject).json() - expect(roundTrip2).toEqual(jsonObject) - // Ensure that calling .json with the shorthand works - const roundTrip3 = await wretch(`${_URL}/json/roundTrip`).json({}).post(jsonObject).json() - expect(roundTrip3).toEqual(jsonObject) - // Ensure that it preserves any content type set previously - try { - await wretch(`${_URL}/json/roundTrip`).content("bad/content").post(jsonObject).json() - fail("should have thrown") - } catch(e) {} - // Ensure that the charset is preserved. - const headerWithCharset = "application/json; charset=utf-16" - expect(wretch().content(headerWithCharset).json({})._options.headers['Content-Type']).toBe(headerWithCharset) - }) - - it("should perform an url encoded form data round trip", async function() { - const reference = "a=1&b=2&%20c=%203&d=%7B%22a%22%3A1%7D" - const jsonObject = { "a": 1, "b": 2, " c": " 3", "d": { a: 1 } } - let roundTrip = await wretch(`${_URL}/urlencoded/roundTrip`).formUrl(reference).post().text() - expect(roundTrip).toBe(reference) - roundTrip = await wretch(`${_URL}/urlencoded/roundTrip`).formUrl(jsonObject).post().text() - expect(roundTrip).toEqual(reference) - // Ensure that calling .json with the shorthand works - const roundTrip3 = await wretch(`${_URL}/json/roundTrip`).json({}).post(jsonObject).json() - expect(roundTrip3).toEqual(jsonObject) - // Ensure that it preserves any content type set previously - try { - await wretch(`${_URL}/json/roundTrip`).content("bad/content").post(jsonObject).json() - fail("should have thrown") - } catch(e) {} - }) - - it("should send a FormData object", async function() { - const form = { - hello: "world", - duck: "Muscovy", - duckProperties: { - beak: { - color: "yellow" - }, - nbOfLegs: 2 - } - } - const decoded = await wretch(`${_URL}/formData/decode`) - .formData(form, ["duckImage"]) - .post() - .json() - expect(decoded).toEqual({ - hello: "world", - duck: "Muscovy", - "duckProperties[beak][color]": "yellow", - "duckProperties[nbOfLegs]": "2" - }) - const f = { arr: [ 1, 2, 3 ]} - const d = await wretch(`${_URL}/formData/decode`).formData(f).post().json() - // expect(d).toEqual({ - // "arr[]": [1, 2, 3] - // }) - }) - - it("should perform OPTIONS and HEAD requests", async function() { - const optsRes = await wretch(_URL + "/options").opts().res() - const optsRes2 = await wretch(_URL + "/options").opts({}).res() - expect(optsRes.headers.get("Allow")).toBe("OPTIONS") - expect(optsRes2.headers.get("Allow")).toBe("OPTIONS") - const headRes = await wretch(_URL + "/json").head().res() - const headRes2 = await wretch(_URL + "/json").head({}).res() - expect(headRes.headers.get("content-type")).toBe("application/json") - expect(headRes2.headers.get("content-type")).toBe("application/json") - }) - - it("should preserve existing headers when undefined or null is passed to .headers()", async function () { - const headers = { "X-HELLO": "WORLD", "X-Y": "Z" } - let req = wretch().headers({ "X-HELLO": "WORLD" }) - req = req.headers({ "X-Y": "Z" }) - expect(req._options.headers).toEqual(headers) - req = req.headers(null) - expect(req._options.headers).toEqual(headers) - req = req.headers(undefined) - expect(req._options.headers).toEqual(headers) - }) - - it("should catch common error codes", async function() { - const w = wretch(_URL + "/") - - try { - let check = 0 - await w.url("400").get().badRequest(_ => { - expect(_.message).toBe("error code : 400") - check++ - }).text(_ => expect(_).toBeNull()) - await w.url("401").get().unauthorized(_ => { - expect(_.message).toBe("error code : 401") - check++ - }).text(_ => expect(_).toBeNull()) - await w.url("403").get().forbidden(_ => { - expect(_.message).toBe("error code : 403") - check++ - }).text(_ => expect(_).toBeNull()) - await w.url("404").get().notFound(_ => { - expect(_.message).toBe("error code : 404") - check++ - }).text(_ => expect(_).toBeNull()) - await w.url("408").get().timeout(_ => { - expect(_.message).toBe("error code : 408") - check++ - }).text(_ => expect(_).toBeNull()) - await w.url("500").get().internalError(_ => { - expect(_.message).toBe("error code : 500") - check++ - }).text(_ => expect(_).toBeNull()) - expect(check).toBe(6) - } catch(error) { - // Firefox specific error in the CI (why?) - expect(error.message).toBe("NetworkError when attempting to fetch resource.") - } - }) - - it("should catch other error codes", async function() { - let check = 0 - await wretch(`${_URL}/444`) - .get() - .notFound(_ => check++) - .error(444, _ => check++) - .unauthorized(_ => check++) - .res(_ => expect(_).toBe(undefined)) - expect(check).toBe(1) - }) - - it("should set and catch errors with global catchers", async function() { - let check = 0 - const w = wretch(_URL) - .catcher(404, err => check++) - .catcher(500, err => check++) - .catcher(400, err => check++) - .catcher(401, err => check--) - .catcher("SyntaxError", err => check++) - - // +1 : 1 - await w.url("/text").get().res(_ => check++) - // +0 : 1 - await w.url("/text").get().json(_ => check--) - // +1 : 2 - await w.url("/400").get().res(_ => check--) - // +1 : 3 - await w.url("/401").get().unauthorized(_ => check++).res(_ => check--) - // +1 : 4 - await w.url("/404").get().res(_ => check--) - // +1 : 5 - await w.url("/408").get().timeout(_ => check++).res(_ => check--) - // +1 : 6 - await w.url("/418").get().res(_ => check--).catch(_ => "muted") - // +1: 7 - await w.url("/500").get().res(_ => check--) - - expect(check).toBe(7) - }) - - it("should capture the original request with resolvers/catchers", async function() { - let check = 0 - const redirectedNotFound = await wretch(`${_URL}/404`) - .get() - .notFound((error, req) => { - check++ - return req.url(`${_URL}/text`, true).get().text() - }).text() - expect(redirectedNotFound).toBe("A text string") - - const withNotFoundCatcher = wretch(`${_URL}/401`) - .catcher(401, (err, req) => { - check++ - return req.url(`${_URL}/text`, true).get().text() - }) - - const withNotFoundRedirect = wretch(`${_URL}/404`) - .resolve(resolver => resolver.notFound((err, req) => { - check++ - return req.url(`${_URL}/text`, true).get().text() - })) - - expect(await withNotFoundCatcher.get().text()).toBe("A text string") - expect(await withNotFoundRedirect.get().text()).toBe("A text string") - expect(check).toBe(3) - }) - - it("should set default fetch options", async function() { - let rejected = await new Promise(res => wretch(`${_URL}/customHeaders`).get().badRequest(_ => { - res(true) - }).res(result => res(!result))) - expect(rejected).toBeTruthy() - wretch().defaults({ - headers: { "X-Custom-Header": "Anything" } - }) - rejected = await new Promise(res => wretch(`${_URL}/customHeaders`).get().badRequest(_ => { - res(true) - }).res(result => res(!result))) - expect(rejected).toBeTruthy() - wretch().defaults({ - headers: { "X-Custom-Header-2": "Anything" } - }, true) - rejected = await new Promise(res => wretch(`${_URL}/customHeaders`).get().badRequest(_ => { - res(true) - }).res(result => res(!result))) - wretch().defaults("not an object", true) - expect(rejected).toBeTruthy() - const accepted = await new Promise(res => wretch(`${_URL}/customHeaders`) - .options({ headers: { "X-Custom-Header-3" : "Anything" }}, false) - .options({ headers: { "X-Custom-Header-4" : "Anything" }}) - .get() - .badRequest(_ => { res(false) }) - .res(result => res(!!result))) - expect(accepted).toBeTruthy() - }) - - it("should allow url, query parameters & options modifications and return a fresh new Wretcher object containing the change", async function() { - const obj1 = wretch("...") - const obj2 = obj1.url(_URL, true) - expect(obj1["_url"]).toBe("...") - expect(obj2["_url"]).toBe(_URL) - const obj3 = obj1.options({ headers: { "X-test": "test" }}) - expect(obj3["_options"]).toEqual({ headers: { "X-test": "test" }}) - expect(obj1["_options"]).toEqual({}) - const obj4 = obj2.query({a: "1!", b: "2"}) - expect(obj4["_url"]).toBe(`${_URL}?a=1%21&b=2`) - expect(obj2["_url"]).toBe(_URL) - const obj5 = obj4.query({c: 6, d: [7, 8]}) - expect(obj4["_url"]).toBe(`${_URL}?a=1%21&b=2`) - expect(obj5["_url"]).toBe(`${_URL}?a=1%21&b=2&c=6&d=7&d=8`) - const obj6 = obj5.query("Literal[]=Query&String", true) - expect(obj5["_url"]).toBe(`${_URL}?a=1%21&b=2&c=6&d=7&d=8`) - expect(obj6["_url"]).toBe(`${_URL}?Literal[]=Query&String`) - const obj7 = obj5.query("Literal[]=Query&String").url("/test") - expect(obj5["_url"]).toBe(`${_URL}?a=1%21&b=2&c=6&d=7&d=8`) - expect(obj7["_url"]).toBe(`${_URL}/test?a=1%21&b=2&c=6&d=7&d=8&Literal[]=Query&String`) - }) - - it("should set the Accept header", async function() { - expect(await wretch(`${_URL}/accept`).get().text()).toBe("text") - expect(await wretch(`${_URL}/accept`).accept("application/json").get().json()).toEqual({ json: "ok" }) - }) - - it("should set the Authorization header", async function() { - try { await wretch(_URL + "/basicauth") - .get() - .res(_ => fail("Authenticated route should not respond without credentials.")) - } catch(e) { - expect(e.status).toBe(401) - } - - const res = await wretch(_URL + "/basicauth") - .auth("Basic d3JldGNoOnJvY2tz") - .get() - .text() - - expect(res).toBe("ok") - }) - - it("should change the parsing used in the default error handler", async function() { - await wretch(`${_URL}/json500`) - .get() - .internalError(error => { expect(error.text).toEqual(`{"error":500,"message":"ok"}`) }) - .res(_ => fail("I should never be called because an error was thrown")) - .then(_ => expect(_).toBe(undefined)) - wretch().errorType("json") - await wretch(`${_URL}/json500`) - .get() - .internalError(error => { expect(error.json).toEqual({ error: 500, message: "ok" }) }) - .res(_ => fail("I should never be called because an error was thrown")) - .then(_ => expect(_).toBe(undefined)) - // Change back - wretch().errorType("text") - }) - - it("should abort a request", function(done) { - if(!window.AbortController || isSafari) - return done() - - let count = 0 - - const handleError = error => { - expect(error.name).toBe("AbortError") - count++ - } - - const controller = new AbortController() - wretch(`${_URL}/longResult`) - .signal(controller) - .get() - .res() - .catch(handleError) - controller.abort() - - const [c, w] = wretch(`${_URL}/longResult`).get().controller() - w.res().catch(handleError) - c.abort() - - wretch(`${_URL}/longResult`) - .get() - .setTimeout(100) - .onAbort(handleError) - .res() - - const [c2, w2] = wretch(`${_URL}/longResult`).get().controller() - w2.setTimeout(100, c2).onAbort(handleError).res() - - setTimeout(() => { - expect(count).toBe(4) - done() - }, 1000) - }) - - it("should program resolvers", async function() { - let check = 0 - const w = wretch() - .url(_URL) - .resolve(resolver => resolver - .unauthorized(_ => check--)) - .resolve(resolver => resolver - .unauthorized(_ => check++), true) - .resolve(resolver => resolver - // .perfs(_ => check++) - .json(_ => { check++; return _ })) - const result = await w.url("/json").get() - await new Promise(res => setTimeout(res, 100)) - expect(result).toEqual({ a: "json", object: "which", is: "stringified" }) - expect(check).toBe(1) - await w.url("/401").get() - await new Promise(res => setTimeout(res, 100)) - expect(check).toBe(2) - }) - - // it("should retrieve performance timings associated with a fetch request", async function(done) { - // if(!window.performance || isSafari) - // return done() - // // Test empty perfs() - // await wretch(`${_URL}/text`).get().perfs().res(_ => expect(_.ok).toBeTruthy()).then(() => - // // Racing condition : observer triggered before response - // wretch(`${_URL}/bla`).get().perfs(_ => { - // expect(typeof _.startTime).toBe("number") - - // // Racing condition : response triggered before observer - // wretch(`${_URL}/fakeurl`).get().perfs(_ => { - // expect(typeof _.startTime).toBe("number") - // done() - // }).res().catch(() => "ignore") - // }).res().catch(_ => "ignore") - // ) + const f = { arr: [1, 2, 3] } + const d = await wretch(`${_URL}/formData/decode`).formData(f).post().json() + // expect(d).toEqual({ + // "arr[]": [1, 2, 3] // }) - - it("should use middlewares", async function() { - const shortCircuit = () => next => (url, opts) => Promise.resolve({ - ok: true, - text: () => Promise.resolve(opts.method + "@" + url) - }) - const setGetMethod = () => next => (url, opts) => { - return next(url, Object.assign(opts, { method: "GET" })) - } - const setPostMethod = () => next => (url, opts) => { - return next(url, Object.assign(opts, { method: "POST" })) - } - const w = wretch().middlewares([ - shortCircuit() - ]) - - expect(await w.url(_URL).head().text()).toBe(`HEAD@${_URL}`) - - const w2 = w.middlewares([ - setGetMethod(), - shortCircuit() - ], true) - - expect(await w2.url(_URL).head().text()).toBe(`GET@${_URL}`) - - const w3 = w.middlewares([ - setGetMethod(), - setPostMethod(), - shortCircuit() - ], true) - - expect(await w3.url(_URL).head().text()).toBe(`POST@${_URL}`) + }) + + it("should perform OPTIONS and HEAD requests", async function () { + const optsRes = await wretch(_URL + "/options").opts().res() + const optsRes2 = await wretch(_URL + "/options").opts({}).res() + expect(optsRes.headers.get("Allow")).toBe("OPTIONS") + expect(optsRes2.headers.get("Allow")).toBe("OPTIONS") + const headRes = await wretch(_URL + "/json").head().res() + const headRes2 = await wretch(_URL + "/json").head({}).res() + expect(headRes.headers.get("content-type")).toBe("application/json") + expect(headRes2.headers.get("content-type")).toBe("application/json") + }) + + it("should preserve existing headers when undefined or null is passed to .headers()", async function () { + const headers = { "X-HELLO": "WORLD", "X-Y": "Z" } + let req = wretch().headers({ "X-HELLO": "WORLD" }) + req = req.headers({ "X-Y": "Z" }) + expect(req._options.headers).toEqual(headers) + req = req.headers(null) + expect(req._options.headers).toEqual(headers) + req = req.headers(undefined) + expect(req._options.headers).toEqual(headers) + }) + + it("should catch common error codes", async function () { + const w = wretch(_URL + "/") + + try { + let check = 0 + await w.url("400").get().badRequest(_ => { + expect(_.message).toBe("error code : 400") + check++ + }).text(_ => expect(_).toBeNull()) + await w.url("401").get().unauthorized(_ => { + expect(_.message).toBe("error code : 401") + check++ + }).text(_ => expect(_).toBeNull()) + await w.url("403").get().forbidden(_ => { + expect(_.message).toBe("error code : 403") + check++ + }).text(_ => expect(_).toBeNull()) + await w.url("404").get().notFound(_ => { + expect(_.message).toBe("error code : 404") + check++ + }).text(_ => expect(_).toBeNull()) + await w.url("408").get().timeout(_ => { + expect(_.message).toBe("error code : 408") + check++ + }).text(_ => expect(_).toBeNull()) + await w.url("500").get().internalError(_ => { + expect(_.message).toBe("error code : 500") + check++ + }).text(_ => expect(_).toBeNull()) + expect(check).toBe(6) + } catch (error) { + // Firefox specific error in the CI (why?) + expect(error.message).toBe("NetworkError when attempting to fetch resource.") + } + }) + + it("should catch other error codes", async function () { + let check = 0 + await wretch(`${_URL}/444`) + .get() + .notFound(_ => check++) + .error(444, _ => check++) + .unauthorized(_ => check++) + .res(_ => expect(_).toBe(undefined)) + expect(check).toBe(1) + }) + + it("should set and catch errors with global catchers", async function () { + let check = 0 + const w = wretch(_URL) + .catcher(404, err => check++) + .catcher(500, err => check++) + .catcher(400, err => check++) + .catcher(401, err => check--) + .catcher("SyntaxError", err => check++) + + // +1 : 1 + await w.url("/text").get().res(_ => check++) + // +0 : 1 + await w.url("/text").get().json(_ => check--) + // +1 : 2 + await w.url("/400").get().res(_ => check--) + // +1 : 3 + await w.url("/401").get().unauthorized(_ => check++).res(_ => check--) + // +1 : 4 + await w.url("/404").get().res(_ => check--) + // +1 : 5 + await w.url("/408").get().timeout(_ => check++).res(_ => check--) + // +1 : 6 + await w.url("/418").get().res(_ => check--).catch(_ => "muted") + // +1: 7 + await w.url("/500").get().res(_ => check--) + + expect(check).toBe(7) + }) + + it("should capture the original request with resolvers/catchers", async function () { + let check = 0 + const redirectedNotFound = await wretch(`${_URL}/404`) + .get() + .notFound((error, req) => { + check++ + return req.url(`${_URL}/text`, true).get().text() + }).text() + expect(redirectedNotFound).toBe("A text string") + + const withNotFoundCatcher = wretch(`${_URL}/401`) + .catcher(401, (err, req) => { + check++ + return req.url(`${_URL}/text`, true).get().text() + }) + + const withNotFoundRedirect = wretch(`${_URL}/404`) + .resolve(resolver => resolver.notFound((err, req) => { + check++ + return req.url(`${_URL}/text`, true).get().text() + })) + + expect(await withNotFoundCatcher.get().text()).toBe("A text string") + expect(await withNotFoundRedirect.get().text()).toBe("A text string") + expect(check).toBe(3) + }) + + it("should set default fetch options", async function () { + let rejected = await new Promise(res => wretch(`${_URL}/customHeaders`).get().badRequest(_ => { + res(true) + }).res(result => res(!result))) + expect(rejected).toBeTruthy() + wretch.defaults({ + headers: { "X-Custom-Header": "Anything" } + }, true) + rejected = await new Promise(res => wretch(`${_URL}/customHeaders`).get().badRequest(_ => { + res(true) + }).res(result => res(!result))) + expect(rejected).toBeTruthy() + wretch.defaults({ + headers: { "X-Custom-Header-2": "Anything" } }) - - it("should chain actions that will be performed just before the request is sent", async function() { - const w = wretch(_URL + "/basicauth") - .defer((w, url, opts) => { - expect(opts.method).toBe("GET") - expect(opts.q).toBe("a") - expect(url).toBe(_URL + "/basicauth") - return w.auth("toto") - }) - .defer((w, url, { token }) => w.auth(token), true) - - const result = await w - .options({ token: "Basic d3JldGNoOnJvY2tz" }) - .get({ q: "a" }) - .text() - expect(result).toBe("ok") - }) - - it("should handle falsey json", async function () { - expect(await wretch(`${_URL}/json/null`).get().json()).toEqual(null) - expect(await wretch(`${_URL}/json/null`).get().json(_ => true)).toEqual(true) - expect(await wretch(`${_URL}/json/null`).get().json(_ => false)).toEqual(false) + rejected = await new Promise(res => wretch(`${_URL}/customHeaders`).get().badRequest(_ => { + res(true) + }).res(result => res(!result))) + // wretch.defaults("not an object" as any, true) + expect(rejected).toBeTruthy() + const accepted = await new Promise(res => wretch(`${_URL}/customHeaders`) + .options({ headers: { "X-Custom-Header-3": "Anything" } }, false) + .options({ headers: { "X-Custom-Header-4": "Anything" } }) + .get() + .badRequest(_ => { res(false) }) + .res(result => res(!!result))) + expect(accepted).toBeTruthy() + }) + + it("should allow url, query parameters & options modifications and return a fresh new Wretcher object containing the change", async function () { + const obj1 = wretch("...") + const obj2 = obj1.url(_URL, true) + expect(obj1["_url"]).toBe("...") + expect(obj2["_url"]).toBe(_URL) + const obj3 = obj1.options({ headers: { "X-test": "test" } }) + expect(obj3["_options"]).toEqual({ headers: { "X-test": "test" } }) + expect(obj1["_options"]).toEqual({}) + const obj4 = obj2.query({ a: "1!", b: "2" }) + expect(obj4["_url"]).toBe(`${_URL}?a=1%21&b=2`) + expect(obj2["_url"]).toBe(_URL) + const obj5 = obj4.query({ c: 6, d: [7, 8] }) + expect(obj4["_url"]).toBe(`${_URL}?a=1%21&b=2`) + expect(obj5["_url"]).toBe(`${_URL}?a=1%21&b=2&c=6&d=7&d=8`) + const obj6 = obj5.query("Literal[]=Query&String", true) + expect(obj5["_url"]).toBe(`${_URL}?a=1%21&b=2&c=6&d=7&d=8`) + expect(obj6["_url"]).toBe(`${_URL}?Literal[]=Query&String`) + const obj7 = obj5.query("Literal[]=Query&String").url("/test") + expect(obj5["_url"]).toBe(`${_URL}?a=1%21&b=2&c=6&d=7&d=8`) + expect(obj7["_url"]).toBe(`${_URL}/test?a=1%21&b=2&c=6&d=7&d=8&Literal[]=Query&String`) + }) + + it("should set the Accept header", async function () { + expect(await wretch(`${_URL}/accept`).get().text()).toBe("text") + expect(await wretch(`${_URL}/accept`).accept("application/json").get().json()).toEqual({ json: "ok" }) + }) + + it("should set the Authorization header", async function () { + try { + await wretch(_URL + "/basicauth") + .get() + .res(_ => fail("Authenticated route should not respond without credentials.")) + } catch (e) { + expect(e.status).toBe(401) + } + + const res = await wretch(_URL + "/basicauth") + .auth("Basic d3JldGNoOnJvY2tz") + .get() + .text() + + expect(res).toBe("ok") + }) + + it("should change the parsing used in the default error handler", async function () { + await wretch(`${_URL}/json500`) + .get() + .internalError(error => { expect(error.text).toEqual(`{"error":500,"message":"ok"}`) }) + .res(_ => fail("I should never be called because an error was thrown")) + .then(_ => expect(_).toBe(undefined)) + wretch.errorType("json") + await wretch(`${_URL}/json500`) + .get() + .internalError(error => { expect(error.json).toEqual({ error: 500, message: "ok" }) }) + .res(_ => fail("I should never be called because an error was thrown")) + .then(_ => expect(_).toBe(undefined)) + // Change back + wretch.errorType("text") + }) + + it("should abort a request", function (done) { + if (!window.AbortController || isSafari) + return done() + + let count = 0 + + const handleError = error => { + expect(error.name).toBe("AbortError") + count++ + } + + const controller = new AbortController() + wretch(`${_URL}/longResult`) + .signal(controller) + .get() + .res() + .catch(handleError) + controller.abort() + + const [c, w] = wretch(`${_URL}/longResult`).get().controller() + w.res().catch(handleError) + c.abort() + + wretch(`${_URL}/longResult`) + .get() + .setTimeout(100) + .onAbort(handleError) + .res() + + const [c2, w2] = wretch(`${_URL}/longResult`).get().controller() + w2.setTimeout(100, c2).onAbort(handleError).res() + + setTimeout(() => { + expect(count).toBe(4) + done() + }, 1000) + }) + + it("should program resolvers", async function () { + let check = 0 + const w = wretch() + .url(_URL) + .resolve(resolver => resolver + .unauthorized(_ => check--)) + .resolve(resolver => resolver + .unauthorized(_ => check++), true) + .resolve(resolver => resolver + // .perfs(_ => check++) + .json(_ => { check++; return _ })) + const result = await w.url("/json").get() + await new Promise(res => setTimeout(res, 100)) + expect(result).toEqual({ a: "json", object: "which", is: "stringified" }) + expect(check).toBe(1) + await w.url("/401").get() + await new Promise(res => setTimeout(res, 100)) + expect(check).toBe(2) + }) + + it("should retrieve performance timings associated with a fetch request", async function (done) { + if (!window.performance || isSafari) + return done() + // Test empty perfs() + await wretch(`${_URL}/text`).get().perfs().res(_ => expect(_.ok).toBeTruthy()).then(() => + // Racing condition : observer triggered before response + wretch(`${_URL}/bla`).get().perfs(_ => { + expect(typeof _.startTime).toBe("number") + + // Racing condition : response triggered before observer + wretch(`${_URL}/fakeurl`).get().perfs(_ => { + expect(typeof _.startTime).toBe("number") + done() + }).res().catch(() => "ignore") + }).res().catch(_ => "ignore") + ) + }) + + it("should use middlewares", async function () { + const shortCircuit = () => next => (url, opts) => Promise.resolve({ + ok: true, + text: () => Promise.resolve(opts.method + "@" + url) }) + const setGetMethod = () => next => (url, opts) => { + return next(url, Object.assign(opts, { method: "GET" })) + } + const setPostMethod = () => next => (url, opts) => { + return next(url, Object.assign(opts, { method: "POST" })) + } + const w = wretch().middlewares([ + shortCircuit() + ]) + + expect(await w.url(_URL).head().text()).toBe(`HEAD@${_URL}`) + + const w2 = w.middlewares([ + setGetMethod(), + shortCircuit() + ], true) + + expect(await w2.url(_URL).head().text()).toBe(`GET@${_URL}`) + + const w3 = w.middlewares([ + setGetMethod(), + setPostMethod(), + shortCircuit() + ], true) + + expect(await w3.url(_URL).head().text()).toBe(`POST@${_URL}`) + }) + + it("should chain actions that will be performed just before the request is sent", async function () { + const w = wretch(_URL + "/basicauth") + .defer((w, url, opts) => { + expect(opts.method).toBe("GET") + expect(opts.q).toBe("a") + expect(url).toBe(_URL + "/basicauth") + return w.auth("toto") + }) + .defer((w, url, { token }) => w.auth(token), true) + + const result = await w + .options({ token: "Basic d3JldGNoOnJvY2tz" }) + .get({ q: "a" }) + .text() + expect(result).toBe("ok") + }) + + it("should handle falsey json", async function () { + expect(await wretch(`${_URL}/json/null`).get().json()).toEqual(null) + expect(await wretch(`${_URL}/json/null`).get().json(_ => true)).toEqual(true) + expect(await wretch(`${_URL}/json/null`).get().json(_ => false)).toEqual(false) + }) }) \ No newline at end of file diff --git a/test/browser/src/wretch.all.min.js b/test/browser/src/wretch.all.min.js new file mode 100644 index 0000000..c00c9da --- /dev/null +++ b/test/browser/src/wretch.all.min.js @@ -0,0 +1,2 @@ +!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define(r):(e="undefined"!=typeof globalThis?globalThis:e||self).wretch=r()}(this,(function(){"use strict";const e="Content-Type";function r(r={}){var t;return null===(t=Object.entries(r).find((([r])=>r.toLowerCase()===e.toLowerCase())))||void 0===t?void 0:t[1]}function t(e){return/^application\/.*json.*/.test(e)}const n=function(e,r,t=!1){if(!e||!r||"object"!=typeof e||"object"!=typeof r)return e;const s=Object.assign({},e);for(const o in r)r.hasOwnProperty(o)&&(r[o]instanceof Array&&e[o]instanceof Array?s[o]=t?[...e[o],...r[o]]:r[o]:"object"==typeof r[o]&&"object"==typeof e[o]?s[o]=n(e[o],r[o],t):s[o]=r[o]);return s},s={defaults:{},errorType:null,polyfills:{},polyfill(e,{doThrow:r=!0,instance:t=!1}={},...n){const s=this.polyfills[e]||("undefined"!=typeof self?self[e]:null)||("undefined"!=typeof global?global[e]:null);if(r&&!s)throw new Error(e+" is not defined");return t&&s?new s(...n):s}};class o{constructor(e){this.error=e}}const i=e=>{const{_url:r,_options:t,_config:s,_catchers:i,_resolvers:c,_middlewares:l,_addons:a}=e,h=new Map(i),u=n(s.defaults,t);a.forEach((r=>r.beforeRequest&&r.beforeRequest(e,u)));const f=(e=>r=>0===e.length?r:1===e.length?e[0](r):e.reduceRight(((t,n,s)=>s===e.length-2?n(t(r)):n(t))))(l)(s.polyfill("fetch"))(r,u),d=f.catch((e=>{throw new o(e)})).then((e=>{if(!e.ok){if("opaque"===e.type){const r=new Error("Opaque response");throw r.response=e,r}return e[s.errorType||"text"]().then((r=>{const t=new Error(r);throw t[s.errorType||"text"]=r,t.response=e,t.status=e.status,t}))}return e})),p=r=>r.catch((r=>{const t=r instanceof o?r.error:r;if(r instanceof o&&h.has("__fetchError__"))return h.get("__fetchError__")(t,e);if(h.has(t.status))return h.get(t.status)(t,e);if(h.has(t.name))return h.get(t.name)(t,e);throw t})),_=e=>r=>p(e?d.then((r=>r&&r[e]())).then((e=>r?r(e):e)):d.then((e=>r?r(e):e))),b={wretchRequest:e,fetchRequest:f,res:_(null),json:_("json"),blob:_("blob"),formData:_("formData"),arrayBuffer:_("arrayBuffer"),text:_("text"),error(e,r){return h.set(e,r),this},badRequest(e){return this.error(400,e)},unauthorized(e){return this.error(401,e)},forbidden(e){return this.error(403,e)},notFound(e){return this.error(404,e)},timeout(e){return this.error(408,e)},internalError(e){return this.error(500,e)},fetchError(e){return this.error("__fetchError__",e)}},g=a.reduce(((e,r)=>Object.assign(Object.assign({},e),r.resolver)),b);return c.reduce(((r,t)=>t(r,e)),g)},c={_url:"",_options:{},_config:s,_catchers:new Map,_resolvers:[],_deferredChain:[],_middlewares:[],_addons:[],clone({url:e=this._url,options:r=this._options,config:t=this._config,catchers:n=this._catchers,resolvers:s=this._resolvers,deferredChain:o=this._deferredChain,middlewares:i=this._middlewares,addons:c=this._addons}={}){return Object.assign(Object.assign({},this),{_url:e,_options:Object.assign({},r),_catchers:new Map(n),_config:Object.assign({},t),_resolvers:[...s],_deferredChain:[...o],_middlewares:[...i],_addons:[...c]})},addon(e){return Object.assign(Object.assign({},this.clone({addons:[...this._addons,e]})),e.wretch)},defaults(e,r=!1){return this.clone({config:Object.assign(Object.assign({},this._config),{defaults:r?e:n(this._config.defaults,e)})})},errorType(e){return this.clone({errorType:e})},polyfills(e,r=!1){return this.clone({config:Object.assign(Object.assign({},this._config),{polyfills:r?e:n(this._config.polyfills,e)})})},url(e,r=!1){if(r)return this.clone({url:e});const t=this._url.split("?");return this.clone({url:t.length>1?t[0]+e+"?"+t[1]:this._url+e})},options(e,r=!0){return this.clone({options:r?n(this._options,e):e})},headers(e){return this.clone({options:n(this._options,{headers:e||{}})})},accept(e){return this.headers({Accept:e})},content(r){return this.headers({[e]:r})},auth(e){return this.headers({Authorization:e})},catcher(e,r){const t=new Map(this._catchers);return t.set(e,r),this.clone({catchers:t})},resolve(e,r=!1){return this.clone({resolvers:r?[e]:[...this._resolvers,e]})},defer(e,r=!1){return this.clone({deferredChain:r?[e]:[...this._deferredChain,e]})},middlewares(e,r=!1){return this.clone({middlewares:r?e:[...this._middlewares,...e]})},method(e,n={},s=null){let o=this.options(Object.assign(Object.assign({},n),{method:e}));const c=r(o._options.headers),l="object"==typeof s&&(!o._options.headers||!c||t(c));return o=s?l?o.json(s,c):o.body(s):o,i(o._deferredChain.reduce(((e,r)=>r(e,e._url,e._options)),o))},get(e){return this.method("GET",e)},delete(e){return this.method("DELETE",e)},put(e,r){return this.method("PUT",r,e)},post(e,r){return this.method("POST",r,e)},patch(e,r){return this.method("PATCH",r,e)},head(e){return this.method("HEAD",e)},opts(e){return this.method("OPTIONS",e)},replay(e){return this.method(this._options.method,e)},body(e){return this.clone({options:Object.assign(Object.assign({},this._options),{body:e})})},json(e,n){const s=r(this._options.headers);return this.content(n||t(s)&&s||"application/json").body(JSON.stringify(e))}},l=()=>{let e=null,r=null;return{beforeRequest(t,n){r=t._config.polyfill("AbortController",{doThrow:!1,instance:!0}),!n.signal&&r&&(n.signal=r.signal),e={ref:null,clear(){e.ref&&(clearTimeout(e.ref),e.ref=null)}}},wretch:{signal(e){return this.clone({options:Object.assign(Object.assign({},this._options),{signal:e.signal})})}},resolver:{setTimeout(t,n=r){return e.clear(),e.ref=setTimeout((()=>n.abort()),t),this},controller(){return[r,this]},onAbort(e){return this.error("AbortError",e)}}}};function a(e,r=!1,t,n=t.polyfill("FormData",{instance:!0}),s=[]){return Object.entries(e).forEach((([e,o])=>{let i=s.reduce(((e,r)=>e?`${e}[${r}]`:r),null);if(i=i?`${i}[${e}]`:e,o instanceof Array)for(const e of o)n.append(i+"[]",e);else!r||"object"!=typeof o||r instanceof Array&&r.includes(e)?n.append(i,o):null!==o&&a(o,r,t,n,[...s,e])})),n}const h={wretch:{formData(e,r=!1){return this.body(a(e,r,this._config))}}};function u(e,r){return encodeURIComponent(e)+"="+encodeURIComponent("object"==typeof r?JSON.stringify(r):""+r)}const f={wretch:{formUrl(e){return this.body("string"==typeof e?e:(r=e,Object.keys(r).map((e=>{const t=r[e];return t instanceof Array?t.map((r=>u(e,r))).join("&"):u(e,t)})).join("&"))).content("application/x-www-form-urlencoded");var r}}},d=(e,r,t,n)=>{if(!e.getEntriesByName)return!1;const s=e.getEntriesByName(r);return!!(s&&s.length>0)&&(t(s.reverse()[0]),n.clearMeasures&&n.clearMeasures(r),p.callbacks.delete(r),p.callbacks.size<1&&(p.observer.disconnect(),n.clearResourceTimings&&n.clearResourceTimings()),!0)},p={callbacks:new Map,observer:null,observe:(e,r,t)=>{if(!e||!r)return;const n=t.polyfill("performance",{doThrow:!1});((e,r)=>(!p.observer&&e&&r&&(p.observer=new r((r=>{p.callbacks.forEach(((t,n)=>{d(r,n,t,e)}))})),e.clearResourceTimings&&e.clearResourceTimings()),p.observer))(n,t.polyfill("PerformanceObserver",{doThrow:!1}))&&(d(n,e,r,n)||(p.callbacks.size<1&&p.observer.observe({entryTypes:["resource","measure"]}),p.callbacks.set(e,r)))}},_=()=>({resolver:{perfs(e){return this.fetchRequest.then((r=>p.observe(r.url,e,this.wretchRequest._config))).catch((()=>{})),this}}}),b=(e,r,t,n)=>{let s;if("string"==typeof r)s=r;else{const e=n.polyfill("URLSearchParams",{instance:!0});for(const t in r)if(r[t]instanceof Array)for(const n of r[t])e.append(t,n);else e.append(t,r[t]);s=e.toString()}const o=e.split("?");return s?t||o.length<2?o[0]+"?"+s:e+"&"+s:t?o[0]:e},g={wretch:{query(e,r=!1){return this.clone({url:b(this._url,e,r,this._config)})}}};function y(e="",r={}){return c.clone({url:e,options:r}).addon(l()).addon(h).addon(f).addon(_()).addon(g)}return y.default=y,y.defaults=function(e,r=!1){s.defaults=r?e:n(s.defaults,e)},y.errorType=function(e){s.errorType=e},y.polyfills=function(e,r=!1){s.polyfills=r?e:n(s.polyfills,e)},y})); +//# sourceMappingURL=wretch.all.min.js.map diff --git a/test/browser/src/wretch.all.min.js.map b/test/browser/src/wretch.all.min.js.map new file mode 100644 index 0000000..626f082 --- /dev/null +++ b/test/browser/src/wretch.all.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"wretch.all.min.js","sources":["../../../src/constants.ts","../../../src/utils.ts","../../../src/config.ts","../../../src/resolver.ts","../../../src/middleware.ts","../../../src/core.ts","../../../src/addons/abort.ts","../../../src/addons/formData.ts","../../../src/addons/formUrl.ts","../../../src/addons/perfs.ts","../../../src/addons/queryString.ts","../../../src/index.all.ts"],"sourcesContent":["export const JSON_MIME = \"application/json\"\nexport const CONTENT_TYPE_HEADER = \"Content-Type\"","import * as Constants from \"./constants.js\"\n\nexport function extractContentType(headers: HeadersInit = {}): string | undefined {\n return Object.entries(headers).find(([k]) =>\n k.toLowerCase() === Constants.CONTENT_TYPE_HEADER.toLowerCase()\n )?.[1]\n}\n\nexport function isLikelyJsonMime(value: string): boolean {\n return /^application\\/.*json.*/.test(value)\n}\n\nexport const mix = function (one: object, two: object, mergeArrays: boolean = false) {\n if (!one || !two || typeof one !== \"object\" || typeof two !== \"object\")\n return one\n\n const clone = { ...one }\n for (const prop in two) {\n if (two.hasOwnProperty(prop)) {\n if (two[prop] instanceof Array && one[prop] instanceof Array) {\n clone[prop] = mergeArrays ? [...one[prop], ...two[prop]] : two[prop]\n } else if (typeof two[prop] === \"object\" && typeof one[prop] === \"object\") {\n clone[prop] = mix(one[prop], two[prop], mergeArrays)\n } else {\n clone[prop] = two[prop]\n }\n }\n }\n\n return clone\n}\n","import { mix } from \"./utils.js\"\n\ndeclare const global\n\nconst config = {\n // Default options\n defaults: {},\n // Error type\n errorType: null,\n // Polyfills\n polyfills: {\n // fetch: null,\n // FormData: null,\n // URLSearchParams: null,\n // performance: null,\n // PerformanceObserver: null,\n // AbortController: null\n },\n polyfill(p: string, { doThrow = true, instance = false } = {}, ...args) {\n const res = this.polyfills[p] ||\n (typeof self !== \"undefined\" ? self[p] : null) ||\n (typeof global !== \"undefined\" ? global[p] : null)\n if (doThrow && !res) throw new Error(p + \" is not defined\")\n return instance && res ? new res(...args) : res\n }\n}\nexport type Config = typeof config\n\n/**\n * Sets the default fetch options used when creating a Wretch instance.\n * @param options New default options\n * @param replace If true, completely replaces the existing options instead of mixing in\n */\nexport function setDefaults(defaults: any, replace = false) {\n config.defaults = replace ? defaults : mix(config.defaults, defaults)\n}\n\n/**\n * Sets the global polyfills which will be used for every subsequent calls.\n *\n * Needed for libraries like [fetch-ponyfill](https://github.com/qubyte/fetch-ponyfill).\n *\n * @param polyfills An object containing the polyfills\n * @param replace If true, replaces the current polyfills instead of mixing in\n */\nexport function setPolyfills(polyfills: any, replace = false) {\n config.polyfills = replace ? polyfills : mix(config.polyfills, polyfills)\n}\n\n/**\n * Sets the default method (text, json ...) used to parse the data contained in the response body in case of an HTTP error.\n *\n * If null, defaults to \"text\".\n */\nexport function setErrorType(errorType: string) {\n config.errorType = errorType\n}\n\nexport default config\n","import { Wretch } from \"./core.js\"\nimport { middlewareHelper } from \"./middleware.js\"\nimport { mix } from \"./utils.js\"\nimport type { WretchResponse, WretchErrorCallback } from \"./types.js\"\n\nclass WretchErrorWrapper {\n constructor(public error: any) { }\n}\n\nexport interface WretchResponseChain {\n wretchRequest: Wretch,\n fetchRequest: Promise,\n\n /**\n * Retrieves the raw result as a promise.\n */\n res: (cb?: (type: WretchResponse) => Result) => Promise,\n /**\n * Retrieves the result as a parsed JSON object.\n */\n json: (cb?: (type: { [key: string]: any }) => Result) => Promise,\n /**\n * Retrieves the result as a Blob object.\n */\n blob: (cb?: (type: Blob) => Result) => Promise,\n /**\n * Retrieves the result as a FormData object.\n */\n formData: (cb?: (type: FormData) => Result) => Promise,\n /**\n * Retrieves the result as an ArrayBuffer object.\n */\n arrayBuffer: (cb?: (type: ArrayBuffer) => Result) => Promise,\n /**\n * Retrieves the result as a string.\n */\n text: (cb?: (type: string) => Result) => Promise,\n\n /**\n * Catches an http response with a specific error code or name and performs a callback.\n */\n error: (this: Self & WretchResponseChain, code: (number | string), cb: WretchErrorCallback) => this,\n /**\n * Catches a bad request (http code 400) and performs a callback.\n */\n badRequest: (this: Self & WretchResponseChain, cb: WretchErrorCallback) => this,\n /**\n * Catches an unauthorized request (http code 401) and performs a callback.\n */\n unauthorized: (this: Self & WretchResponseChain, cb: WretchErrorCallback) => this,\n /**\n * Catches a forbidden request (http code 403) and performs a callback.\n */\n forbidden: (this: Self & WretchResponseChain, cb: WretchErrorCallback) => this,\n /**\n * Catches a \"not found\" request (http code 404) and performs a callback.\n */\n notFound: (this: Self & WretchResponseChain, cb: WretchErrorCallback) => this,\n /**\n * Catches a timeout (http code 408) and performs a callback.\n */\n timeout: (this: Self & WretchResponseChain, cb: WretchErrorCallback) => this,\n /**\n * Catches an internal server error (http code 500) and performs a callback.\n */\n internalError: (this: Self & WretchResponseChain, cb: WretchErrorCallback) => this,\n /**\n * Catches errors thrown when calling the fetch function and performs a callback.\n */\n fetchError: (this: Self & WretchResponseChain, cb: WretchErrorCallback) => this,\n}\n\nexport const resolver = (wretch: Wretch) => {\n const {\n _url: url,\n _options: opts,\n _config: config,\n _catchers: _catchers,\n _resolvers: resolvers,\n _middlewares: middlewares,\n _addons: addons\n } = wretch\n\n const catchers = new Map(_catchers)\n const finalOptions = mix(config.defaults, opts)\n addons.forEach(addon => addon.beforeRequest && addon.beforeRequest(wretch, finalOptions))\n // The generated fetch request\n const fetchRequest = middlewareHelper(middlewares)(config.polyfill(\"fetch\"))(url, finalOptions)\n // Throws on an http error\n const throwingPromise: Promise = fetchRequest\n .catch(error => {\n throw new WretchErrorWrapper(error)\n })\n .then(response => {\n if (!response.ok) {\n if (response.type === \"opaque\") {\n const err = new Error(\"Opaque response\")\n err[\"response\"] = response\n throw err\n }\n return response[config.errorType || \"text\"]().then(body => {\n // Enhances the error object\n const err = new Error(body)\n err[config.errorType || \"text\"] = body\n err[\"response\"] = response\n err[\"status\"] = response.status\n throw err\n })\n }\n return response\n })\n // Wraps the Promise in order to dispatch the error to a matching catcher\n const catchersWrapper = (promise: Promise): Promise => {\n return promise.catch(err => {\n const error = err instanceof WretchErrorWrapper ? err.error : err\n if (err instanceof WretchErrorWrapper && catchers.has(\"__fetchError__\"))\n return catchers.get(\"__fetchError__\")(error, wretch)\n else if (catchers.has(error.status))\n return catchers.get(error.status)(error, wretch)\n else if (catchers.has(error.name))\n return catchers.get(error.name)(error, wretch)\n else\n throw error\n })\n }\n // Enforces the proper promise type when a body parsing method is called.\n type BodyParser = (funName: string | null) => (cb?: (type: Type) => Result) => Promise\n const bodyParser: BodyParser = funName => cb => funName ?\n // If a callback is provided, then callback with the body result otherwise return the parsed body itself.\n catchersWrapper(throwingPromise.then(_ => _ && _[funName]()).then(_ => cb ? cb(_) : _)) :\n // No body parsing method - return the response\n catchersWrapper(throwingPromise.then(_ => cb ? cb(_ as any) : _))\n\n const responseChain: WretchResponseChain = {\n wretchRequest: wretch,\n fetchRequest,\n res: bodyParser(null),\n json: bodyParser(\"json\"),\n blob: bodyParser(\"blob\"),\n formData: bodyParser(\"formData\"),\n arrayBuffer: bodyParser(\"arrayBuffer\"),\n text: bodyParser(\"text\"),\n error(errorId, cb) {\n catchers.set(errorId, cb)\n return this\n },\n badRequest(cb) { return this.error(400, cb) },\n unauthorized(cb) { return this.error(401, cb) },\n forbidden(cb) { return this.error(403, cb) },\n notFound(cb) { return this.error(404, cb) },\n timeout(cb) { return this.error(408, cb) },\n internalError(cb) { return this.error(500, cb) },\n fetchError(cb) { return this.error(\"__fetchError__\", cb) },\n }\n\n const enhancedResponseChain: Chain & WretchResponseChain = addons.reduce((chain, addon) => ({\n ...chain,\n ...(addon.resolver as any)\n }), responseChain)\n\n return resolvers.reduce((chain, r) => r(chain, wretch), enhancedResponseChain) as (Chain & WretchResponseChain & Promise)\n}\n","import { WretchOptions } from \"./types.js\"\nimport { WretchResponse } from \"./types.js\"\n\nexport type Middleware = (options?: { [key: string]: any }) => ConfiguredMiddleware\nexport type ConfiguredMiddleware = (next: FetchLike) => FetchLike\nexport type FetchLike = (url: string, opts: WretchOptions) => Promise\n\nexport const middlewareHelper = (middlewares: ConfiguredMiddleware[]) => (fetchFunction: FetchLike): FetchLike => {\n return (\n middlewares.length === 0 ?\n fetchFunction :\n middlewares.length === 1 ?\n middlewares[0](fetchFunction) :\n middlewares.reduceRight((acc, curr, idx): any =>\n (idx === middlewares.length - 2) ? curr(acc(fetchFunction)) : curr(acc as any)\n )\n ) as FetchLike\n}\n","import { mix, extractContentType, isLikelyJsonMime } from \"./utils.js\"\nimport * as Constants from \"./constants.js\"\nimport { resolver } from \"./resolver.js\"\nimport config from \"./config.js\"\nimport type { Config } from \"./config.js\"\nimport type { WretchError, WretchOptions, WretchDeferredCallback, WretchAddon } from \"./types.js\"\nimport type { WretchResponseChain } from \"./resolver.js\"\nimport type { ConfiguredMiddleware } from \"./middleware.js\"\n\n/**\n * The Wretch object used to perform easy fetch requests.\n *\n * Immutability : almost every method of this class return a fresh Wretch object.\n */\nexport interface Wretch {\n _url: string,\n _options: WretchOptions,\n _config: Config,\n _catchers: Map) => void>\n _resolvers: ((resolver: Chain & WretchResponseChain, originalRequest: Wretch) => any)[]\n _deferredChain: WretchDeferredCallback[]\n _middlewares: ConfiguredMiddleware[]\n _addons: WretchAddon[]\n\n /**\n * @private\n */\n clone(args: Record): this\n\n /**\n * Register an Addon to enhance the wretch or response objects.\n */\n addon: (addon: WretchAddon) => W & Self & Wretch\n\n /**\n * Sets the default fetch options used for every subsequent fetch call.\n * @param options - New default options\n * @param replace - If true, replaces the existing options instead of mixing in\n */\n defaults(this: Self & Wretch, options: WretchOptions, replace?: boolean): this\n\n /**\n * Sets the method (text, json ...) used to parse the data contained in the response body in case of an HTTP error.\n *\n * Persists for every subsequent requests.\n *\n * Default is \"text\".\n */\n errorType(this: Self & Wretch, method: string): this\n\n /**\n * Sets the non-global polyfills which will be used for every subsequent calls.\n *\n * Needed for libraries like [fetch-ponyfill](https://github.com/qubyte/fetch-ponyfill).\n *\n * @param polyfills - An object containing the polyfills\n * @param replace - If true, replaces the current polyfills instead of mixing in\n */\n polyfills(this: Self & Wretch, polyfills: Partial): this\n\n /**\n * Returns a new Wretch object with the argument url appended and the same options.\n * @param url - Url segment\n * @param replace - If true, replaces the current url instead of appending\n */\n url(this: Self & Wretch, url: string, replace?: boolean): this\n\n /**\n * Returns a new Wretch object with the same url and new options.\n * @param options - New options\n * @param mixin - If true, mixes in instead of replacing the existing options\n */\n options(this: Self & Wretch, options: WretchOptions, mixin?: boolean): this\n\n /**\n * Set request headers.\n * @param headerValues - An object containing header keys and values\n */\n headers(this: Self & Wretch, headerValues: HeadersInit): this\n\n /**\n * Shortcut to set the \"Accept\" header.\n * @param headerValue - Header value\n */\n accept(this: Self & Wretch, headerValue: string): this\n\n /**\n * Shortcut to set the \"Content-Type\" header.\n * @param headerValue - Header value\n */\n content(this: Self & Wretch, headerValue: string): this\n\n /**\n * Shortcut to set the \"Authorization\" header.\n * @param headerValue - Header value\n */\n auth(this: Self & Wretch, headerValue: string): this\n\n /**\n * Adds a default catcher which will be called on every subsequent request error when the error code matches.\n * @param errorId - Error code or name\n * @param catcher - The catcher method\n */\n catcher(this: Self & Wretch, errorId: number | string, catcher: (error: WretchError, originalRequest: Wretch) => any): this\n\n /**\n * Defer wretch methods that will be chained and called just before the request is performed.\n */\n defer(this: Self & Wretch, callback: WretchDeferredCallback, clear?: boolean): this\n\n /**\n * Program a resolver to perform response chain tasks automatically.\n * @param resolver - Resolver callback\n */\n resolve(this: Self & Wretch, resolver: (chain: Chain & WretchResponseChain, originalRequest: Wretch) => WretchResponseChain | Promise, clear?: boolean): this\n\n /**\n * Add middlewares to intercept a request before being sent.\n */\n middlewares(this: Self & Wretch, middlewares: ConfiguredMiddleware[], clear?: boolean): this\n\n method(this: Self & Wretch, method: string, options?: any, body?: any): Chain & WretchResponseChain\n /**\n * Performs a get request.\n */\n get(this: Self & Wretch, options?: WretchOptions): Chain & WretchResponseChain\n /**\n * Performs a delete request.\n */\n delete(this: Self & Wretch, options?: WretchOptions): Chain & WretchResponseChain\n /**\n * Performs a put request.\n */\n put(this: Self & Wretch, body?: any, options?: WretchOptions): Chain & WretchResponseChain\n /**\n * Performs a post request.\n */\n post(this: Self & Wretch, body?: any, options?: WretchOptions): Chain & WretchResponseChain\n /**\n * Performs a patch request.\n */\n patch(this: Self & Wretch, body?: any, options?: WretchOptions): Chain & WretchResponseChain\n /**\n * Performs a head request.\n */\n head(this: Self & Wretch, options?: WretchOptions): Chain & WretchResponseChain\n /**\n * Performs an options request\n */\n opts(this: Self & Wretch, options?: WretchOptions): Chain & WretchResponseChain\n /**\n * Replay a request.\n */\n replay(this: Self & Wretch, options?: WretchOptions): Chain & WretchResponseChain\n\n /**\n * Sets the request body with any content.\n * @param contents - The body contents\n */\n body(this: Self & Wretch, contents: any): this\n\n /**\n * Sets the content type header, stringifies an object and sets the request body.\n * @param jsObject - An object which will be serialized into a JSON\n * @param contentType - A custom content type.\n */\n json(this: Self & Wretch, jsObject: object, contentType?: string): this\n}\n\nexport const core: Wretch = {\n _url: \"\",\n _options: {},\n _config: config,\n _catchers: new Map(),\n _resolvers: [],\n _deferredChain: [],\n _middlewares: [],\n _addons: [],\n clone({\n url = this._url,\n options = this._options,\n config = this._config,\n catchers = this._catchers,\n resolvers = this._resolvers,\n deferredChain = this._deferredChain,\n middlewares = this._middlewares,\n addons = this._addons\n } = {}) {\n return {\n ...this,\n _url: url,\n _options: { ...options },\n _catchers: new Map(catchers),\n _config: { ...config },\n _resolvers: [...resolvers],\n _deferredChain: [...deferredChain],\n _middlewares: [...middlewares],\n _addons: [...addons]\n }\n },\n\n addon(addon) {\n return {\n ...this.clone({ addons: [...this._addons, addon] }),\n ...addon.wretch\n }\n },\n\n defaults(options, replace = false) {\n return this.clone({\n config: {\n ...this._config,\n defaults: replace ? options : mix(this._config.defaults, options)\n }\n })\n },\n\n errorType(errorType: string) {\n return this.clone({ errorType })\n },\n\n polyfills(polyfills, replace = false) {\n return this.clone({\n config: {\n ...this._config,\n polyfills: replace ? polyfills : mix(this._config.polyfills, polyfills)\n }\n })\n },\n\n url(url, replace = false) {\n if (replace)\n return this.clone({ url })\n const split = this._url.split(\"?\")\n return this.clone({\n url: split.length > 1 ?\n split[0] + url + \"?\" + split[1] :\n this._url + url\n })\n },\n options(options, mixin = true) {\n return this.clone({ options: mixin ? mix(this._options, options) : options })\n },\n headers(headerValues) {\n return this.clone({ options: mix(this._options, { headers: headerValues || {} }) })\n },\n accept(headerValue) {\n return this.headers({ Accept: headerValue })\n },\n content(headerValue) {\n return this.headers({ [Constants.CONTENT_TYPE_HEADER]: headerValue })\n },\n auth(headerValue) {\n return this.headers({ Authorization: headerValue })\n },\n catcher(errorId, catcher) {\n const newMap = new Map(this._catchers)\n newMap.set(errorId, catcher)\n return this.clone({ catchers: newMap })\n },\n resolve(resolver, clear: boolean = false) {\n return this.clone({ resolvers: clear ? [resolver] : [...this._resolvers, resolver] })\n },\n defer(callback, clear = false) {\n return this.clone({\n deferredChain: clear ? [callback] : [...this._deferredChain, callback]\n })\n },\n middlewares(middlewares, clear = false) {\n return this.clone({\n middlewares: clear ? middlewares : [...this._middlewares, ...middlewares]\n })\n },\n method(method: string, options = {}, body = null) {\n let base = this.options({ ...options, method })\n // \"Jsonify\" the body if it is an object and if it is likely that the content type targets json.\n const contentType = extractContentType(base._options.headers)\n const jsonify = typeof body === \"object\" && (!base._options.headers || !contentType || isLikelyJsonMime(contentType))\n base =\n !body ? base :\n jsonify ? base.json(body, contentType) :\n base.body(body)\n return resolver(\n base\n ._deferredChain\n .reduce((acc: Wretch, curr) => curr(acc, acc._url, acc._options), base)\n )\n },\n get(options) {\n return this.method(\"GET\", options)\n },\n delete(options) {\n return this.method(\"DELETE\", options)\n },\n put(body, options) {\n return this.method(\"PUT\", options, body)\n },\n post(body, options) {\n return this.method(\"POST\", options, body)\n },\n patch(body, options) {\n return this.method(\"PATCH\", options, body)\n },\n head(options) {\n return this.method(\"HEAD\", options)\n },\n opts(options) {\n return this.method(\"OPTIONS\", options)\n },\n replay(options) {\n return this.method(this._options.method, options)\n },\n body(contents) {\n return this.clone({ options: { ...this._options, body: contents } })\n },\n json(jsObject, contentType) {\n const currentContentType = extractContentType(this._options.headers)\n return this.content(\n contentType ||\n isLikelyJsonMime(currentContentType) && currentContentType ||\n Constants.JSON_MIME\n ).body(JSON.stringify(jsObject))\n }\n}","import type { WretchResponseChain } from \"../resolver.js\"\nimport type { Wretch, WretchAddon, WretchErrorCallback } from \"../types.js\"\n\nexport interface AbortWretch {\n /**\n * Associates a custom signal with the request.\n * @param controller - An AbortController\n */\n signal: (this: T & Wretch, controller: AbortController) => this\n}\n\nexport interface AbortResolver {\n /**\n * Aborts the request after a fixed time.\n *\n * @param time - Time in milliseconds\n * @param controller - A custom controller\n */\n setTimeout: (this: C & WretchResponseChain, time: number, controller?: AbortController) => this\n /**\n * Returns the automatically generated AbortController alongside the current wretch response as a pair.\n */\n controller: (this: C & WretchResponseChain) => [any, this]\n /**\n * Catches an AbortError and performs a callback.\n */\n onAbort: (this: C & WretchResponseChain, cb: WretchErrorCallback) => this\n}\n\n/**\n * Adds the ability to abort requests using AbortController and signals under the hood.\n *\n * ```js\n * import AbortAddon from \"wetch/addons/abort\"\n *\n * wretch().addon(AbortAddon())\n * ```\n */\nconst abort: () => WretchAddon = () => {\n let timeout = null\n let fetchController = null\n return {\n beforeRequest(wretch, options) {\n fetchController = wretch._config.polyfill(\"AbortController\", { doThrow: false, instance: true })\n if (!options[\"signal\"] && fetchController) {\n options[\"signal\"] = fetchController.signal\n }\n timeout = {\n ref: null,\n clear() {\n if (timeout.ref) {\n clearTimeout(timeout.ref)\n timeout.ref = null\n }\n }\n }\n },\n wretch: {\n signal(controller) {\n return this.clone({ options: { ...this._options, signal: controller.signal } })\n },\n },\n resolver: {\n setTimeout(time, controller = fetchController) {\n timeout.clear()\n timeout.ref = setTimeout(() => controller.abort(), time)\n return this\n },\n controller() { return [fetchController, this] },\n onAbort(cb) { return this.error(\"AbortError\", cb) }\n },\n }\n}\n\nexport default abort","import type { Wretch } from \"../core.js\"\nimport type { Config } from \"../config.js\"\nimport type { WretchAddon } from \"../types.js\"\n\nfunction convertFormData(\n formObject: object,\n recursive: string[] | boolean = false,\n config: Config,\n formData = config.polyfill(\"FormData\", { instance: true }),\n ancestors = [],\n) {\n Object.entries(formObject).forEach(([key, value]) => {\n let formKey = ancestors.reduce((acc, ancestor) => (\n acc ? `${acc}[${ancestor}]` : ancestor\n ), null)\n formKey = formKey ? `${formKey}[${key}]` : key\n if (value instanceof Array) {\n for (const item of value)\n formData.append(formKey + \"[]\", item)\n } else if (\n recursive &&\n typeof value === \"object\" &&\n (\n !(recursive instanceof Array) ||\n !recursive.includes(key)\n )\n ) {\n if (value !== null) {\n convertFormData(value, recursive, config, formData, [...ancestors, key])\n }\n } else {\n formData.append(formKey, value)\n }\n })\n\n return formData\n}\n\nexport interface FormDataAddon {\n /**\n * Converts the javascript object to a FormData and sets the request body.\n * @param formObject - An object which will be converted to a FormData\n * @param recursive - If `true`, will recurse through all nested objects\n * Can be set as an array of string to exclude specific keys.\n * @see https://github.com/elbywan/wretch/issues/68 for more details.\n */\n formData(this: T & Wretch, formObject: object, recursive?: string[] | boolean): this\n}\n\n/**\n * Adds the ability to convert a an object to a FormData and use it as a request body.\n *\n * ```js\n * import FormDataAddon from \"wretch/addons/formData\"\n *\n * wretch().addon(FormDataAddon)\n * ```\n */\nconst formData: WretchAddon = {\n wretch: {\n formData(formObject, recursive = false) {\n return this.body(convertFormData(formObject, recursive, this._config))\n }\n }\n}\n\nexport default formData","import type { Wretch } from \"../core.js\"\nimport type { WretchAddon } from \"../types.js\"\n\nfunction encodeQueryValue(key: string, value: unknown) {\n return encodeURIComponent(key) +\n \"=\" +\n encodeURIComponent(\n typeof value === \"object\" ?\n JSON.stringify(value) :\n \"\" + value\n )\n}\nfunction convertFormUrl(formObject: object) {\n return Object.keys(formObject)\n .map(key => {\n const value = formObject[key]\n if (value instanceof Array) {\n return value.map(v => encodeQueryValue(key, v)).join(\"&\")\n }\n return encodeQueryValue(key, value)\n })\n .join(\"&\")\n}\n\nexport interface FormUrlAddon {\n /**\n * Converts the input to an url encoded string and sets the content-type header and body.\n * If the input argument is already a string, skips the conversion part.\n *\n * @param input - An object to convert into an url encoded string or an already encoded string\n */\n formUrl(this: T & Wretch, input: (object | string)): this\n}\n\n/**\n * Adds the ability to convert a an object to a FormUrl and use it as a request body.\n *\n * ```js\n * import FormUrlAddon from \"wretch/addons/formUrl\"\n *\n * wretch().addon(FormUrlAddon)\n * ```\n */\nconst formUrl: WretchAddon = {\n wretch: {\n formUrl(input) {\n return this\n .body(typeof input === \"string\" ? input : convertFormUrl(input))\n .content(\"application/x-www-form-urlencoded\")\n }\n }\n}\n\nexport default formUrl","import type { WretchResponseChain } from \"../resolver.js\"\nimport type { WretchAddon } from \"../types.js\"\n\nconst onMatch = (entries, name, callback, _performance) => {\n if (!entries.getEntriesByName)\n return false\n const matches = entries.getEntriesByName(name)\n if (matches && matches.length > 0) {\n callback(matches.reverse()[0])\n if (_performance.clearMeasures)\n _performance.clearMeasures(name)\n utils.callbacks.delete(name)\n\n if (utils.callbacks.size < 1) {\n utils.observer.disconnect()\n if (_performance.clearResourceTimings) {\n _performance.clearResourceTimings()\n }\n }\n return true\n }\n return false\n}\n\nconst lazyObserver = (_performance, _observer) => {\n if (!utils.observer && _performance && _observer) {\n utils.observer = new _observer(entries => {\n utils.callbacks.forEach((callback, name) => {\n onMatch(entries, name, callback, _performance)\n })\n })\n if (_performance.clearResourceTimings)\n _performance.clearResourceTimings()\n }\n return utils.observer\n}\n\nconst utils = {\n callbacks: new Map(),\n observer: null,\n observe: (name, callback, config) => {\n if (!name || !callback)\n return\n\n const _performance = config.polyfill(\"performance\", { doThrow: false })\n const _observer = config.polyfill(\"PerformanceObserver\", { doThrow: false })\n\n if (!lazyObserver(_performance, _observer))\n return\n\n if (!onMatch(_performance, name, callback, _performance)) {\n if (utils.callbacks.size < 1)\n utils.observer.observe({ entryTypes: [\"resource\", \"measure\"] })\n utils.callbacks.set(name, callback)\n }\n\n }\n}\n\nexport interface PerfsAddon {\n /**\n * Performs a callback on the API performance timings of the request.\n *\n * Warning: Still experimental on browsers and node.js\n */\n perfs: (this: C & WretchResponseChain, cb?: (timing: any) => void) => this,\n}\n\n/**\n * Adds the ability to measure requests using the Performance Timings API.\n *\n * ```js\n * import PerfsAddon from \"wretch/addons/perfs\"\n *\n * wretch().addon(PerfsAddon())\n * ```\n */\nconst perfs: () => WretchAddon = () => ({\n resolver: {\n perfs(cb) {\n this.fetchRequest.then(res => utils.observe(res.url, cb, this.wretchRequest._config)).catch(() => {/* swallow */ })\n return this\n },\n }\n})\n\nexport default perfs","import type { Wretch, Config, WretchAddon } from \"../types.js\"\n\nconst appendQueryParams = (url: string, qp: object | string, replace: boolean, config: Config) => {\n let queryString: string\n\n if (typeof qp === \"string\") {\n queryString = qp\n } else {\n const usp = config.polyfill(\"URLSearchParams\", { instance: true })\n for (const key in qp) {\n if (qp[key] instanceof Array) {\n for (const val of qp[key])\n usp.append(key, val)\n } else {\n usp.append(key, qp[key])\n }\n }\n queryString = usp.toString()\n }\n\n const split = url.split(\"?\")\n\n if (!queryString)\n return replace ? split[0] : url\n\n if (replace || split.length < 2)\n return split[0] + \"?\" + queryString\n\n return url + \"&\" + queryString\n}\n\nexport interface QueryStringAddon {\n /**\n * Converts a javascript object to query parameters,\n * then appends this query string to the current url.\n *\n * If given a string, use the string as the query verbatim.\n *\n * ```\n * import QueryAddon from \"wretch/addons/queryString\"\n *\n * let w = wretch(\"http://example.com\").addon(QueryAddon) // url is http://example.com\n *\n * // Chain query calls\n * w = w.query({ a: 1, b : 2 }) // url is now http://example.com?a=1&b=2\n * w = w.query(\"foo-bar-baz-woz\") // url is now http://example.com?a=1&b=2&foo-bar-baz-woz\n *\n * // Pass true as the second argument to replace existing query parameters\n * w = w.query(\"c=3&d=4\", true) // url is now http://example.com?c=3&d=4\n * ```\n *\n * @param qp - An object which will be converted, or a string which will be used verbatim.\n */\n query(this: T & Wretch, qp: object | string, replace?: boolean): this\n}\n\n/**\n * Adds the ability to append query parameters from a javascript object.\n *\n * ```js\n * import QueryAddon from \"wretch/addons/queryString\"\n *\n * wretch().addon(QueryAddon)\n * ```\n */\nconst queryString: WretchAddon = {\n wretch: {\n query(qp, replace = false) {\n return this.clone({ url: appendQueryParams(this._url, qp, replace, this._config) })\n }\n }\n}\n\nexport default queryString","import { setDefaults, setErrorType, setPolyfills } from \"./config.js\"\nimport { core } from \"./core.js\"\nimport * as Addons from \"./addons/index.js\"\n\nfunction factory(url = \"\", options = {}) {\n return core\n .clone({ url, options })\n .addon(Addons.abortAddon())\n .addon(Addons.formDataAddon)\n .addon(Addons.formUrlAddon)\n .addon(Addons.perfsAddon())\n .addon(Addons.queryStringAddon)\n}\n\nfactory[\"default\"] = factory\nfactory.defaults = setDefaults\nfactory.errorType = setErrorType\nfactory.polyfills = setPolyfills\nexport default factory"],"names":["CONTENT_TYPE_HEADER","extractContentType","headers","_a","Object","entries","find","k","toLowerCase","Constants.CONTENT_TYPE_HEADER","isLikelyJsonMime","value","test","mix","one","two","mergeArrays","clone","assign","prop","hasOwnProperty","Array","config","defaults","errorType","polyfills","polyfill","p","doThrow","instance","args","res","this","self","global","Error","WretchErrorWrapper","constructor","error","resolver","wretch","_url","url","_options","opts","_config","_catchers","_resolvers","resolvers","_middlewares","middlewares","_addons","addons","catchers","Map","finalOptions","forEach","addon","beforeRequest","fetchRequest","fetchFunction","length","reduceRight","acc","curr","idx","middlewareHelper","throwingPromise","catch","then","response","ok","type","err","body","status","catchersWrapper","promise","has","get","name","bodyParser","funName","cb","_","responseChain","wretchRequest","json","blob","formData","arrayBuffer","text","errorId","set","badRequest","unauthorized","forbidden","notFound","timeout","internalError","fetchError","enhancedResponseChain","reduce","chain","r","core","_deferredChain","options","deferredChain","replace","split","mixin","headerValues","accept","headerValue","Accept","content","auth","Authorization","catcher","newMap","resolve","clear","defer","callback","method","base","contentType","jsonify","delete","put","post","patch","head","replay","contents","jsObject","currentContentType","JSON","stringify","abort","fetchController","signal","ref","clearTimeout","controller","setTimeout","time","onAbort","convertFormData","formObject","recursive","ancestors","key","formKey","ancestor","item","append","includes","encodeQueryValue","encodeURIComponent","formUrl","input","keys","map","v","join","onMatch","_performance","getEntriesByName","matches","reverse","clearMeasures","utils","callbacks","size","observer","disconnect","clearResourceTimings","observe","_observer","lazyObserver","entryTypes","perfs","appendQueryParams","qp","queryString","usp","val","toString","query","factory","Addons.abortAddon","Addons.formDataAddon","Addons.formUrlAddon","Addons.perfsAddon","Addons.queryStringAddon"],"mappings":"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"} \ No newline at end of file diff --git a/test/browser/src/wretch.min.js b/test/browser/src/wretch.min.js deleted file mode 100644 index e563e32..0000000 --- a/test/browser/src/wretch.min.js +++ /dev/null @@ -1,2 +0,0 @@ -!function(t,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define(r):(t="undefined"!=typeof globalThis?globalThis:t||self).wretch=r()}(this,(function(){"use strict";var t=function(){return(t=Object.assign||function(t){for(var r,e=1,n=arguments.length;e0)&&(e(o.reverse()[0]),n.clearMeasures&&n.clearMeasures(r),i.callbacks.delete(r),i.callbacks.size<1&&(i.observer.disconnect(),n.clearResourceTimings&&n.clearResourceTimings()),!0)},i={callbacks:new Map,observer:null,observe:function(t,r){if(t&&r){var e=n.polyfill("performance",{doThrow:!1});(function(t,r){return!i.observer&&t&&r&&(i.observer=new r((function(r){i.callbacks.forEach((function(e,n){o(r,n,e,t)}))})),t.clearResourceTimings&&t.clearResourceTimings()),i.observer})(e,n.polyfill("PerformanceObserver",{doThrow:!1}))&&(o(e,t,r,e)||(i.callbacks.size<1&&i.observer.observe({entryTypes:["resource","measure"]}),i.callbacks.set(t,r)))}}},s=function(t){this.error=t},u="Content-Type";function a(t){var r;return void 0===t&&(t={}),null===(r=Object.entries(t).find((function(t){return t[0].toLowerCase()===u.toLowerCase()})))||void 0===r?void 0:r[1]}function c(t){return/^application\/.*json.*/.test(t)}var f=function(){function o(t,r,e,n,o,i){void 0===e&&(e=new Map),void 0===n&&(n=[]),void 0===o&&(o=[]),void 0===i&&(i=[]),this._url=t,this._options=r,this._catchers=e,this._resolvers=n,this._middlewares=o,this._deferredChain=i}return o.factory=function(t,r){return void 0===t&&(t=""),void 0===r&&(r={}),new o(t,r)},o.prototype.selfFactory=function(e){var n=void 0===e?{}:e,i=n.url,s=void 0===i?this._url:i,u=n.options,a=void 0===u?this._options:u,c=n.catchers,f=void 0===c?this._catchers:c,l=n.resolvers,p=void 0===l?this._resolvers:l,h=n.middlewares,d=void 0===h?this._middlewares:h,y=n.deferredChain,v=void 0===y?this._deferredChain:y;return new o(s,t({},a),new Map(f),r([],p,!0),r([],d,!0),r([],v,!0))},o.prototype.defaults=function(t,r){return void 0===r&&(r=!1),n.defaults=r?e(n.defaults,t):t,this},o.prototype.errorType=function(t){return n.errorType=t,this},o.prototype.polyfills=function(r){return n.polyfills=t(t({},n.polyfills),r),this},o.prototype.url=function(t,r){if(void 0===r&&(r=!1),r)return this.selfFactory({url:t});var e=this._url.split("?");return this.selfFactory({url:e.length>1?e[0]+t+"?"+e[1]:this._url+t})},o.prototype.options=function(t,r){return void 0===r&&(r=!0),this.selfFactory({options:r?e(this._options,t):t})},o.prototype.query=function(t,r){return void 0===r&&(r=!1),this.selfFactory({url:l(this._url,t,r)})},o.prototype.headers=function(t){return this.selfFactory({options:e(this._options,{headers:t||{}})})},o.prototype.accept=function(t){return this.headers({Accept:t})},o.prototype.content=function(t){var r;return this.headers(((r={})["Content-Type"]=t,r))},o.prototype.auth=function(t){return this.headers({Authorization:t})},o.prototype.catcher=function(t,r){var e=new Map(this._catchers);return e.set(t,r),this.selfFactory({catchers:e})},o.prototype.signal=function(r){return this.selfFactory({options:t(t({},this._options),{signal:r.signal})})},o.prototype.resolve=function(t,e){return void 0===e&&(e=!1),this.selfFactory({resolvers:e?[t]:r(r([],this._resolvers,!0),[t],!1)})},o.prototype.defer=function(t,e){return void 0===e&&(e=!1),this.selfFactory({deferredChain:e?[t]:r(r([],this._deferredChain,!0),[t],!1)})},o.prototype.middlewares=function(t,e){return void 0===e&&(e=!1),this.selfFactory({middlewares:e?t:r(r([],this._middlewares,!0),t,!0)})},o.prototype.method=function(r,o,u){void 0===o&&(o={}),void 0===u&&(u=null);var f=this.options(t(t({},o),{method:r})),l=a(f._options.headers),p="object"==typeof u&&(!f._options.headers||!l||c(l));return function(t){var r=t._url,o=t._catchers,u=t._resolvers,a=t._middlewares,c=t._options,f=new Map(o),l=e(n.defaults,c),p=n.polyfill("AbortController",{doThrow:!1,instance:!0});!l.signal&&p&&(l.signal=p.signal);var h={ref:null,clear:function(){h.ref&&(clearTimeout(h.ref),h.ref=null)}},d=function(t){return function(r){return 0===t.length?r:1===t.length?t[0](r):t.reduceRight((function(e,n,o){return o===t.length-2?n(e(r)):n(e)}))}}(a)(n.polyfill("fetch"))(r,l),y=d.catch((function(t){throw new s(t)})).then((function(t){if(h.clear(),!t.ok){if("opaque"===t.type){var r=new Error("Opaque response");throw r.status=t.status,r.response=t,r}return t[n.errorType||"text"]().then((function(r){var e=new Error(r);throw e[n.errorType||"text"]=r,e.status=t.status,e.response=t,e}))}return t})),v=function(r){return r.catch((function(r){h.clear();var e=r instanceof s?r.error:r;if(r instanceof s&&f.has("__fromFetch"))return f.get("__fromFetch")(e,t);if(f.has(e.status))return f.get(e.status)(e,t);if(f.has(e.name))return f.get(e.name)(e,t);throw e}))},m=function(t){return function(r){return v(t?y.then((function(r){return r&&r[t]()})).then((function(t){return r?r(t):t})):y.then((function(t){return r?r(t):t})))}},b={res:m(null),json:m("json"),blob:m("blob"),formData:m("formData"),arrayBuffer:m("arrayBuffer"),text:m("text"),perfs:function(t){return d.then((function(r){return i.observe(r.url,t)})),b},setTimeout:function(t,r){return void 0===r&&(r=p),h.clear(),h.ref=setTimeout((function(){return r.abort()}),t),b},controller:function(){return[p,b]},error:function(t,r){return f.set(t,r),b},badRequest:function(t){return b.error(400,t)},unauthorized:function(t){return b.error(401,t)},forbidden:function(t){return b.error(403,t)},notFound:function(t){return b.error(404,t)},timeout:function(t){return b.error(408,t)},internalError:function(t){return b.error(500,t)},fetchError:function(t){return b.error("__fromFetch",t)},onAbort:function(t){return b.error("AbortError",t)}};return u.reduce((function(r,e){return e(r,t)}),b)}((f=u?p?f.json(u,l):f.body(u):f)._deferredChain.reduce((function(t,r){return r(t,t._url,t._options)}),f))},o.prototype.get=function(t){return this.method("GET",t)},o.prototype.delete=function(t){return this.method("DELETE",t)},o.prototype.put=function(t,r){return this.method("PUT",r,t)},o.prototype.post=function(t,r){return this.method("POST",r,t)},o.prototype.patch=function(t,r){return this.method("PATCH",r,t)},o.prototype.head=function(t){return this.method("HEAD",t)},o.prototype.opts=function(t){return this.method("OPTIONS",t)},o.prototype.replay=function(t){return this.method(this._options.method,t)},o.prototype.body=function(r){return this.selfFactory({options:t(t({},this._options),{body:r})})},o.prototype.json=function(t,r){var e=a(this._options.headers);return this.content(r||c(e)&&e||"application/json").body(JSON.stringify(t))},o.prototype.formData=function(t,r){return void 0===r&&(r=!1),this.body(p(t,r))},o.prototype.formUrl=function(t){return this.body("string"==typeof t?t:(r=t,Object.keys(r).map((function(t){var e=r[t];return e instanceof Array?e.map((function(r){return h(t,r)})).join("&"):h(t,e)})).join("&"))).content("application/x-www-form-urlencoded");var r},o}(),l=function(t,r,e){var o;if("string"==typeof r)o=r;else{var i=n.polyfill("URLSearchParams",{instance:!0});for(var s in r)if(r[s]instanceof Array)for(var u=0,a=r[s];u {\n if(!entries.getEntriesByName)\n return false\n const matches = entries.getEntriesByName(name)\n if(matches && matches.length > 0) {\n callback(matches.reverse()[0])\n if(_performance.clearMeasures)\n _performance.clearMeasures(name)\n perfs.callbacks.delete(name)\n\n if(perfs.callbacks.size < 1) {\n perfs.observer.disconnect()\n if(_performance.clearResourceTimings) {\n _performance.clearResourceTimings()\n }\n }\n return true\n }\n return false\n}\n\nconst lazyObserver = (_performance, _observer) => {\n if(!perfs.observer && _performance && _observer) {\n perfs.observer = new _observer(entries => {\n perfs.callbacks.forEach((callback, name) => {\n onMatch(entries, name, callback, _performance)\n })\n })\n if(_performance.clearResourceTimings)\n _performance.clearResourceTimings()\n }\n return perfs.observer\n}\n\nconst perfs = {\n callbacks: new Map(),\n observer: null,\n observe: (name, callback) => {\n if(!name || !callback)\n return\n\n const _performance = conf.polyfill(\"performance\", { doThrow: false })\n const _observer = conf.polyfill(\"PerformanceObserver\", { doThrow: false })\n\n if(!lazyObserver(_performance, _observer))\n return\n\n if(!onMatch(_performance, name, callback, _performance)) {\n if(perfs.callbacks.size < 1)\n perfs.observer.observe({ entryTypes: [\"resource\", \"measure\"] })\n perfs.callbacks.set(name, callback)\n }\n\n }\n}\n\nexport default perfs\n","import { Wretcher } from \"./wretcher\"\nimport { mix } from \"./mix\"\nimport conf from \"./config\"\nimport perfs from \"./perfs\"\nimport { middlewareHelper } from \"./middleware\"\n\nexport type WretcherError = Error & { status: number, response: WretcherResponse, text?: string, json?: any }\nexport type WretcherErrorCallback = (error: WretcherError, originalRequest: Wretcher) => any\nexport type WretcherResponse = Response & { [key: string]: any }\nexport type ResponseChain = {\n // Response types\n res: (cb?: (type: WretcherResponse) => Result) => Promise,\n json: (cb?: (type: { [key: string]: any }) => Result) => Promise,\n blob: (cb?: (type: Blob) => Result) => Promise,\n formData: (cb?: (type: FormData) => Result) => Promise,\n arrayBuffer: (cb?: (type: ArrayBuffer) => Result) => Promise,\n text: (cb?: (type: string) => Result) => Promise,\n // Extras\n perfs: (cb?: (timing: any) => void) => ResponseChain,\n setTimeout: (time: number, controller?: AbortController) => ResponseChain,\n controller: () => [any, ResponseChain],\n // Catchers\n error: (code: (number | string), cb: WretcherErrorCallback) => ResponseChain,\n badRequest: (cb: WretcherErrorCallback) => ResponseChain,\n unauthorized: (cb: WretcherErrorCallback) => ResponseChain,\n forbidden: (cb: WretcherErrorCallback) => ResponseChain,\n notFound: (cb: WretcherErrorCallback) => ResponseChain,\n timeout: (cb: WretcherErrorCallback) => ResponseChain,\n internalError: (cb: WretcherErrorCallback) => ResponseChain,\n fetchError: (cb: WretcherErrorCallback) => ResponseChain,\n onAbort: (cb: WretcherErrorCallback) => ResponseChain\n}\n\nclass WretchErrorWrapper {\n constructor(public error: any) { }\n}\n\nexport const resolver = (wretcher: Wretcher) => {\n const {\n _url: url,\n _catchers: _catchers,\n _resolvers: resolvers,\n _middlewares: middlewares,\n _options: opts\n } = wretcher\n const catchers = new Map(_catchers)\n const finalOptions = mix(conf.defaults, opts)\n const fetchController = conf.polyfill(\"AbortController\", { doThrow: false, instance: true })\n if (!finalOptions[\"signal\"] && fetchController) {\n finalOptions[\"signal\"] = fetchController.signal\n }\n // Request timeout\n const timeout = {\n ref: null,\n clear() {\n if (timeout.ref) {\n clearTimeout(timeout.ref)\n timeout.ref = null\n }\n }\n }\n // The generated fetch request\n const fetchRequest = middlewareHelper(middlewares)(conf.polyfill(\"fetch\"))(url, finalOptions)\n // Throws on an http error\n const throwingPromise: Promise = fetchRequest\n .catch(error => {\n throw new WretchErrorWrapper(error)\n })\n .then(response => {\n timeout.clear()\n if (!response.ok) {\n if (response.type === \"opaque\") {\n const err = new Error(\"Opaque response\")\n err[\"status\"] = response.status\n err[\"response\"] = response\n throw err\n }\n return response[conf.errorType || \"text\"]().then(msg => {\n // Enhances the error object\n const err = new Error(msg)\n err[conf.errorType || \"text\"] = msg\n err[\"status\"] = response.status\n err[\"response\"] = response\n throw err\n })\n }\n return response\n })\n // Wraps the Promise in order to dispatch the error to a matching catcher\n const catchersWrapper = (promise: Promise): Promise => {\n return promise.catch(err => {\n timeout.clear()\n const error = err instanceof WretchErrorWrapper ? err.error : err\n if (err instanceof WretchErrorWrapper && catchers.has(\"__fromFetch\"))\n return catchers.get(\"__fromFetch\")(error, wretcher)\n else if (catchers.has(error.status))\n return catchers.get(error.status)(error, wretcher)\n else if (catchers.has(error.name))\n return catchers.get(error.name)(error, wretcher)\n else\n throw error\n })\n }\n // Enforces the proper promise type when a body parsing method is called.\n type BodyParser = (funName: string | null) => (cb?: (type: Type) => Result) => Promise\n const bodyParser: BodyParser = (funName) => (cb) => funName ?\n // If a callback is provided, then callback with the body result otherwise return the parsed body itself.\n catchersWrapper(throwingPromise.then(_ => _ && _[funName]()).then(_ => cb ? cb(_) : _)) :\n // No body parsing method - return the response\n catchersWrapper(throwingPromise.then(_ => cb ? cb(_) : _))\n\n const responseChain: ResponseChain = {\n /**\n * Retrieves the raw result as a promise.\n */\n res: bodyParser(null),\n /**\n * Retrieves the result as a parsed JSON object.\n */\n json: bodyParser(\"json\"),\n /**\n * Retrieves the result as a Blob object.\n */\n blob: bodyParser(\"blob\"),\n /**\n * Retrieves the result as a FormData object.\n */\n formData: bodyParser(\"formData\"),\n /**\n * Retrieves the result as an ArrayBuffer object.\n */\n arrayBuffer: bodyParser(\"arrayBuffer\"),\n /**\n * Retrieves the result as a string.\n */\n text: bodyParser(\"text\"),\n /**\n * Performs a callback on the API performance timings of the request.\n *\n * Warning: Still experimental on browsers and node.js\n */\n perfs: cb => {\n fetchRequest.then(res => perfs.observe(res.url, cb))\n return responseChain\n },\n /**\n * Aborts the request after a fixed time.\n *\n * @param time Time in milliseconds\n * @param controller A custom controller\n */\n setTimeout: (time, controller = fetchController) => {\n timeout.clear()\n timeout.ref = setTimeout(() => controller.abort(), time)\n return responseChain\n },\n /**\n * Returns the automatically generated AbortController alongside the current wretch response as a pair.\n */\n controller: () => [fetchController, responseChain],\n /**\n * Catches an http response with a specific error code or name and performs a callback.\n */\n error(errorId, cb) {\n catchers.set(errorId, cb)\n return responseChain\n },\n /**\n * Catches a bad request (http code 400) and performs a callback.\n */\n badRequest: cb => responseChain.error(400, cb),\n /**\n * Catches an unauthorized request (http code 401) and performs a callback.\n */\n unauthorized: cb => responseChain.error(401, cb),\n /**\n * Catches a forbidden request (http code 403) and performs a callback.\n */\n forbidden: cb => responseChain.error(403, cb),\n /**\n * Catches a \"not found\" request (http code 404) and performs a callback.\n */\n notFound: cb => responseChain.error(404, cb),\n /**\n * Catches a timeout (http code 408) and performs a callback.\n */\n timeout: cb => responseChain.error(408, cb),\n /**\n * Catches an internal server error (http code 500) and performs a callback.\n */\n internalError: cb => responseChain.error(500, cb),\n /**\n * Catches errors thrown when calling the fetch function and performs a callback.\n */\n fetchError: cb => responseChain.error(\"__fromFetch\", cb),\n /**\n * Catches an AbortError and performs a callback.\n */\n onAbort: cb => responseChain.error(\"AbortError\", cb)\n }\n\n return resolvers.reduce((chain, r) => r(chain, wretcher), responseChain) as (ResponseChain & Promise)\n}\n","import { mix } from \"./mix\"\nimport conf from \"./config\"\nimport { resolver, WretcherError, ResponseChain } from \"./resolver\"\nimport { ConfiguredMiddleware } from \"./middleware\"\n\nexport type WretcherOptions = RequestInit & {\n [key: string]: any\n}\n\nexport type DeferredCallback = (wretcher: Wretcher, url: string, options: WretcherOptions) => Wretcher\n\nconst JSON_MIME = \"application/json\"\nconst CONTENT_TYPE_HEADER = \"Content-Type\"\n\nfunction extractContentType(headers: HeadersInit = {}): string | undefined {\n return Object.entries(headers).find(([k]) =>\n k.toLowerCase() === CONTENT_TYPE_HEADER.toLowerCase()\n )?.[1]\n}\n\nfunction isLikelyJsonMime(value: string): boolean {\n return /^application\\/.*json.*/.test(value)\n}\n\n/**\n * The Wretcher class used to perform easy fetch requests.\n *\n * Immutability : almost every method of this class return a fresh Wretcher object.\n */\nexport class Wretcher {\n\n protected constructor(\n public _url: string,\n public _options: WretcherOptions,\n public _catchers: Map void> = new Map(),\n public _resolvers: ((resolver: ResponseChain, originalRequest: Wretcher) => any)[] = [],\n public _middlewares: ConfiguredMiddleware[] = [],\n public _deferredChain: DeferredCallback[] = []) { }\n\n static factory(url = \"\", options: WretcherOptions = {}) { return new Wretcher(url, options) }\n private selfFactory({ url = this._url, options = this._options, catchers = this._catchers,\n resolvers = this._resolvers, middlewares = this._middlewares, deferredChain = this._deferredChain } = {}) {\n return new Wretcher(url, { ...options }, new Map(catchers), [...resolvers], [...middlewares], [...deferredChain])\n }\n\n /**\n * Sets the default fetch options used for every subsequent fetch call.\n * @param options New default options\n * @param mixin If true, mixes in instead of replacing the existing options\n */\n defaults(options: WretcherOptions, mixin = false) {\n conf.defaults = mixin ? mix(conf.defaults, options) : options\n return this\n }\n\n /**\n * Sets the method (text, json ...) used to parse the data contained in the response body in case of an HTTP error.\n *\n * Persists for every subsequent requests.\n *\n * Default is \"text\".\n */\n errorType(method: string) {\n conf.errorType = method\n return this\n }\n\n /**\n * Sets the non-global polyfills which will be used for every subsequent calls.\n *\n * Needed for libraries like [fetch-ponyfill](https://github.com/qubyte/fetch-ponyfill).\n *\n * @param polyfills An object containing the polyfills.\n */\n polyfills(polyfills: Partial) {\n conf.polyfills = { ...conf.polyfills, ...polyfills }\n return this\n }\n\n /**\n * Returns a new Wretcher object with the argument url appended and the same options.\n * @param url String url\n * @param replace Boolean If true, replaces the current url instead of appending\n */\n url(url: string, replace = false) {\n if (replace)\n return this.selfFactory({ url })\n const split = this._url.split(\"?\")\n return this.selfFactory({\n url: split.length > 1 ?\n split[0] + url + \"?\" + split[1] :\n this._url + url\n })\n }\n\n /**\n * Returns a new Wretcher object with the same url and new options.\n * @param options New options\n * @param mixin If true, mixes in instead of replacing the existing options\n */\n options(options: WretcherOptions, mixin = true) {\n return this.selfFactory({ options: mixin ? mix(this._options, options) : options })\n }\n\n /**\n * Converts a javascript object to query parameters,\n * then appends this query string to the current url.\n *\n * If given a string, use the string as the query verbatim.\n *\n * ```\n * let w = wretch(\"http://example.com\") // url is http://example.com\n *\n * // Chain query calls\n * w = w.query({ a: 1, b : 2 }) // url is now http://example.com?a=1&b=2\n * w = w.query(\"foo-bar-baz-woz\") // url is now http://example.com?a=1&b=2&foo-bar-baz-woz\n *\n * // Pass true as the second argument to replace existing query parameters\n * w = w.query(\"c=3&d=4\", true) // url is now http://example.com?c=3&d=4\n * ```\n *\n * @param qp An object which will be converted, or a string which will be used verbatim.\n */\n query(qp: object | string, replace: boolean = false) {\n return this.selfFactory({ url: appendQueryParams(this._url, qp, replace) })\n }\n\n /**\n * Set request headers.\n * @param headerValues An object containing header keys and values\n */\n headers(headerValues: HeadersInit) {\n return this.selfFactory({ options: mix(this._options, { headers: headerValues || {} }) })\n }\n\n /**\n * Shortcut to set the \"Accept\" header.\n * @param headerValue Header value\n */\n accept(headerValue: string) {\n return this.headers({ Accept: headerValue })\n }\n\n /**\n * Shortcut to set the \"Content-Type\" header.\n * @param headerValue Header value\n */\n content(headerValue: string) {\n return this.headers({ [CONTENT_TYPE_HEADER]: headerValue })\n }\n\n /**\n * Shortcut to set the \"Authorization\" header.\n * @param headerValue Header value\n */\n auth(headerValue: string) {\n return this.headers({ Authorization: headerValue })\n }\n\n /**\n * Adds a default catcher which will be called on every subsequent request error when the error code matches.\n * @param errorId Error code or name\n * @param catcher: The catcher method\n */\n catcher(errorId: number | string, catcher: (error: WretcherError, originalRequest: Wretcher) => any) {\n const newMap = new Map(this._catchers)\n newMap.set(errorId, catcher)\n return this.selfFactory({ catchers: newMap })\n }\n\n /**\n * Associates a custom signal with the request.\n * @param controller : An AbortController\n */\n signal(controller: AbortController) {\n return this.selfFactory({ options: { ...this._options, signal: controller.signal } })\n }\n\n /**\n * Program a resolver to perform response chain tasks automatically.\n * @param doResolve : Resolver callback\n */\n resolve(doResolve: (chain: ResponseChain, originalRequest: Wretcher) => ResponseChain | Promise, clear: boolean = false) {\n return this.selfFactory({ resolvers: clear ? [doResolve] : [...this._resolvers, doResolve] })\n }\n\n /**\n * Defer wretcher methods that will be chained and called just before the request is performed.\n */\n defer(callback: DeferredCallback, clear = false) {\n return this.selfFactory({\n deferredChain: clear ? [callback] : [...this._deferredChain, callback]\n })\n }\n\n /**\n * Add middlewares to intercept a request before being sent.\n */\n middlewares(middlewares: ConfiguredMiddleware[], clear = false) {\n return this.selfFactory({\n middlewares: clear ? middlewares : [...this._middlewares, ...middlewares]\n })\n }\n\n private method(method: string, options = {}, body = null) {\n let base = this.options({ ...options, method })\n // \"Jsonify\" the body if it is an object and if it is likely that the content type targets json.\n const contentType = extractContentType(base._options.headers)\n const jsonify = typeof body === \"object\" && (!base._options.headers || !contentType || isLikelyJsonMime(contentType))\n base =\n !body ? base :\n jsonify ? base.json(body, contentType) :\n base.body(body)\n return resolver(\n base\n ._deferredChain\n .reduce((acc: Wretcher, curr) => curr(acc, acc._url, acc._options), base)\n )\n }\n\n /**\n * Performs a get request.\n */\n get(options?: WretcherOptions) {\n return this.method(\"GET\", options)\n }\n /**\n * Performs a delete request.\n */\n delete(options?: WretcherOptions) {\n return this.method(\"DELETE\", options)\n }\n /**\n * Performs a put request.\n */\n put(body?: any, options?: WretcherOptions) {\n return this.method(\"PUT\", options, body)\n }\n /**\n * Performs a post request.\n */\n post(body?: any, options?: WretcherOptions) {\n return this.method(\"POST\", options, body)\n }\n /**\n * Performs a patch request.\n */\n patch(body?: any, options?: WretcherOptions) {\n return this.method(\"PATCH\", options, body)\n }\n /**\n * Performs a head request.\n */\n head(options?: WretcherOptions) {\n return this.method(\"HEAD\", options)\n }\n /**\n * Performs an options request\n */\n opts(options?: WretcherOptions) {\n return this.method(\"OPTIONS\", options)\n }\n /**\n * Replay a request.\n */\n replay(options?: WretcherOptions) {\n return this.method(this._options.method, options)\n }\n\n /**\n * Sets the request body with any content.\n * @param contents The body contents\n */\n body(contents: any) {\n return this.selfFactory({ options: { ...this._options, body: contents } })\n }\n /**\n * Sets the content type header, stringifies an object and sets the request body.\n * @param jsObject An object which will be serialized into a JSON\n * @param contentType A custom content type.\n */\n json(jsObject: object, contentType?: string) {\n const currentContentType = extractContentType(this._options.headers)\n return this.content(\n contentType ||\n isLikelyJsonMime(currentContentType) && currentContentType ||\n JSON_MIME\n ).body(JSON.stringify(jsObject))\n }\n /**\n * Converts the javascript object to a FormData and sets the request body.\n * @param formObject An object which will be converted to a FormData\n * @param recursive If `true`, will recurse through all nested objects\n * Can be set as an array of string to exclude specific keys.\n * See https://github.com/elbywan/wretch/issues/68 for more details.\n */\n formData(formObject: object, recursive: string[] | boolean = false) {\n return this.body(convertFormData(formObject, recursive))\n }\n /**\n * Converts the input to an url encoded string and sets the content-type header and body.\n * If the input argument is already a string, skips the conversion part.\n *\n * @param input An object to convert into an url encoded string or an already encoded string\n */\n formUrl(input: (object | string)) {\n return this\n .body(typeof input === \"string\" ? input : convertFormUrl(input))\n .content(\"application/x-www-form-urlencoded\")\n }\n}\n\n// Internal helpers\n\nconst appendQueryParams = (url: string, qp: object | string, replace: boolean) => {\n let queryString: string\n\n if (typeof qp === \"string\") {\n queryString = qp\n } else {\n const usp = conf.polyfill(\"URLSearchParams\", { instance: true })\n for (const key in qp) {\n if (qp[key] instanceof Array) {\n for (const val of qp[key])\n usp.append(key, val)\n } else {\n usp.append(key, qp[key])\n }\n }\n queryString = usp.toString()\n }\n\n const split = url.split(\"?\")\n\n if (!queryString)\n return replace ? split[0] : url\n\n if (replace || split.length < 2)\n return split[0] + \"?\" + queryString\n\n return url + \"&\" + queryString\n}\n\nfunction convertFormData(\n formObject: object,\n recursive: string[] | boolean = false,\n formData = conf.polyfill(\"FormData\", { instance: true }),\n ancestors = []\n) {\n Object.entries(formObject).forEach(([key, value]) => {\n let formKey = ancestors.reduce((acc, ancestor) => (\n acc ? `${acc}[${ancestor}]` : ancestor\n ), null)\n formKey = formKey ? `${formKey}[${key}]` : key\n if (value instanceof Array) {\n for (const item of value)\n formData.append(formKey + \"[]\", item)\n } else if (\n recursive &&\n typeof value === \"object\" &&\n (\n !(recursive instanceof Array) ||\n !recursive.includes(key)\n )\n ) {\n if (value !== null) {\n convertFormData(value, recursive, formData, [...ancestors, key])\n }\n } else {\n formData.append(formKey, value)\n }\n })\n\n return formData\n}\n\nfunction encodeQueryValue(key: string, value: unknown) {\n return encodeURIComponent(key) +\n \"=\" +\n encodeURIComponent(\n typeof value === \"object\" ?\n JSON.stringify(value) :\n \"\" + value\n )\n}\nfunction convertFormUrl(formObject: object) {\n return Object.keys(formObject)\n .map(key => {\n const value = formObject[key]\n if (value instanceof Array) {\n return value.map(v => encodeQueryValue(key, v)).join(\"&\")\n }\n return encodeQueryValue(key, value)\n })\n .join(\"&\")\n}\n","import { WretcherOptions } from \"./wretcher\"\nimport { WretcherResponse } from \"./resolver\"\n\nexport type Middleware = (options?: {[key: string]: any}) => ConfiguredMiddleware\nexport type ConfiguredMiddleware = (next: FetchLike) => FetchLike\nexport type FetchLike = (url: string, opts: WretcherOptions) => Promise\n\nexport const middlewareHelper = (middlewares: ConfiguredMiddleware[]) => (fetchFunction: FetchLike): FetchLike => {\n return (\n middlewares.length === 0 ?\n fetchFunction :\n middlewares.length === 1 ?\n middlewares[0](fetchFunction) :\n middlewares.reduceRight((acc, curr, idx): any =>\n (idx === middlewares.length - 2) ? curr(acc(fetchFunction)) : curr(acc as any)\n )\n ) as FetchLike\n}\n","import { Wretcher } from \"./wretcher\"\n\nconst factory = Wretcher.factory\nfactory[\"default\"] = Wretcher.factory\n\n/**\n * Return a fresh Wretcher instance.\n */\nexport default factory\n"],"names":["mix","one","two","mergeArrays","clone","prop","hasOwnProperty","Array","config","defaults","errorType","polyfills","fetch","FormData","URLSearchParams","performance","PerformanceObserver","AbortController","polyfill","p","_a","_b","_c","doThrow","_d","instance","_i","args","res","this","self","global","Error","onMatch","entries","name","callback","_performance","getEntriesByName","matches","length","reverse","clearMeasures","perfs","callbacks","delete","size","observer","disconnect","clearResourceTimings","Map","observe","conf","_observer","forEach","lazyObserver","entryTypes","set","error","CONTENT_TYPE_HEADER","extractContentType","headers","Object","find","toLowerCase","isLikelyJsonMime","value","test","_url","_options","_catchers","_resolvers","_middlewares","_deferredChain","Wretcher","url","options","_e","catchers","_f","resolvers","_g","middlewares","_h","deferredChain","mixin","method","replace","selfFactory","split","qp","appendQueryParams","headerValues","headerValue","Accept","Authorization","errorId","catcher","newMap","controller","signal","doResolve","clear","body","base","contentType","jsonify","wretcher","opts","finalOptions","fetchController","timeout","ref","clearTimeout","fetchRequest","fetchFunction","reduceRight","acc","curr","idx","middlewareHelper","throwingPromise","catch","WretchErrorWrapper","then","response","ok","type","err","status","msg","catchersWrapper","promise","has","get","bodyParser","funName","cb","_","responseChain","json","blob","formData","arrayBuffer","text","setTimeout","time","abort","badRequest","unauthorized","forbidden","notFound","internalError","fetchError","onAbort","reduce","chain","r","resolver","contents","jsObject","currentContentType","content","JSON","stringify","formObject","recursive","convertFormData","input","keys","map","key","v","encodeQueryValue","join","queryString","usp","val","append","toString","ancestors","formKey","ancestor","value_1","item","includes","encodeURIComponent","factory"],"mappings":"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"} \ No newline at end of file diff --git a/test/mock.cjs b/test/mock.cjs new file mode 100644 index 0000000..f016415 --- /dev/null +++ b/test/mock.cjs @@ -0,0 +1,199 @@ +const fs = require("fs") +const path = require("path") +const restify = require("restify") +const corsMiddleware = require("restify-cors-middleware2") + +const cors = corsMiddleware({ + origins: ["*"], + allowHeaders: ["Authorization", "X-Custom-Header", "X-Custom-Header-2", "X-Custom-Header-3", "X-Custom-Header-4"], + exposeHeaders: ["Allow", "Timing-Allow-Origin"] +}) + +const preload = { + duck: fs.readFileSync(path.resolve(__dirname, "assets", "duck.jpg")) +} + +const mockServer = { + launch: port => { + const server = restify.createServer() + mockServer["server"] = server + + server.use(restify.plugins.queryParser()) + server.use(restify.plugins.jsonBodyParser()) + server.use(restify.plugins.multipartBodyParser({ + mapFiles: true + })) + server.use(restify.plugins.authorizationParser()) + server.pre(cors.preflight) + server.use(cors.actual) + server.pre(function (req, res, next) { + res.setHeader("Timing-Allow-Origin", '*') + return next() + }) + + setupReplies(server, "text", textReply) + setupReplies(server, "json", jsonReply) + setupReplies(server, "blob", imgReply) + setupReplies(server, "arrayBuffer", binaryReply) + + server.head("/json", (req, res) => { + res.setHeader("content-type", "application/json") + res.end() + }) + + server.get("/json/null", (req, res) => { + res.json(null) + }) + + server.opts("/options", (req, res) => { + res.header("Allow", "OPTIONS") + res.end() + }) + + server.get("/customHeaders", (req, res) => { + const hasCustomHeaders = req.header("X-Custom-Header", false) + && req.header("X-Custom-Header-2", false) + && req.header("X-Custom-Header-3", false) + && req.header("X-Custom-Header-4", false) + res.send(hasCustomHeaders ? 200 : 400) + }) + + setupErrors(server) + + server.post("/text/roundTrip", (req, res) => { + try { + if (req.header("content-type") === "text/plain") + res.sendRaw(req.body) + else + res.send(400) + } catch (error) { + console.error(error) + res.send(400) + } + }) + + server.post("/json/roundTrip", (req, res) => { + try { + if (req.header("content-type") === "application/json") + res.json(req.body) + else + res.send(400) + } catch (error) { + console.error(error) + res.send(400) + } + }) + + server.post("/urlencoded/roundTrip", (req, res) => { + try { + if (req.header("content-type") === "application/x-www-form-urlencoded") + res.sendRaw(req.body) + else + res.send(400) + } catch (error) { + console.error(error) + res.send(400) + } + }) + + server.post("/blob/roundTrip", (req, res) => { + try { + if (req.header("content-type") === "application/xxx-octet-stream") { + res.header("content-type", "application/octet-stream") + res.sendRaw(req.body) + } else { + res.send(400) + } + } catch (error) { + console.error(error) + res.send(400) + } + }) + + server.post("/formData/decode", (req, res) => { + if (req.header("content-type").startsWith("multipart/form-data")) + res.json(req.params) + else + res.send(400) + }) + + server.get("/accept", (req, res) => { + const accept = req.header("Accept") + if (~accept.indexOf("application/json")) + res.json({ json: "ok" }) + else + res.sendRaw("text") + }) + + server.get("/basicauth", (req, res) => { + if (req.authorization && + req.authorization.scheme === "Basic" && + req.authorization.basic.username === "wretch" && + req.authorization.basic.password === "rocks") + res.sendRaw("ok") + else + res.send(401) + }) + + server.get("/json500", (req, res) => { + res.json(500, { error: 500, message: "ok" }) + }) + + server.get("/longResult", (req, res) => { + setTimeout(() => res.sendRaw("ok"), 1000) + }) + + server.get("/*", (req, res) => { + res.json(404, {}) + }) + + server.listen(port) + }, + stop: () => { + mockServer["server"].close() + } +} + +const textReply = (req, res) => { + res.sendRaw("A text string") +} +const jsonReply = (req, res) => { + res.json({ a: "json", "object": "which", "is": "stringified" }) +} +const imgReply = (req, res) => { + res.setHeader("content-type", "image/jpeg") + res.send(preload.duck) +} +const binaryReply = (req, res) => { + res.setHeader("content-type", "application/octet-stream") + const binaryData = Buffer.from([0x00, 0x01, 0x02, 0x03]) + res.send(binaryData) +} + +const setupReplies = (server, type, fun) => { + server.get("/" + type, fun) + server.post("/" + type, fun) + server.put("/" + type, fun) + server.patch("/" + type, fun) + server.del("/" + type, fun) +} + +const setupErrors = server => { + const errorList = [444, 449, 450, 451, 456, 495, 496, 497, 498, 499] + for (let i = 0; i < 512; i++) { + if (!errorList.includes(i)) + errorList.push(i) + if (i === 418) i += 2 + else if (i === 426 || i === 429) i++ + } + + for (let error of errorList) { + server.get("/" + error, (req, res) => { + res.sendRaw(error, "error code : " + error) + }) + } +} + +module.exports = mockServer + +// mockServer.launch(9876) \ No newline at end of file diff --git a/test/mock.js b/test/mock.js deleted file mode 100644 index 3fde59e..0000000 --- a/test/mock.js +++ /dev/null @@ -1,199 +0,0 @@ -const fs = require("fs") -const path = require("path") -const restify = require("restify") -const corsMiddleware = require("restify-cors-middleware2") - -const cors = corsMiddleware({ - origins: ["*"], - allowHeaders: ["Authorization", "X-Custom-Header", "X-Custom-Header-2", "X-Custom-Header-3", "X-Custom-Header-4"], - exposeHeaders: ["Allow", "Timing-Allow-Origin"] -}) - -const preload = { - duck: fs.readFileSync(path.resolve(__dirname, "assets", "duck.jpg")) -} - -const mockServer = { - launch: port => { - const server = restify.createServer() - mockServer["server"] = server - - server.use(restify.plugins.queryParser()) - server.use(restify.plugins.jsonBodyParser()) - server.use(restify.plugins.multipartBodyParser({ - mapFiles: true - })) - server.use(restify.plugins.authorizationParser()) - server.pre(cors.preflight) - server.use(cors.actual) - server.pre(function(req, res, next) { - res.setHeader("Timing-Allow-Origin", '*') - return next() - }) - - setupReplies(server, "text", textReply) - setupReplies(server, "json", jsonReply) - setupReplies(server, "blob", imgReply) - setupReplies(server, "arrayBuffer", binaryReply) - - server.head("/json", (req, res) => { - res.setHeader("content-type", "application/json") - res.end() - }) - - server.get("/json/null", (req,res) => { - res.json(null) - }) - - server.opts("/options", (req, res) => { - res.header("Allow", "OPTIONS") - res.end() - }) - - server.get("/customHeaders", (req, res) => { - const hasCustomHeaders = req.header("X-Custom-Header", false) - && req.header("X-Custom-Header-2", false) - && req.header("X-Custom-Header-3", false) - && req.header("X-Custom-Header-4", false) - res.send(hasCustomHeaders ? 200 : 400) - }) - - setupErrors(server) - - server.post("/text/roundTrip", (req,res) => { - try { - if(req.header("content-type") === "text/plain") - res.sendRaw(req.body) - else - res.send(400) - } catch(error) { - console.error(error) - res.send(400) - } - }) - - server.post("/json/roundTrip", (req, res) => { - try { - if(req.header("content-type") === "application/json") - res.json(req.body) - else - res.send(400) - } catch(error) { - console.error(error) - res.send(400) - } - }) - - server.post("/urlencoded/roundTrip", (req, res) => { - try { - if(req.header("content-type") === "application/x-www-form-urlencoded") - res.sendRaw(req.body) - else - res.send(400) - } catch(error) { - console.error(error) - res.send(400) - } - }) - - server.post("/blob/roundTrip", (req, res) => { - try { - if(req.header("content-type") === "application/xxx-octet-stream") { - res.header("content-type", "application/octet-stream") - res.sendRaw(req.body) - } else { - res.send(400) - } - } catch (error) { - console.error(error) - res.send(400) - } - }) - - server.post("/formData/decode", (req, res) => { - if(req.header("content-type").startsWith("multipart/form-data")) - res.json(req.params) - else - res.send(400) - }) - - server.get("/accept", (req, res) => { - const accept = req.header("Accept") - if(~accept.indexOf("application/json")) - res.json({ json: "ok" }) - else - res.sendRaw("text") - }) - - server.get("/basicauth", (req, res) => { - if(req.authorization && - req.authorization.scheme === "Basic" && - req.authorization.basic.username === "wretch" && - req.authorization.basic.password === "rocks") - res.sendRaw("ok") - else - res.send(401) - }) - - server.get("/json500", (req, res) => { - res.json(500, { error: 500, message: "ok" }) - }) - - server.get("/longResult", (req, res) => { - setTimeout(() => res.sendRaw("ok"), 1000) - }) - - server.get("/*", (req, res) => { - res.json(404, {}) - }) - - server.listen(port) - }, - stop: () => { - mockServer["server"].close() - } -} - -const textReply = (req, res) => { - res.sendRaw("A text string") -} -const jsonReply = (req, res) => { - res.json({ a: "json", "object": "which", "is": "stringified" }) -} -const imgReply = (req, res) => { - res.setHeader("content-type", "image/jpeg") - res.send(preload.duck) -} -const binaryReply = (req, res) => { - res.setHeader("content-type", "application/octet-stream") - const binaryData = Buffer.from([ 0x00, 0x01, 0x02, 0x03 ]) - res.send(binaryData) -} - -const setupReplies = (server, type, fun) => { - server.get( "/" + type, fun) - server.post( "/" + type, fun) - server.put( "/" + type, fun) - server.patch( "/" + type, fun) - server.del( "/" + type, fun) -} - -const setupErrors = server => { - const errorList = [ 444, 449, 450, 451, 456, 495, 496, 497, 498, 499 ] - for(let i = 0; i < 512; i++){ - if(!errorList.includes(i)) - errorList.push(i) - if(i === 418) i += 2 - else if(i === 426 || i === 429) i++ - } - - for(let error of errorList) { - server.get("/" + error, (req, res) => { - res.sendRaw(error, "error code : " + error) - }) - } -} - -module.exports = mockServer - -// mockServer.launch(9876) \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 80735b5..1d5e619 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "es6", "lib": [ - "es2015", + "es2020", "dom" ], "module": "es2015",