Repository: pmcelhaney/counterfact
Pull request #318 (2022-11-22 22:01:44)

proxy
The `$.proxy()` function sends the request (via nodeFetch) to a different server and returns a counterfact style response.

The simples usage:

```ts
return $.proxy("http://example.com"); 
```

We alter the response. 

```ts
const response = $.proxy("http://example.com");
delete response.headers['x-remove-me'];
response.type = "text/plain";
return response;
```

We can also alter the request. 

```ts
const response = $.proxy({host: "http://example.com", query: { ...$.query, extraThing: 1} });
delete response.headers['x-remove-me'];
response.type = "text/plain";
return response;
```

Someone might not want to change the request or response, but intercept the response to update the `$.context` or maybe even just log it.

Or maybe read the response and then decide to return it as is or return some kind of mock. 

## To do

- [x] documentation
- [x] more unit tests


 

changeset-bot[bot]: ###  🦋  Changeset detected

Latest commit: 80e985c2acc281e547b1b6fdf260a0c737931ab0

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name        | Type  |
| ----------- | ----- |
| counterfact | Minor |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/pmcelhaney/counterfact/new/proxy?filename=.changeset/strange-ladybugs-repair.md&value=---%0A%22counterfact%22%3A%20patch%0A---%0A%0Aproxy%0A)



Patch:
diff --git a/.changeset/angry-plums-exist.md b/.changeset/angry-plums-exist.md
new file mode 100644
index 0000000..818685b
--- /dev/null
+++ b/.changeset/angry-plums-exist.md
@@ -0,0 +1,5 @@
+---
+"counterfact": minor
+---
+
+return $.proxy(url) to proxy to another server
diff --git a/package.json b/package.json
index a21cbb1..86ef205 100644
--- a/package.json
+++ b/package.json
@@ -61,6 +61,7 @@
     "@types/json-schema": "^7.0.11",
     "chokidar": "^3.5.3",
     "commander": "^9.4.0",
+    "fetch": "^1.1.0",
     "fs-extra": "^10.1.0",
     "handlebars": "^4.7.7",
     "js-yaml": "^4.1.0",
@@ -69,6 +70,7 @@
     "jsonwebtoken": "^8.5.1",
     "koa": "^2.13.4",
     "koa-bodyparser": "^4.3.0",
+    "koa-proxy": "^1.0.0-alpha.3",
     "koa2-swagger-ui": "^5.6.0",
     "node-fetch": "^3.2.10",
     "open": "^8.4.0",
diff --git a/src/server/dispatcher.js b/src/server/dispatcher.js
index 10cb902..6a48b2c 100644
--- a/src/server/dispatcher.js
+++ b/src/server/dispatcher.js
@@ -1,4 +1,6 @@
 import Accept from "@hapi/accept";
+// eslint-disable-next-line no-shadow
+import fetch, { Headers } from "node-fetch";
 
 import { createResponseBuilder } from "./response-builder.js";
 import { Tools } from "./tools.js";
@@ -30,6 +32,9 @@ function parameterTypes(parameters) {
 export class Dispatcher {
   registry;
 
+  // alias the fetch function so we can mock it in tests
+  fetch = fetch;
+
   constructor(registry, contextRegistry, openApiDocument) {
     this.registry = registry;
     this.contextRegistry = contextRegistry;
@@ -61,6 +66,32 @@ export class Dispatcher {
           method
         )
       ),
+
+      proxy: async (hostOrOptions) => {
+        const options =
+          typeof hostOrOptions === "string"
+            ? { host: hostOrOptions }
+            : hostOrOptions;
+
+        const fetchResponse = await this.fetch(options.host, {
+          method,
+          path,
+          headers: new Headers(headers),
+          query,
+          ...options,
+        });
+
+        const responseHeaders = Object.fromEntries(
+          fetchResponse.headers.entries()
+        );
+
+        return {
+          status: fetchResponse.status,
+          contentType: responseHeaders["content-type"] ?? "unknown/unknown",
+          headers: responseHeaders,
+          body: await fetchResponse.text(),
+        };
+      },
     });
 
     const normalizedResponse = this.normalizeResponse(
diff --git a/src/server/koa-middleware.js b/src/server/koa-middleware.js
index eb5509c..859b906 100644
--- a/src/server/koa-middleware.js
+++ b/src/server/koa-middleware.js
@@ -1,12 +1,19 @@
+import koaProxy from "koa-proxy";
+
 const HTTP_STATUS_CODE_OK = 200;
 
-export function koaMiddleware(dispatcher) {
-  return async function middleware(ctx) {
-    const { method, path, body, query } = ctx.request;
+export function koaMiddleware(dispatcher, options = {}, proxy = koaProxy) {
+  return async function middleware(ctx, next) {
+    const { method, path, headers, body, query } = ctx.request;
+
+    if (options.proxyUrl) {
+      return proxy({ host: options.proxyUrl })(ctx, next);
+    }
 
     const response = await dispatcher.request({
       method,
       path,
+      headers,
       body,
       query,
     });
@@ -15,5 +22,7 @@ export function koaMiddleware(dispatcher) {
     ctx.body = response.body;
     ctx.status = response.status ?? HTTP_STATUS_CODE_OK;
     /* eslint-enable require-atomic-updates */
+
+    return undefined;
   };
 }
diff --git a/src/typescript-generator/operation-type-coder.js b/src/typescript-generator/operation-type-coder.js
index df24cd2..dbbf943 100644
--- a/src/typescript-generator/operation-type-coder.js
+++ b/src/typescript-generator/operation-type-coder.js
@@ -101,9 +101,10 @@ export class OperationTypeCoder extends Coder {
       this.requirement.get("produces")?.data
     ).write(script);
 
-    return `({ query, path, header, body, context }: { query: ${queryType}, path: ${pathType}, header: ${headerType}, body: ${bodyType}, context: typeof ${contextImportName}, response: ${responseType} }) => ${this.responseTypes(
+    const proxyType = "(url: string) => { proxyUrl: string }";
+
+    return `({ query, path, header, body, context, proxy }: { query: ${queryType}, path: ${pathType}, header: ${headerType}, body: ${bodyType}, context: typeof ${contextImportName}, response: ${responseType}, proxy: ${proxyType} }) => ${this.responseTypes(
       script
-    )} | { status: 415, contentType: "text/plain", body: string }
-    | void`;
+    )} | { status: 415, contentType: "text/plain", body: string } | void`;
   }
 }
diff --git a/yarn.lock b/yarn.lock
index 0e4a96a..b5bda2d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2664,7 +2664,7 @@ acorn@^8.8.0:
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
   integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
 
-ajv@^6.10.0, ajv@^6.12.4:
+ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4:
   version "6.12.6"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
   integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@@ -2856,6 +2856,18 @@ asap@^2.0.0:
   resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
   integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==
 
+asn1@~0.2.3:
+  version "0.2.6"
+  resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d"
+  integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==
+  dependencies:
+    safer-buffer "~2.1.0"
+
+assert-plus@1.0.0, assert-plus@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
+  integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==
+
 ast-types-flow@^0.0.7:
   version "0.0.7"
   resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
@@ -2883,6 +2895,16 @@ asynckit@^0.4.0:
   resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
   integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
 
+aws-sign2@~0.7.0:
+  version "0.7.0"
+  resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
+  integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==
+
+aws4@^1.8.0:
+  version "1.11.0"
+  resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
+  integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
+
 axe-core@^4.4.3:
   version "4.4.3"
   resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.3.tgz#11c74d23d5013c0fa5d183796729bc3482bd2f6f"
@@ -2973,6 +2995,13 @@ base64-js@^1.3.1:
   resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
   integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
 
+bcrypt-pbkdf@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
+  integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==
+  dependencies:
+    tweetnacl "^0.14.3"
+
 better-path-resolve@1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/better-path-resolve/-/better-path-resolve-1.0.0.tgz#13a35a1104cdd48a7b74bf8758f96a1ee613f99d"
@@ -2985,6 +3014,13 @@ binary-extensions@^2.0.0:
   resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
   integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
 
+biskviit@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/biskviit/-/biskviit-1.0.1.tgz#037a0cd4b71b9e331fd90a1122de17dc49e420a7"
+  integrity sha512-VGCXdHbdbpEkFgtjkeoBN8vRlbj1ZRX2/mxhE8asCCRalUx2nBzOomLJv8Aw/nRt5+ccDb+tPKidg4XxcfGW4w==
+  dependencies:
+    psl "^1.1.7"
+
 bl@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
@@ -3167,6 +3203,11 @@ capital-case@^1.0.4:
     tslib "^2.0.3"
     upper-case-first "^2.0.2"
 
+caseless@~0.12.0:
+  version "0.12.0"
+  resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
+  integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==
+
 chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.2:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
@@ -3391,7 +3432,7 @@ colord@^2.9.2:
   resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.2.tgz#25e2bacbbaa65991422c07ea209e2089428effb1"
   integrity sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==
 
-combined-stream@^1.0.8:
+combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
   version "1.0.8"
   resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
   integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
@@ -3484,6 +3525,11 @@ core-js-pure@^3.20.2:
   resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.24.1.tgz#8839dde5da545521bf282feb7dc6d0b425f39fd3"
   integrity sha512-r1nJk41QLLPyozHUUPmILCEMtMw24NG4oWK6RbsDdjzQgg9ZvrUsPBj1MnG0wXXp1DCDU6j+wUvEmBSrtRbLXg==
 
+core-util-is@1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+  integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==
+
 cosmiconfig@^7.0.0, cosmiconfig@^7.0.1:
   version "7.0.1"
   resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d"
@@ -3570,6 +3616,13 @@ damerau-levenshtein@^1.0.8:
   resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
   integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==
 
+dashdash@^1.12.0:
+  version "1.14.1"
+  resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
+  integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==
+  dependencies:
+    assert-plus "^1.0.0"
+
 data-uri-to-buffer@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b"
@@ -3780,6 +3833,14 @@ eastasianwidth@^0.2.0:
   resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
   integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
 
+ecc-jsbn@~0.1.1:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
+  integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==
+  dependencies:
+    jsbn "~0.1.0"
+    safer-buffer "^2.1.0"
+
 ecdsa-sig-formatter@1.0.11:
   version "1.0.11"
   resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
@@ -3827,6 +3888,13 @@ encodeurl@^1.0.2:
   resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
   integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
 
+encoding@0.1.12:
+  version "0.1.12"
+  resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
+  integrity sha512-bl1LAgiQc4ZWr++pNYUdRe/alecaHFeHxIJ/pNciqGdKXghaTCOwKkbKp6ye7pKZGu/GcaSXFk8PBVhgs+dJdA==
+  dependencies:
+    iconv-lite "~0.4.13"
+
 enhanced-resolve@^5.10.0:
   version "5.10.0"
   resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz#0dc579c3bb2a1032e357ac45b8f3a6f3ad4fb1e6"
@@ -4648,7 +4716,7 @@ expect@^28.1.3:
     jest-message-util "^28.1.3"
     jest-util "^28.1.3"
 
-extend@^3.0.0:
+extend@^3.0.0, extend@~3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
   integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
@@ -4667,6 +4735,16 @@ external-editor@^3.0.3, external-editor@^3.1.0:
     iconv-lite "^0.4.24"
     tmp "^0.0.33"
 
+extsprintf@1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
+  integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==
+
+extsprintf@^1.2.0:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07"
+  integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==
+
 fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@@ -4741,6 +4819,14 @@ fetch-blob@^3.1.2, fetch-blob@^3.1.4:
     node-domexception "^1.0.0"
     web-streams-polyfill "^3.0.3"
 
+fetch@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/fetch/-/fetch-1.1.0.tgz#0a8279f06be37f9f0ebb567560a30a480da59a2e"
+  integrity sha512-5O8TwrGzoNblBG/jtK4NFuZwNCkZX6s5GfRNOaGtm+QGJEuNakSC/i2RW0R93KX6E0jVjNXm6O3CRN4Ql3K+yA==
+  dependencies:
+    biskviit "1.0.1"
+    encoding "0.1.12"
+
 figures@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
@@ -4841,6 +4927,11 @@ flatted@^3.2.7:
   resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
   integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
 
+forever-agent@~0.6.1:
+  version "0.6.1"
+  resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
+  integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==
+
 form-data@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
@@ -4850,6 +4941,15 @@ form-data@^4.0.0:
     combined-stream "^1.0.8"
     mime-types "^2.1.12"
 
+form-data@~2.3.2:
+  version "2.3.3"
+  resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
+  integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==
+  dependencies:
+    asynckit "^0.4.0"
+    combined-stream "^1.0.6"
+    mime-types "^2.1.12"
+
 format-io@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/format-io/-/format-io-2.0.0.tgz#8da8a37ccc6c5e0502cbf1d68be7734070105bf2"
@@ -5008,6 +5108,13 @@ get-tsconfig@^4.2.0:
   resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.2.0.tgz#ff368dd7104dab47bf923404eb93838245c66543"
   integrity sha512-X8u8fREiYOE6S8hLbq99PeykTDoLVnxvF4DjWKJmz9xy2nNRdUcV8ZN9tniJFeKyTU3qnC9lL8n4Chd6LmVKHg==
 
+getpass@^0.1.1:
+  version "0.1.7"
+  resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
+  integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==
+  dependencies:
+    assert-plus "^1.0.0"
+
 glob-parent@^5.1.2, glob-parent@~5.1.2:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@@ -5145,6 +5252,19 @@ handlebars@^4.7.7:
   optionalDependencies:
     uglify-js "^3.1.4"
 
+har-schema@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
+  integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==
+
+har-validator@~5.1.3:
+  version "5.1.5"
+  resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd"
+  integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==
+  dependencies:
+    ajv "^6.12.3"
+    har-schema "^2.0.0"
+
 hard-rejection@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883"
@@ -5256,6 +5376,15 @@ http-errors@^1.6.3, http-errors@~1.8.0:
     statuses ">= 1.5.0 < 2"
     toidentifier "1.0.1"
 
+http-signature@~1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
+  integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==
+  dependencies:
+    assert-plus "^1.0.0"
+    jsprim "^1.2.2"
+    sshpk "^1.7.0"
+
 human-id@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/human-id/-/human-id-1.0.2.tgz#e654d4b2b0d8b07e45da9f6020d8af17ec0a5df3"
@@ -5276,7 +5405,7 @@ husky@8.0.2:
   resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.2.tgz#5816a60db02650f1f22c8b69b928fd6bcd77a236"
   integrity sha512-Tkv80jtvbnkK3mYWxPZePGFpQ/tT3HNSs/sasF9P2YfkMezDl3ON37YN6jUUI4eTg5LcyVynlb6r4eyvOmspvg==
 
-iconv-lite@0.4.24, iconv-lite@^0.4.24:
+iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13:
   version "0.4.24"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
   integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -5660,6 +5789,11 @@ is-symbol@^1.0.2, is-symbol@^1.0.3:
   dependencies:
     has-symbols "^1.0.2"
 
+is-typedarray@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
+  integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==
+
 is-unc-path@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d"
@@ -5718,6 +5852,11 @@ isomorphic-git@^1.4.0:
     sha.js "^2.4.9"
     simple-get "^4.0.1"
 
+isstream@~0.1.2:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
+  integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==
+
 istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3"
@@ -6153,6 +6292,11 @@ js-yaml@^4.1.0:
   dependencies:
     argparse "^2.0.1"
 
+jsbn@~0.1.0:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
+  integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==
+
 jsdoctypeparser@^9.0.0:
   version "9.0.0"
   resolved "https://registry.yarnpkg.com/jsdoctypeparser/-/jsdoctypeparser-9.0.0.tgz#8c97e2fb69315eb274b0f01377eaa5c940bd7b26"
@@ -6202,11 +6346,21 @@ json-schema-traverse@^1.0.0:
   resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
   integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
 
+json-schema@0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5"
+  integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==
+
 json-stable-stringify-without-jsonify@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
   integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
 
+json-stringify-safe@~5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
+  integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==
+
 json5@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
@@ -6261,6 +6415,16 @@ jsonwebtoken@^8.5.1:
     ms "^2.1.1"
     semver "^5.6.0"
 
+jsprim@^1.2.2:
+  version "1.4.2"
+  resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb"
+  integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==
+  dependencies:
+    assert-plus "1.0.0"
+    extsprintf "1.3.0"
+    json-schema "0.4.0"
+    verror "1.10.0"
+
 "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.2.1, jsx-ast-utils@^3.3.0, jsx-ast-utils@^3.3.2:
   version "3.3.2"
   resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.2.tgz#afe5efe4332cd3515c065072bd4d6b0aa22152bd"
@@ -6354,6 +6518,15 @@ koa-convert@^2.0.0:
     co "^4.6.0"
     koa-compose "^4.1.0"
 
+koa-proxy@^1.0.0-alpha.3:
+  version "1.0.0-alpha.3"
+  resolved "https://registry.yarnpkg.com/koa-proxy/-/koa-proxy-1.0.0-alpha.3.tgz#afc61edc9dc6a195464664beccc162cfe994bf55"
+  integrity sha512-8ke0WoQKAsQ8BpkC9+I83lKsFaycE9fcLeTx12jQtEa5SrdTI6mNKR5M4LslHbvkX1hDhXreIszXsnom5Ej7RQ==
+  dependencies:
+    pause-stream "0.0.11"
+    request "^2.88.0"
+    request-promise-native "^1.0.5"
+
 koa2-swagger-ui@^5.6.0:
   version "5.6.0"
   resolved "https://registry.yarnpkg.com/koa2-swagger-ui/-/koa2-swagger-ui-5.6.0.tgz#2e4835504c482db8d565775ed85f9cb962188fa3"
@@ -6532,7 +6705,7 @@ lodash.zip@^4.2.0:
   resolved "https://registry.yarnpkg.com/lodash.zip/-/lodash.zip-4.2.0.tgz#ec6662e4896408ed4ab6c542a3990b72cc080020"
   integrity sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg==
 
-lodash@^4.13.1, lodash@^4.17.12, lodash@^4.17.15, lodash@^4.17.21:
+lodash@^4.13.1, lodash@^4.17.12, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21:
   version "4.17.21"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -6979,7 +7152,7 @@ mime-db@1.52.0:
   resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
   integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
 
-mime-types@^2.1.12, mime-types@^2.1.18, mime-types@~2.1.24, mime-types@~2.1.34:
+mime-types@^2.1.12, mime-types@^2.1.18, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34:
   version "2.1.35"
   resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
   integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
@@ -7244,6 +7417,11 @@ nth-check@^2.0.1:
   dependencies:
     boolbase "^1.0.0"
 
+oauth-sign@~0.9.0:
+  version "0.9.0"
+  resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
+  integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
+
 obj-props@^1.0.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/obj-props/-/obj-props-1.4.0.tgz#9a9beebb6faf8b287ff7dc1cd133a4247dc85641"
@@ -7596,6 +7774,18 @@ path-type@^4.0.0:
   resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
   integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
 
+pause-stream@0.0.11:
+  version "0.0.11"
+  resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
+  integrity sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==
+  dependencies:
+    through "~2.3"
+
+performance-now@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
+  integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==
+
 picocolors@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
@@ -7750,12 +7940,17 @@ pseudomap@^1.0.2:
   resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
   integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==
 
+psl@^1.1.28, psl@^1.1.7:
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
+  integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==
+
 pstree.remy@^1.1.8:
   version "1.1.8"
   resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"
   integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==
 
-punycode@^2.1.0:
+punycode@^2.1.0, punycode@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
   integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
@@ -7923,6 +8118,11 @@ qs@^6.11.0, qs@^6.5.2, qs@^6.9.1:
   dependencies:
     side-channel "^1.0.4"
 
+qs@~6.5.2:
+  version "6.5.3"
+  resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
+  integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==
+
 queue-microtask@^1.2.2:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
@@ -8309,6 +8509,48 @@ req-all@^1.0.0:
   resolved "https://registry.yarnpkg.com/req-all/-/req-all-1.0.0.tgz#d128569451c340b432409c656cf166260cd2628d"
   integrity sha512-k8RAnaq+vrezRAZWQlI6sbwGIlII699fnPYamb8K0UVgQel3Wb6YgPfpGyair00vmArXNag1vr5PjwDQBE7DUA==
 
+request-promise-core@1.1.4:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f"
+  integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==
+  dependencies:
+    lodash "^4.17.19"
+
+request-promise-native@^1.0.5:
+  version "1.0.9"
+  resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28"
+  integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==
+  dependencies:
+    request-promise-core "1.1.4"
+    stealthy-require "^1.1.1"
+    tough-cookie "^2.3.3"
+
+request@^2.88.0:
+  version "2.88.2"
+  resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
+  integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
+  dependencies:
+    aws-sign2 "~0.7.0"
+    aws4 "^1.8.0"
+    caseless "~0.12.0"
+    combined-stream "~1.0.6"
+    extend "~3.0.2"
+    forever-agent "~0.6.1"
+    form-data "~2.3.2"
+    har-validator "~5.1.3"
+    http-signature "~1.2.0"
+    is-typedarray "~1.0.0"
+    isstream "~0.1.2"
+    json-stringify-safe "~5.0.1"
+    mime-types "~2.1.19"
+    oauth-sign "~0.9.0"
+    performance-now "^2.1.0"
+    qs "~6.5.2"
+    safe-buffer "^5.1.2"
+    tough-cookie "~2.5.0"
+    tunnel-agent "^0.6.0"
+    uuid "^3.3.2"
+
 require-directory@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@@ -8448,7 +8690,7 @@ sade@^1.7.3:
   dependencies:
     mri "^1.1.0"
 
-safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@~5.2.0:
+safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
   version "5.2.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
   integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@@ -8465,7 +8707,7 @@ safe-regex@^2.1.1:
   dependencies:
     regexp-tree "~0.1.1"
 
-"safer-buffer@>= 2.1.2 < 3":
+"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
   integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@@ -8700,6 +8942,21 @@ sprintf-js@~1.0.2:
   resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
   integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
 
+sshpk@^1.7.0:
+  version "1.17.0"
+  resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5"
+  integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==
+  dependencies:
+    asn1 "~0.2.3"
+    assert-plus "^1.0.0"
+    bcrypt-pbkdf "^1.0.0"
+    dashdash "^1.12.0"
+    ecc-jsbn "~0.1.1"
+    getpass "^0.1.1"
+    jsbn "~0.1.0"
+    safer-buffer "^2.0.2"
+    tweetnacl "~0.14.0"
+
 stack-utils@^2.0.3:
   version "2.0.5"
   resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5"
@@ -8717,6 +8974,11 @@ statuses@2.0.1:
   resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
   integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
 
+stealthy-require@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
+  integrity sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==
+
 stream-transform@^2.1.3:
   version "2.1.3"
   resolved "https://registry.yarnpkg.com/stream-transform/-/stream-transform-2.1.3.tgz#a1c3ecd72ddbf500aa8d342b0b9df38f5aa598e3"
@@ -9059,7 +9321,7 @@ text-table@^0.2.0:
   resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
   integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
 
-through@^2.3.6:
+through@^2.3.6, through@~2.3:
   version "2.3.8"
   resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
   integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==
@@ -9108,6 +9370,14 @@ touch@^3.1.0:
   dependencies:
     nopt "~1.0.10"
 
+tough-cookie@^2.3.3, tough-cookie@~2.5.0:
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
+  integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
+  dependencies:
+    psl "^1.1.28"
+    punycode "^2.1.1"
+
 tree-kill@~1.2.2:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
@@ -9191,11 +9461,23 @@ tty-table@^4.1.5:
     wcwidth "^1.0.1"
     yargs "^17.1.1"
 
+tunnel-agent@^0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
+  integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==
+  dependencies:
+    safe-buffer "^5.0.1"
+
 tunnel@0.0.6:
   version "0.0.6"
   resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
   integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
 
+tweetnacl@^0.14.3, tweetnacl@~0.14.0:
+  version "0.14.5"
+  resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
+  integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==
+
 type-check@^0.4.0, type-check@~0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
@@ -9447,6 +9729,11 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2:
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
   integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
 
+uuid@^3.3.2:
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
+  integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
+
 uvu@^0.5.0:
   version "0.5.6"
   resolved "https://registry.yarnpkg.com/uvu/-/uvu-0.5.6.tgz#2754ca20bcb0bb59b64e9985e84d2e81058502df"
@@ -9484,6 +9771,15 @@ vary@^1.1.2:
   resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
   integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
 
+verror@1.10.0:
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
+  integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==
+  dependencies:
+    assert-plus "^1.0.0"
+    core-util-is "1.0.2"
+    extsprintf "^1.2.0"
+
 vfile-location@^4.0.0:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-4.0.1.tgz#06f2b9244a3565bef91f099359486a08b10d3a95"


Test patch:
diff --git a/test/server/dispatcher.proxy.test.js b/test/server/dispatcher.proxy.test.js
new file mode 100644
index 0000000..88a0e9c
--- /dev/null
+++ b/test/server/dispatcher.proxy.test.js
@@ -0,0 +1,40 @@
+import { Headers } from "node-fetch";
+
+import { Dispatcher } from "../../src/server/dispatcher.js";
+import { ContextRegistry } from "../../src/server/context-registry.js";
+import { Registry } from "../../src/server/registry.js";
+
+describe("a dispatcher passes a proxy function to the operation", () => {
+  it("passes a proxy function", async () => {
+    const registry = new Registry();
+
+    registry.add("/a", {
+      GET({ proxy }) {
+        return proxy("https://example.com");
+      },
+    });
+
+    const dispatcher = new Dispatcher(registry, new ContextRegistry());
+
+    dispatcher.fetch = (url, options) => ({
+      status: 200,
+      headers: new Headers([["content-type", "application/test"]]),
+
+      text() {
+        return `body from ${url}${options.path}`;
+      },
+    });
+
+    const response = await dispatcher.request({
+      method: "GET",
+      path: "/a",
+    });
+
+    expect(response.body).toBe("body from https://example.com/a");
+    expect(response.headers).toStrictEqual({
+      "content-type": "application/test",
+    });
+    expect(response.contentType).toBe("application/test");
+    expect(response.status).toBe(200);
+  });
+});
diff --git a/test/server/koa-middleware.test.js b/test/server/koa-middleware.test.js
index 177027c..ab5b20e 100644
--- a/test/server/koa-middleware.test.js
+++ b/test/server/koa-middleware.test.js
@@ -3,6 +3,12 @@ import { Dispatcher } from "../../src/server/dispatcher.js";
 import { koaMiddleware } from "../../src/server/koa-middleware.js";
 import { ContextRegistry } from "../../src/server/context-registry.js";
 
+function mockKoaProxy(options) {
+  return function proxy(ctx) {
+    ctx.mockProxyHost = options.host;
+  };
+}
+
 describe("koa middleware", () => {
   it("passes the request to the dispatcher and returns the response", async () => {
     const registry = new Registry();
@@ -48,4 +54,28 @@ describe("koa middleware", () => {
 
     expect(ctx.status).toBe(304);
   });
+
+  it("proxies when the response says to use a proxy", async () => {
+    const registry = new Registry();
+
+    registry.add("/proxy", {
+      GET() {
+        throw new Error("should not be called because the proxy is used");
+      },
+    });
+
+    const dispatcher = new Dispatcher(registry, new ContextRegistry());
+    const middleware = koaMiddleware(
+      dispatcher,
+      { proxyUrl: "https://example.com" },
+      mockKoaProxy
+    );
+    const ctx = {
+      request: { path: "/proxy", method: "GET" },
+    };
+
+    await middleware(ctx);
+
+    expect(ctx.mockProxyHost).toBe("https://example.com");
+  });
 });
diff --git a/test/typescript-generator/__snapshots__/end-to-end.test.js.snap b/test/typescript-generator/__snapshots__/end-to-end.test.js.snap
index 14aec68..5511229 100644
--- a/test/typescript-generator/__snapshots__/end-to-end.test.js.snap
+++ b/test/typescript-generator/__snapshots__/end-to-end.test.js.snap
@@ -51,7 +51,7 @@ Map {
           },
           "exports": Map {
             "HTTP_POST" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: never, header: never, body: Pet, context: typeof Context, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: never, header: never, body: Pet, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -67,7 +67,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: Pet
@@ -77,8 +77,7 @@ Map {
               body?: Pet
             } | {  
           status: 405 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet/post",
               "isDefault": false,
@@ -88,7 +87,7 @@ Map {
               "typeDeclaration": "",
             },
             "HTTP_PUT" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: never, header: never, body: Pet, context: typeof Context2, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: never, header: never, body: Pet, context: typeof Context2, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -112,7 +111,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: Pet
@@ -126,8 +125,7 @@ Map {
           status: 404 
         } | {  
           status: 405 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet/put",
               "isDefault": false,
@@ -324,7 +322,7 @@ Map {
           },
           "exports": Map {
             "HTTP_POST" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: never, header: never, body: Pet, context: typeof Context, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: never, header: never, body: Pet, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -340,7 +338,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: Pet
@@ -350,8 +348,7 @@ Map {
               body?: Pet
             } | {  
           status: 405 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet/post",
               "isDefault": false,
@@ -361,7 +358,7 @@ Map {
               "typeDeclaration": "",
             },
             "HTTP_PUT" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: never, header: never, body: Pet, context: typeof Context2, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: never, header: never, body: Pet, context: typeof Context2, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -385,7 +382,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: Pet
@@ -399,8 +396,7 @@ Map {
           status: 404 
         } | {  
           status: 405 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet/put",
               "isDefault": false,
@@ -601,7 +597,7 @@ Map {
     },
     "exports": Map {
       "HTTP_POST" => Object {
-        "code": "({ query, path, header, body, context }: { query: never, path: never, header: never, body: Pet, context: typeof Context, response: ResponseBuilderFactory<{
+        "code": "({ query, path, header, body, context, proxy }: { query: never, path: never, header: never, body: Pet, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -617,7 +613,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: Pet
@@ -627,8 +623,7 @@ Map {
               body?: Pet
             } | {  
           status: 405 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
         "done": true,
         "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet/post",
         "isDefault": false,
@@ -638,7 +633,7 @@ Map {
         "typeDeclaration": "",
       },
       "HTTP_PUT" => Object {
-        "code": "({ query, path, header, body, context }: { query: never, path: never, header: never, body: Pet, context: typeof Context2, response: ResponseBuilderFactory<{
+        "code": "({ query, path, header, body, context, proxy }: { query: never, path: never, header: never, body: Pet, context: typeof Context2, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -662,7 +657,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: Pet
@@ -676,8 +671,7 @@ Map {
           status: 404 
         } | {  
           status: 405 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
         "done": true,
         "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet/put",
         "isDefault": false,
@@ -892,7 +886,7 @@ Map {
           },
           "exports": Map {
             "HTTP_GET" => Object {
-              "code": "({ query, path, header, body, context }: { query: {status?: string}, path: never, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: {status?: string}, path: never, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -908,7 +902,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: Array<Pet>
@@ -918,8 +912,7 @@ Map {
               body?: Array<Pet>
             } | {  
           status: 400 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet~1findByStatus/get",
               "isDefault": false,
@@ -1090,7 +1083,7 @@ Map {
     },
     "exports": Map {
       "HTTP_GET" => Object {
-        "code": "({ query, path, header, body, context }: { query: {status?: string}, path: never, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+        "code": "({ query, path, header, body, context, proxy }: { query: {status?: string}, path: never, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -1106,7 +1099,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: Array<Pet>
@@ -1116,8 +1109,7 @@ Map {
               body?: Array<Pet>
             } | {  
           status: 400 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
         "done": true,
         "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet~1findByStatus/get",
         "isDefault": false,
@@ -1304,7 +1296,7 @@ Map {
           },
           "exports": Map {
             "HTTP_GET" => Object {
-              "code": "({ query, path, header, body, context }: { query: {tags?: Array<string>}, path: never, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: {tags?: Array<string>}, path: never, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -1320,7 +1312,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: Array<Pet>
@@ -1330,8 +1322,7 @@ Map {
               body?: Array<Pet>
             } | {  
           status: 400 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet~1findByTags/get",
               "isDefault": false,
@@ -1502,7 +1493,7 @@ Map {
     },
     "exports": Map {
       "HTTP_GET" => Object {
-        "code": "({ query, path, header, body, context }: { query: {tags?: Array<string>}, path: never, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+        "code": "({ query, path, header, body, context, proxy }: { query: {tags?: Array<string>}, path: never, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -1518,7 +1509,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: Array<Pet>
@@ -1528,8 +1519,7 @@ Map {
               body?: Array<Pet>
             } | {  
           status: 400 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
         "done": true,
         "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet~1findByTags/get",
         "isDefault": false,
@@ -1744,7 +1734,7 @@ Map {
           },
           "exports": Map {
             "HTTP_GET" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: {petId: number}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: {petId: number}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -1764,7 +1754,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: Pet
@@ -1776,8 +1766,7 @@ Map {
           status: 400 
         } | {  
           status: 404 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet~1{petId}/get",
               "isDefault": false,
@@ -1787,15 +1776,14 @@ Map {
               "typeDeclaration": "",
             },
             "HTTP_POST" => Object {
-              "code": "({ query, path, header, body, context }: { query: {name?: number, status?: string}, path: {petId: number}, header: never, body: undefined, context: typeof Context2, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: {name?: number, status?: string}, path: {petId: number}, header: never, body: undefined, context: typeof Context2, response: ResponseBuilderFactory<{
 405: {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: 405 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet~1{petId}/post",
               "isDefault": false,
@@ -1805,15 +1793,14 @@ Map {
               "typeDeclaration": "",
             },
             "HTTP_DELETE" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: {petId: string}, header: {api_key?: string}, body: undefined, context: typeof Context3, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: {petId: string}, header: {api_key?: string}, body: undefined, context: typeof Context3, response: ResponseBuilderFactory<{
 400: {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: 400 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet~1{petId}/delete",
               "isDefault": false,
@@ -2048,7 +2035,7 @@ Map {
           },
           "exports": Map {
             "HTTP_GET" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: {petId: number}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: {petId: number}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -2068,7 +2055,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: Pet
@@ -2080,8 +2067,7 @@ Map {
           status: 400 
         } | {  
           status: 404 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet~1{petId}/get",
               "isDefault": false,
@@ -2091,15 +2077,14 @@ Map {
               "typeDeclaration": "",
             },
             "HTTP_POST" => Object {
-              "code": "({ query, path, header, body, context }: { query: {name?: number, status?: string}, path: {petId: number}, header: never, body: undefined, context: typeof Context2, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: {name?: number, status?: string}, path: {petId: number}, header: never, body: undefined, context: typeof Context2, response: ResponseBuilderFactory<{
 405: {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: 405 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet~1{petId}/post",
               "isDefault": false,
@@ -2109,15 +2094,14 @@ Map {
               "typeDeclaration": "",
             },
             "HTTP_DELETE" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: {petId: string}, header: {api_key?: string}, body: undefined, context: typeof Context3, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: {petId: string}, header: {api_key?: string}, body: undefined, context: typeof Context3, response: ResponseBuilderFactory<{
 400: {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: 400 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet~1{petId}/delete",
               "isDefault": false,
@@ -2352,7 +2336,7 @@ Map {
           },
           "exports": Map {
             "HTTP_GET" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: {petId: number}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: {petId: number}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -2372,7 +2356,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: Pet
@@ -2384,8 +2368,7 @@ Map {
           status: 400 
         } | {  
           status: 404 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet~1{petId}/get",
               "isDefault": false,
@@ -2395,15 +2378,14 @@ Map {
               "typeDeclaration": "",
             },
             "HTTP_POST" => Object {
-              "code": "({ query, path, header, body, context }: { query: {name?: number, status?: string}, path: {petId: number}, header: never, body: undefined, context: typeof Context2, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: {name?: number, status?: string}, path: {petId: number}, header: never, body: undefined, context: typeof Context2, response: ResponseBuilderFactory<{
 405: {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: 405 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet~1{petId}/post",
               "isDefault": false,
@@ -2413,15 +2395,14 @@ Map {
               "typeDeclaration": "",
             },
             "HTTP_DELETE" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: {petId: string}, header: {api_key?: string}, body: undefined, context: typeof Context3, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: {petId: string}, header: {api_key?: string}, body: undefined, context: typeof Context3, response: ResponseBuilderFactory<{
 400: {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: 400 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet~1{petId}/delete",
               "isDefault": false,
@@ -2660,7 +2641,7 @@ Map {
     },
     "exports": Map {
       "HTTP_GET" => Object {
-        "code": "({ query, path, header, body, context }: { query: never, path: {petId: number}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+        "code": "({ query, path, header, body, context, proxy }: { query: never, path: {petId: number}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -2680,7 +2661,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: Pet
@@ -2692,8 +2673,7 @@ Map {
           status: 400 
         } | {  
           status: 404 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
         "done": true,
         "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet~1{petId}/get",
         "isDefault": false,
@@ -2703,15 +2683,14 @@ Map {
         "typeDeclaration": "",
       },
       "HTTP_POST" => Object {
-        "code": "({ query, path, header, body, context }: { query: {name?: number, status?: string}, path: {petId: number}, header: never, body: undefined, context: typeof Context2, response: ResponseBuilderFactory<{
+        "code": "({ query, path, header, body, context, proxy }: { query: {name?: number, status?: string}, path: {petId: number}, header: never, body: undefined, context: typeof Context2, response: ResponseBuilderFactory<{
 405: {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: 405 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
         "done": true,
         "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet~1{petId}/post",
         "isDefault": false,
@@ -2721,15 +2700,14 @@ Map {
         "typeDeclaration": "",
       },
       "HTTP_DELETE" => Object {
-        "code": "({ query, path, header, body, context }: { query: never, path: {petId: string}, header: {api_key?: string}, body: undefined, context: typeof Context3, response: ResponseBuilderFactory<{
+        "code": "({ query, path, header, body, context, proxy }: { query: never, path: {petId: string}, header: {api_key?: string}, body: undefined, context: typeof Context3, response: ResponseBuilderFactory<{
 400: {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: 400 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
         "done": true,
         "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet~1{petId}/delete",
         "isDefault": false,
@@ -2980,7 +2958,7 @@ Map {
           },
           "exports": Map {
             "HTTP_POST" => Object {
-              "code": "({ query, path, header, body, context }: { query: {additionalMetadata?: number}, path: {petId: number}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: {additionalMetadata?: number}, path: {petId: number}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -2989,12 +2967,11 @@ Map {
          }
 };
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/json\\",
               body?: ApiResponse
-            } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+            } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet~1{petId}~1uploadImage/post",
               "isDefault": false,
@@ -3102,7 +3079,7 @@ Map {
     },
     "exports": Map {
       "HTTP_POST" => Object {
-        "code": "({ query, path, header, body, context }: { query: {additionalMetadata?: number}, path: {petId: number}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+        "code": "({ query, path, header, body, context, proxy }: { query: {additionalMetadata?: number}, path: {petId: number}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -3111,12 +3088,11 @@ Map {
          }
 };
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/json\\",
               body?: ApiResponse
-            } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+            } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
         "done": true,
         "id": "OperationTypeCoder@petstore.yaml#/paths/~1pet~1{petId}~1uploadImage/post",
         "isDefault": false,
@@ -3239,7 +3215,7 @@ Map {
           },
           "exports": Map {
             "HTTP_GET" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: never, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: never, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -3248,12 +3224,11 @@ Map {
          }
 };
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/json\\",
               body?: {[key: string]: number}
-            } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+            } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1store~1inventory/get",
               "isDefault": false,
@@ -3330,7 +3305,7 @@ Map {
     },
     "exports": Map {
       "HTTP_GET" => Object {
-        "code": "({ query, path, header, body, context }: { query: never, path: never, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+        "code": "({ query, path, header, body, context, proxy }: { query: never, path: never, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -3339,12 +3314,11 @@ Map {
          }
 };
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/json\\",
               body?: {[key: string]: number}
-            } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+            } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
         "done": true,
         "id": "OperationTypeCoder@petstore.yaml#/paths/~1store~1inventory/get",
         "isDefault": false,
@@ -3438,7 +3412,7 @@ Map {
           },
           "exports": Map {
             "HTTP_POST" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: never, header: never, body: Order, context: typeof Context, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: never, header: never, body: Order, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -3451,14 +3425,13 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/json\\",
               body?: Order
             } | {  
           status: 405 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1store~1order/post",
               "isDefault": false,
@@ -3566,7 +3539,7 @@ Map {
     },
     "exports": Map {
       "HTTP_POST" => Object {
-        "code": "({ query, path, header, body, context }: { query: never, path: never, header: never, body: Order, context: typeof Context, response: ResponseBuilderFactory<{
+        "code": "({ query, path, header, body, context, proxy }: { query: never, path: never, header: never, body: Order, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -3579,14 +3552,13 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/json\\",
               body?: Order
             } | {  
           status: 405 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
         "done": true,
         "id": "OperationTypeCoder@petstore.yaml#/paths/~1store~1order/post",
         "isDefault": false,
@@ -3724,7 +3696,7 @@ Map {
           },
           "exports": Map {
             "HTTP_GET" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: {orderId: number}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: {orderId: number}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -3744,7 +3716,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: Order
@@ -3756,8 +3728,7 @@ Map {
           status: 400 
         } | {  
           status: 404 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1store~1order~1{orderId}/get",
               "isDefault": false,
@@ -3767,7 +3738,7 @@ Map {
               "typeDeclaration": "",
             },
             "HTTP_DELETE" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: {orderId: number}, header: never, body: undefined, context: typeof Context2, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: {orderId: number}, header: never, body: undefined, context: typeof Context2, response: ResponseBuilderFactory<{
 400: {
           headers: {};
           content: {};
@@ -3776,12 +3747,11 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: 400 
         } | {  
           status: 404 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1store~1order~1{orderId}/delete",
               "isDefault": false,
@@ -3919,7 +3889,7 @@ Map {
           },
           "exports": Map {
             "HTTP_GET" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: {orderId: number}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: {orderId: number}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -3939,7 +3909,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: Order
@@ -3951,8 +3921,7 @@ Map {
           status: 400 
         } | {  
           status: 404 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1store~1order~1{orderId}/get",
               "isDefault": false,
@@ -3962,7 +3931,7 @@ Map {
               "typeDeclaration": "",
             },
             "HTTP_DELETE" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: {orderId: number}, header: never, body: undefined, context: typeof Context2, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: {orderId: number}, header: never, body: undefined, context: typeof Context2, response: ResponseBuilderFactory<{
 400: {
           headers: {};
           content: {};
@@ -3971,12 +3940,11 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: 400 
         } | {  
           status: 404 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1store~1order~1{orderId}/delete",
               "isDefault": false,
@@ -4118,7 +4086,7 @@ Map {
     },
     "exports": Map {
       "HTTP_GET" => Object {
-        "code": "({ query, path, header, body, context }: { query: never, path: {orderId: number}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+        "code": "({ query, path, header, body, context, proxy }: { query: never, path: {orderId: number}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -4138,7 +4106,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: Order
@@ -4150,8 +4118,7 @@ Map {
           status: 400 
         } | {  
           status: 404 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
         "done": true,
         "id": "OperationTypeCoder@petstore.yaml#/paths/~1store~1order~1{orderId}/get",
         "isDefault": false,
@@ -4161,7 +4128,7 @@ Map {
         "typeDeclaration": "",
       },
       "HTTP_DELETE" => Object {
-        "code": "({ query, path, header, body, context }: { query: never, path: {orderId: number}, header: never, body: undefined, context: typeof Context2, response: ResponseBuilderFactory<{
+        "code": "({ query, path, header, body, context, proxy }: { query: never, path: {orderId: number}, header: never, body: undefined, context: typeof Context2, response: ResponseBuilderFactory<{
 400: {
           headers: {};
           content: {};
@@ -4170,12 +4137,11 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: 400 
         } | {  
           status: 404 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
         "done": true,
         "id": "OperationTypeCoder@petstore.yaml#/paths/~1store~1order~1{orderId}/delete",
         "isDefault": false,
@@ -4331,7 +4297,7 @@ Map {
           },
           "exports": Map {
             "HTTP_POST" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: never, header: never, body: User, context: typeof Context, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: never, header: never, body: User, context: typeof Context, response: ResponseBuilderFactory<{
 [statusCode in HttpStatusCode]: {
           headers: {};
           content: {
@@ -4343,7 +4309,7 @@ Map {
          }
 };
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: number | undefined, 
               contentType?: \\"application/json\\",
               body?: User
@@ -4351,8 +4317,7 @@ Map {
               status: number | undefined, 
               contentType?: \\"application/xml\\",
               body?: User
-            } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+            } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1user/post",
               "isDefault": false,
@@ -4458,7 +4423,7 @@ Map {
     },
     "exports": Map {
       "HTTP_POST" => Object {
-        "code": "({ query, path, header, body, context }: { query: never, path: never, header: never, body: User, context: typeof Context, response: ResponseBuilderFactory<{
+        "code": "({ query, path, header, body, context, proxy }: { query: never, path: never, header: never, body: User, context: typeof Context, response: ResponseBuilderFactory<{
 [statusCode in HttpStatusCode]: {
           headers: {};
           content: {
@@ -4470,7 +4435,7 @@ Map {
          }
 };
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: number | undefined, 
               contentType?: \\"application/json\\",
               body?: User
@@ -4478,8 +4443,7 @@ Map {
               status: number | undefined, 
               contentType?: \\"application/xml\\",
               body?: User
-            } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+            } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
         "done": true,
         "id": "OperationTypeCoder@petstore.yaml#/paths/~1user/post",
         "isDefault": false,
@@ -4601,7 +4565,7 @@ Map {
           },
           "exports": Map {
             "HTTP_POST" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: never, header: never, body: Array<User>, context: typeof Context, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: never, header: never, body: Array<User>, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -4617,7 +4581,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: User
@@ -4627,8 +4591,7 @@ Map {
               body?: User
             } | {  
           status: number | undefined 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1user~1createWithList/post",
               "isDefault": false,
@@ -4736,7 +4699,7 @@ Map {
     },
     "exports": Map {
       "HTTP_POST" => Object {
-        "code": "({ query, path, header, body, context }: { query: never, path: never, header: never, body: Array<User>, context: typeof Context, response: ResponseBuilderFactory<{
+        "code": "({ query, path, header, body, context, proxy }: { query: never, path: never, header: never, body: Array<User>, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -4752,7 +4715,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: User
@@ -4762,8 +4725,7 @@ Map {
               body?: User
             } | {  
           status: number | undefined 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
         "done": true,
         "id": "OperationTypeCoder@petstore.yaml#/paths/~1user~1createWithList/post",
         "isDefault": false,
@@ -4886,7 +4848,7 @@ Map {
           },
           "exports": Map {
             "HTTP_GET" => Object {
-              "code": "({ query, path, header, body, context }: { query: {username?: string, password?: string}, path: never, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: {username?: string, password?: string}, path: never, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {
 \\"X-Rate-Limit\\": { schema: number},
@@ -4905,7 +4867,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: string
@@ -4915,8 +4877,7 @@ Map {
               body?: string
             } | {  
           status: 400 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1user~1login/get",
               "isDefault": false,
@@ -4993,7 +4954,7 @@ Map {
     },
     "exports": Map {
       "HTTP_GET" => Object {
-        "code": "({ query, path, header, body, context }: { query: {username?: string, password?: string}, path: never, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+        "code": "({ query, path, header, body, context, proxy }: { query: {username?: string, password?: string}, path: never, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {
 \\"X-Rate-Limit\\": { schema: number},
@@ -5012,7 +4973,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: string
@@ -5022,8 +4983,7 @@ Map {
               body?: string
             } | {  
           status: 400 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
         "done": true,
         "id": "OperationTypeCoder@petstore.yaml#/paths/~1user~1login/get",
         "isDefault": false,
@@ -5114,15 +5074,14 @@ Map {
           },
           "exports": Map {
             "HTTP_GET" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: never, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: never, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 [statusCode in HttpStatusCode]: {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: number | undefined 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1user~1logout/get",
               "isDefault": false,
@@ -5199,15 +5158,14 @@ Map {
     },
     "exports": Map {
       "HTTP_GET" => Object {
-        "code": "({ query, path, header, body, context }: { query: never, path: never, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+        "code": "({ query, path, header, body, context, proxy }: { query: never, path: never, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 [statusCode in HttpStatusCode]: {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: number | undefined 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
         "done": true,
         "id": "OperationTypeCoder@petstore.yaml#/paths/~1user~1logout/get",
         "isDefault": false,
@@ -5329,7 +5287,7 @@ Map {
           },
           "exports": Map {
             "HTTP_GET" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: {username: string}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: {username: string}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -5349,7 +5307,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: User
@@ -5361,8 +5319,7 @@ Map {
           status: 400 
         } | {  
           status: 404 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1user~1{username}/get",
               "isDefault": false,
@@ -5372,15 +5329,14 @@ Map {
               "typeDeclaration": "",
             },
             "HTTP_PUT" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: {username: string}, header: never, body: User, context: typeof Context2, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: {username: string}, header: never, body: User, context: typeof Context2, response: ResponseBuilderFactory<{
 [statusCode in HttpStatusCode]: {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: number | undefined 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1user~1{username}/put",
               "isDefault": false,
@@ -5390,7 +5346,7 @@ Map {
               "typeDeclaration": "",
             },
             "HTTP_DELETE" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: {username: string}, header: never, body: undefined, context: typeof Context3, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: {username: string}, header: never, body: undefined, context: typeof Context3, response: ResponseBuilderFactory<{
 400: {
           headers: {};
           content: {};
@@ -5399,12 +5355,11 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: 400 
         } | {  
           status: 404 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1user~1{username}/delete",
               "isDefault": false,
@@ -5576,7 +5531,7 @@ Map {
           },
           "exports": Map {
             "HTTP_GET" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: {username: string}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: {username: string}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -5596,7 +5551,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: User
@@ -5608,8 +5563,7 @@ Map {
           status: 400 
         } | {  
           status: 404 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1user~1{username}/get",
               "isDefault": false,
@@ -5619,15 +5573,14 @@ Map {
               "typeDeclaration": "",
             },
             "HTTP_PUT" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: {username: string}, header: never, body: User, context: typeof Context2, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: {username: string}, header: never, body: User, context: typeof Context2, response: ResponseBuilderFactory<{
 [statusCode in HttpStatusCode]: {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: number | undefined 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1user~1{username}/put",
               "isDefault": false,
@@ -5637,7 +5590,7 @@ Map {
               "typeDeclaration": "",
             },
             "HTTP_DELETE" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: {username: string}, header: never, body: undefined, context: typeof Context3, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: {username: string}, header: never, body: undefined, context: typeof Context3, response: ResponseBuilderFactory<{
 400: {
           headers: {};
           content: {};
@@ -5646,12 +5599,11 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: 400 
         } | {  
           status: 404 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1user~1{username}/delete",
               "isDefault": false,
@@ -5823,7 +5775,7 @@ Map {
           },
           "exports": Map {
             "HTTP_GET" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: {username: string}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: {username: string}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -5843,7 +5795,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: User
@@ -5855,8 +5807,7 @@ Map {
           status: 400 
         } | {  
           status: 404 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1user~1{username}/get",
               "isDefault": false,
@@ -5866,15 +5817,14 @@ Map {
               "typeDeclaration": "",
             },
             "HTTP_PUT" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: {username: string}, header: never, body: User, context: typeof Context2, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: {username: string}, header: never, body: User, context: typeof Context2, response: ResponseBuilderFactory<{
 [statusCode in HttpStatusCode]: {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: number | undefined 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1user~1{username}/put",
               "isDefault": false,
@@ -5884,7 +5834,7 @@ Map {
               "typeDeclaration": "",
             },
             "HTTP_DELETE" => Object {
-              "code": "({ query, path, header, body, context }: { query: never, path: {username: string}, header: never, body: undefined, context: typeof Context3, response: ResponseBuilderFactory<{
+              "code": "({ query, path, header, body, context, proxy }: { query: never, path: {username: string}, header: never, body: undefined, context: typeof Context3, response: ResponseBuilderFactory<{
 400: {
           headers: {};
           content: {};
@@ -5893,12 +5843,11 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: 400 
         } | {  
           status: 404 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
               "done": true,
               "id": "OperationTypeCoder@petstore.yaml#/paths/~1user~1{username}/delete",
               "isDefault": false,
@@ -6074,7 +6023,7 @@ Map {
     },
     "exports": Map {
       "HTTP_GET" => Object {
-        "code": "({ query, path, header, body, context }: { query: never, path: {username: string}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
+        "code": "({ query, path, header, body, context, proxy }: { query: never, path: {username: string}, header: never, body: undefined, context: typeof Context, response: ResponseBuilderFactory<{
 200: {
           headers: {};
           content: {
@@ -6094,7 +6043,7 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
               status: 200, 
               contentType?: \\"application/xml\\",
               body?: User
@@ -6106,8 +6055,7 @@ Map {
           status: 400 
         } | {  
           status: 404 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
         "done": true,
         "id": "OperationTypeCoder@petstore.yaml#/paths/~1user~1{username}/get",
         "isDefault": false,
@@ -6117,15 +6065,14 @@ Map {
         "typeDeclaration": "",
       },
       "HTTP_PUT" => Object {
-        "code": "({ query, path, header, body, context }: { query: never, path: {username: string}, header: never, body: User, context: typeof Context2, response: ResponseBuilderFactory<{
+        "code": "({ query, path, header, body, context, proxy }: { query: never, path: {username: string}, header: never, body: User, context: typeof Context2, response: ResponseBuilderFactory<{
 [statusCode in HttpStatusCode]: {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: number | undefined 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
         "done": true,
         "id": "OperationTypeCoder@petstore.yaml#/paths/~1user~1{username}/put",
         "isDefault": false,
@@ -6135,7 +6082,7 @@ Map {
         "typeDeclaration": "",
       },
       "HTTP_DELETE" => Object {
-        "code": "({ query, path, header, body, context }: { query: never, path: {username: string}, header: never, body: undefined, context: typeof Context3, response: ResponseBuilderFactory<{
+        "code": "({ query, path, header, body, context, proxy }: { query: never, path: {username: string}, header: never, body: undefined, context: typeof Context3, response: ResponseBuilderFactory<{
 400: {
           headers: {};
           content: {};
@@ -6144,12 +6091,11 @@ Map {
           headers: {};
           content: {};
         }
-}> }) => {  
+}>, proxy: (url: string) => { proxyUrl: string } }) => {  
           status: 400 
         } | {  
           status: 404 
-        } | { status: 415, contentType: \\"text/plain\\", body: string }
-    | void",
+        } | { status: 415, contentType: \\"text/plain\\", body: string } | void",
         "done": true,
         "id": "OperationTypeCoder@petstore.yaml#/paths/~1user~1{username}/delete",
         "isDefault": false,
diff --git a/test/typescript-generator/__snapshots__/operation-type-coder.test.js.snap b/test/typescript-generator/__snapshots__/operation-type-coder.test.js.snap
index c452458..2e6c883 100644
--- a/test/typescript-generator/__snapshots__/operation-type-coder.test.js.snap
+++ b/test/typescript-generator/__snapshots__/operation-type-coder.test.js.snap
@@ -7,6 +7,7 @@ exports[`an OperationTypeCoder generates a complex post operation (OpenAPI 2) 1`
   header,
   body,
   context,
+  proxy,
 }: {
   query: { name?: string };
   path: { id?: string };
@@ -39,6 +40,7 @@ exports[`an OperationTypeCoder generates a complex post operation (OpenAPI 2) 1`
       };
     };
   }>;
+  proxy: (url: string) => { proxyUrl: string };
 }) =>
   | {
       status: 200;
@@ -67,6 +69,7 @@ exports[`an OperationTypeCoder generates a complex post operation 1`] = `
   header,
   body,
   context,
+  proxy,
 }: {
   query: { name?: string };
   path: { id?: string };
@@ -99,6 +102,7 @@ exports[`an OperationTypeCoder generates a complex post operation 1`] = `
       };
     };
   }>;
+  proxy: (url: string) => { proxyUrl: string };
 }) =>
   | {
       status: 200;
@@ -127,6 +131,7 @@ exports[`an OperationTypeCoder generates a simple get operation (OpenAPI 2) 1`]
   header,
   body,
   context,
+  proxy,
 }: {
   query: never;
   path: never;
@@ -143,6 +148,7 @@ exports[`an OperationTypeCoder generates a simple get operation (OpenAPI 2) 1`]
       };
     };
   }>;
+  proxy: (url: string) => { proxyUrl: string };
 }) =>
   | {
       status: number | undefined;
@@ -161,6 +167,7 @@ exports[`an OperationTypeCoder generates a simple get operation 1`] = `
   header,
   body,
   context,
+  proxy,
 }: {
   query: never;
   path: never;
@@ -177,6 +184,7 @@ exports[`an OperationTypeCoder generates a simple get operation 1`] = `
       };
     };
   }>;
+  proxy: (url: string) => { proxyUrl: string };
 }) =>
   | {
       status: number | undefined;