counterfact 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,5 +15,14 @@
15
15
  "[typescript]": {
16
16
  "editor.defaultFormatter": "dbaeumer.vscode-eslint"
17
17
  },
18
- "cSpell.words": ["bodyparser", "counterfact", "openapi", "transpiles"]
18
+ "cSpell.words": [
19
+ "bodyparser",
20
+ "counterfact",
21
+ "Deno",
22
+ "openapi",
23
+ "Petstore",
24
+ "counterfactuals",
25
+ "openapi",
26
+ "transpiles"
27
+ ]
19
28
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # counterfact
2
2
 
3
+ ## 0.15.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 78c0c31: return $.proxy(url) to proxy to another server
8
+ - 6bf3d42: $context.ts -> $.context.ts
9
+ - 7c2893a: change rapidoc configuration to show paths in the nav bar
10
+ - 033c067: new landing page with links to Rapidoc and Swagger UI
11
+ - 6bf3d42: improve and document the REPL
12
+
13
+ ### Patch Changes
14
+
15
+ - a2d6bf0: always start the server regardless of which command line options are present
16
+ - 4a96dfa: fix crash when a path is not found
17
+
3
18
  ## 0.14.0
4
19
 
5
20
  ### Minor Changes
package/README.md CHANGED
@@ -1,75 +1,126 @@
1
+ <div align="center" markdown="1">
1
2
 
3
+ # Counterfact is the mock server that front end engineers need to be productive
2
4
 
3
- <div align="center" markdown="1">
5
+ [Quick Start](./docs/quick-start.md) | [Documentation](./docs/usage.md) | [Contributing](CONTRIBUTING.md)
4
6
 
5
- # Counterfact
7
+ </div>
6
8
 
7
- _Front end development without back end headaches_
9
+ <br>
8
10
 
9
- [Quick Start](#quick-start) | [Documentation](./docs/usage.md) | [Contributing](CONTRIBUTING.md)
11
+ <div align="center" markdown="1">
10
12
 
11
- Counterfact is a stand-in REST server powered by Node, TypeScript, and OpenAPI.<br>
12
- It enables you to write front end code and test UX flows without a complete back end.
13
+ [![Coverage Status](https://coveralls.io/repos/github/pmcelhaney/counterfact/badge.svg)](https://coveralls.io/github/pmcelhaney/counterfact) [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fpmcelhaney%2Fcounterfact%2Fmain)](https://dashboard.stryker-mutator.io/reports/github.com/pmcelhaney/counterfact/main) ![MIT License](https://img.shields.io/badge/license-MIT-blue)
13
14
 
14
15
  </div>
15
16
 
16
- <br>
17
+ ## Features
17
18
 
18
- <table align="center" cols="2" markdown="1">
19
+ - Supports JavaScript and TypeScript
20
+ - Runs on Node, Deno, and Bun
21
+ - Uses Swagger / OpenAPI if you have it
22
+ - File system based routing
23
+ - Hot reload server-side code
24
+ - Manipulate test data with a REPL
25
+ - Toggle between the mock and real backend at runtime
26
+ - Works with any “front end” that calls RESTful services (a React app, a native iOS app, a web service, etc.)
27
+ - Emulates the real back end, but 100X faster
19
28
 
20
- <tr>
21
- <td>
29
+ ## Use Cases
22
30
 
23
- 💪 build the UI before the API or build both in parallel<br>
24
- 🏎️ quickly and cheaply prototype UX workflows<br>
25
- 🎉 turn OpenAPI docs into functional code
31
+ - Rapidly iterate on the front end without waiting for back end features or changes
32
+ - Reproduce bugs when the conditions on the server side are hard to replicate
33
+ - Test against a private, local stack that you fully control
34
+ - Write contract tests for APIs
26
35
 
27
- </td>
36
+ Unlike similar tools, Counterfact is not limited to canned or randomly generated responses. It can mimic as much or as little of the business logic in your n-tier / cloud-native back end as you want. Test end-to-end scenarios in your front end code as if a lighting-fast implementation of the real backend is running on your machine. Because it is.
28
37
 
29
- <td>
38
+ As a front end dev, when you have **complete and granular control over the back end**, and you can **modify anything on a whim**, it **enhances your developer experience** in ways that are hard to describe.
30
39
 
31
- ⛓️ write fast, repeatable UI integration tests<br>
32
- 🧑‍🔬 test UI code against hard-to-recreate edge cases<br>
33
- 🔌 plug into your existing toolchain
40
+ ## What does the code look like?
34
41
 
35
- </td>
42
+ <details markdown="1">
36
43
 
37
- </tr>
44
+ <summary><code>GET /hello/world</code></summary>
38
45
 
39
- </table>
46
+ ```js
47
+ // ./paths/hello/world.js
48
+ export const GET = () => "Hello World!";
49
+ ```
40
50
 
41
- <div align="center" markdown="1">
51
+ </details>
42
52
 
43
- [![Coverage Status](https://coveralls.io/repos/github/pmcelhaney/counterfact/badge.svg)](https://coveralls.io/github/pmcelhaney/counterfact) [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fpmcelhaney%2Fcounterfact%2Fmain)](https://dashboard.stryker-mutator.io/reports/github.com/pmcelhaney/counterfact/main) ![MIT License](https://img.shields.io/badge/license-MIT-blue)
53
+ <details>
44
54
 
55
+ <summary markdown="1">Using the <a href="https://petstore3.swagger.io">Swagger Petstore</a> as an example, here’s one way to implement <code>GET /pet/{petId}`</code> and <code>POST `POST /pets`</code>.</summary>
45
56
 
46
- </div>
57
+ ```js
58
+ // ./paths/pet/{petId}.js
59
+ export const GET = ({ context, response, path }) => {
60
+ const pet = context.getPetById(path.petId);
47
61
 
48
- <h2 id="quick-start">Got 60 Seconds? Try me!</h2>
62
+ if (!pet) {
63
+ return response[404].text(`Pet with ID ${path.petID} not found.`);
64
+ }
49
65
 
50
- Copy the following command into your terminal. The only prerequisite is Node 16+.
66
+ return response[200].json(pet);
67
+ };
68
+ ```
51
69
 
52
- ```sh copy
53
- npx counterfact@latest https://petstore3.swagger.io/api/v3/openapi.json api --open
70
+ ```js
71
+ // ./paths/pets.js
72
+ export const POST = ({ context, response, body }) => {
73
+ const pet = context.addPet(body);
74
+
75
+ return response[200].json(pet);
76
+ };
54
77
  ```
55
78
 
56
- <details>
57
- <summary>What does that command do?</summary>
79
+ ```js
80
+ // ./paths/$.context.js
81
+ class PetStore () {
82
+ pets = {};
83
+
84
+ getPetById(petId) {
85
+ return pets[id];
86
+ }
58
87
 
59
- 1. installs the `@latest` version of `counterfact`
60
- 2. reads an [OpenAPI 3](https://oai.github.io/Documentation/) document (`https://petstore3.swagger.io/api/v3/openapi.json`)
61
- 3. generates TypeScript files in the `api` directory
62
- 4. starts a server which implements the API
63
- 5. opens your browser to [Swagger UI](https://swagger.io/tools/swagger-ui/) (`--open`)
88
+ addPet(pet) {
89
+ this.pets[pet.id] = pet;
90
+ }
91
+ }
64
92
 
65
- You can use Swagger to try out the auto-generated API. Out of the box, it returns random responses using metadata from the OpenAPI document. Edit the files under `./api/paths` to add more realistic behavior. There's no need to restart the server.
93
+ export default new PetStore();
94
+ ```
66
95
 
67
96
  </details>
68
97
 
69
- <br>
98
+ ## If you have a Swagger / OpenAPI spec, you'll be up and running in seconds
99
+
100
+ For example, run the following command to generate code for the Swagger Petstore.
101
+
102
+ ```sh copy
103
+ npx counterfact@latest https://petstore3.swagger.io/api/v3/openapi.json api --open
104
+ ```
70
105
 
71
- 📗 See [Usage](./docs/usage.md) for detailed documentation.
106
+ That command generates and starts a TypeScript implementation of the Swagger Petstore which returns random, valid responses. Not too shabby for _a few seconds of work_!
107
+
108
+ ## It's your server. Do whatever you want with it.
109
+
110
+ Edit the code under `./api/paths` to **implement real behavior**, as we did in the JavaScript examples above. Store and retrieve data, perform calculations, filter / sort / paginate, whatever it takes the make the API real enough to support the development and testing of your front end.
111
+
112
+ - Use autocomplete as documentation. For example, when you're working on an operation that takes query parameters, type "`$.query.`" to list all of the available parameters.
113
+ - Stay in sync with changes in your OpenAPI / Swagger document. Counterfact will keep the type definitions up to date without overwriting your work.
114
+ - Get immediate feedback. Modules are hot reloaded so changes apply immediately without restarting the server or resetting its state.
115
+ - Interact with the running server using a REPL. For example, type `context.addPet({id: 2, name: "Fido"})` to add a pet to the store. Then view the pet you just added at `http://localhost:3100/pet/2`.
116
+ - Much more!
117
+
118
+ See the [Usage Guide](./docs/usage.md) for details.
72
119
 
73
120
  ---
74
121
 
75
- Counterfact is brand new as of October 5, 2022. Please send feedback / questions to pmcelhaney@gmail.com or [create a new issue](https://github.com/pmcelhaney/counterfact/issues/new). If you like what you see, please give this project a star!
122
+ Counterfact is brand new as of November 30, 2022. Please send feedback / questions to pmcelhaney@gmail.com or [create a new issue](https://github.com/pmcelhaney/counterfact/issues/new). If you like what you see, please give this project a star!
123
+
124
+ ```
125
+
126
+ ```
@@ -20,32 +20,79 @@ async function main(source, destination) {
20
20
  const basePath = nodePath.resolve(nodePath.join(process.cwd(), destination));
21
21
 
22
22
  const openBrowser = options.open;
23
- const includeSwagger = options.swagger || openBrowser;
24
- const startServer = options.server || includeSwagger;
25
23
 
26
- if (startServer) {
27
- const { contextRegistry } = await start({
24
+ const url = `http://localhost:${options.port}`;
25
+
26
+ const guiUrl = `${url}/counterfact/`;
27
+
28
+ const { contextRegistry } = await start({
29
+ basePath,
30
+ port: options.port,
31
+ openApiPath: source,
32
+ includeSwaggerUi: true,
33
+ });
34
+
35
+ const waysToInteract = [
36
+ `Call the REST APIs at ${url} (with your front end app, curl, Postman, etc.)`,
37
+ `Change the implementation of the APIs by editing files under ${nodePath.join(
28
38
  basePath,
29
- port: options.port,
30
- openApiPath: source,
31
- includeSwaggerUi: includeSwagger,
32
- });
39
+ "paths"
40
+ )} (no need to restart)`,
41
+ `Use the GUI at ${guiUrl}`,
42
+ "Use the REPL below (type .counterfact for more information)",
43
+ ];
33
44
 
34
- process.stdout.write(
35
- `API is running at http://localhost:${options.port}.\n`
36
- );
45
+ const introduction = [
46
+ "",
47
+ "Welcome to Counterfact!",
48
+ "",
49
+ "Counterfact is a mock server used to develop and test your front end app.",
50
+ "There are several ways to poke and prod the server in order to make it behave the way you need for testing.",
51
+ "",
52
+ ];
37
53
 
38
- process.stdout.write(
39
- '\n\nStarting REPL... (start with `const context = loadContext("/")`)\n'
40
- );
54
+ process.stdout.write(`${introduction.join("\n")}\n`);
41
55
 
42
- const replServer = repl.start("> ");
56
+ process.stdout.write(
57
+ waysToInteract.map((text, index) => `${index + 1}. ${text}`).join("\n")
58
+ );
43
59
 
44
- replServer.context.loadContext = (path) => contextRegistry.find(path);
45
- }
60
+ process.stdout.write("\n\n");
61
+
62
+ process.stdout.write("Starting REPL...\n");
63
+
64
+ const replServer = repl.start("> ");
65
+
66
+ replServer.defineCommand("counterfact", {
67
+ help: "Get help with Counterfact",
68
+
69
+ action() {
70
+ process.stdout.write(
71
+ "This is a read-eval-print loop (REPL), the same as the one you get when you run node with no arguments.\n"
72
+ );
73
+ process.stdout.write(
74
+ "Except that it's connected to the running server, which you can access with the following globals:\n\n"
75
+ );
76
+ process.stdout.write(
77
+ "- loadContext('/some/path'): to access the context object for a given path\n"
78
+ );
79
+ process.stdout.write(
80
+ "- context: the root context ( same as loadContext('/') )\n"
81
+ );
82
+ process.stdout.write(
83
+ "\nFor more information, see https://counterfact.dev/docs/usage.html\n\n"
84
+ );
85
+
86
+ this.clearBufferedCommand();
87
+ this.displayPrompt();
88
+ },
89
+ });
90
+
91
+ replServer.context.loadContext = (path) => contextRegistry.find(path);
92
+ replServer.context.context = replServer.context.loadContext("/");
46
93
 
47
94
  if (openBrowser) {
48
- await open(`http://localhost:${options.port}/counterfact`);
95
+ await open(guiUrl);
49
96
  }
50
97
  }
51
98
 
@@ -56,12 +103,8 @@ program
56
103
  )
57
104
  .argument("<openapi.yaml>", "path or URL to OpenAPI document")
58
105
  .argument("[destination]", "path to generated code", ".")
59
- .option("--serve", "start the server after generating code")
60
106
  .option("--port <number>", "server port number", DEFAULT_PORT)
61
- .option("--swagger", "include swagger-ui (implies --serve)")
62
- .option(
63
- "--open",
64
- "open a browser to swagger-ui (implies --swagger and --serve)"
65
- )
107
+ .option("--swagger", "include swagger-ui")
108
+ .option("--open", "open a browser")
66
109
  .action(main)
67
110
  .parse(process.argv);
@@ -0,0 +1,19 @@
1
+ # Got 60 Seconds? Try me!
2
+
3
+ Copy the following command into your terminal. The only prerequisite is Node 16+.
4
+
5
+ ```sh copy
6
+ npx counterfact@latest https://petstore3.swagger.io/api/v3/openapi.json api --open
7
+ ```
8
+
9
+ ## What does that command do?
10
+
11
+ 1. installs the `@latest` version of `counterfact`
12
+ 2. reads an [OpenAPI 3](https://oai.github.io/Documentation/) document (`https://petstore3.swagger.io/api/v3/openapi.json`)
13
+ 3. generates TypeScript files in the `api` directory
14
+ 4. starts a server which implements the API
15
+ 5. opens your browser to [Swagger UI](https://swagger.io/tools/swagger-ui/) (`--open`)
16
+
17
+ You can use Swagger to try out the auto-generated API. Out of the box, it returns random responses using metadata from the OpenAPI document. Edit the files under `./api/paths` to add more realistic behavior. There's no need to restart the server.
18
+
19
+ To learn more, see the [Usage Guide](./usage.md).
package/docs/usage.md CHANGED
@@ -117,7 +117,9 @@ Note that each of these objects is typed so you can use autocomplete to identify
117
117
 
118
118
  The `$.path` parameters are identified by dynamic sections of the path, i.e. `/groups/{groupName}/user/{userId}.ts`.
119
119
 
120
- ### Working with state: the `$.context` object and `$context.ts`
120
+ <a id="context-object"></a>
121
+
122
+ ### Working with state: the `$.context` object and `$.context.ts`
121
123
 
122
124
  There's one more parameter we need to explore, the `$.context` object. It stands in for microservices, databases, and other entities with which the real API interacts. It looks something like this:
123
125
 
@@ -137,7 +139,7 @@ export const POST: HTTP_POST = ($) => {
137
139
  };
138
140
  ```
139
141
 
140
- The `context` object is defined in `$context.js` in the same directory as the file that uses it. It's up to you to define the API for a context object. For example, your `$context.js` file might look like this.
142
+ The `context` object is defined in `$.context.js` in the same directory as the file that uses it. It's up to you to define the API for a context object. For example, your `$.context.js` file might look like this.
141
143
 
142
144
  ```ts
143
145
  class PetStore {
@@ -157,7 +159,7 @@ class PetStore {
157
159
  export default new PetStore();
158
160
  ```
159
161
 
160
- By default, each `$context.ts` delegates to its parent directory, so you can define one context object in the root `$context.ts` and use it everywhere.
162
+ By default, each `$.context.ts` delegates to its parent directory, so you can define one context object in the root `$.context.ts` and use it everywhere.
161
163
 
162
164
  > You can make the context objects do whatever you want, including things like writing to databases. But remember that Counterfact is meant for testing, so holding on to data between sessions is an anti-pattern. Keeping everything in memory also makes it fast.
163
165
 
@@ -174,23 +176,61 @@ Hot reloading supports one of Counterfact's key design goals. While developing a
174
176
 
175
177
  In such cases, we want to be sure the front end code responds appropriately. Getting a real server to do what we need to test front end code is usually difficult if not impossible. Counterfact is optimized to make bending the server's behavior to suit a test case as painless as possible, in both manual and automated tests.
176
178
 
179
+ ## REPL without a Pause ⏯
180
+
181
+ Another way to explore counterfactuals in real time is to interact with the running server via the read-eval-print loop (REPL), in the same way that you interact with running UI code in your browser's developer tools console. If you look in the terminal after starting Counterfact you should see a prompt like this:
182
+
183
+ ```txt
184
+ Welcome to Counterfact!
185
+
186
+ Counterfact is a mock server used to develop and test your front end app.
187
+ There are several ways to poke and prod the server in order to make it behave the way you need for testing.
188
+
189
+ 1. Call the REST APIs at http://localhost:3100 (with your front end app, curl, Postman, etc.)
190
+ 2. Change the implementation of the APIs by editing files under /Users/pmcelhaney/code/counterfact/out/paths (no need to restart)
191
+ 3. Use the GUI at http://localhost:3100
192
+ 4. Use the REPL below (type .counterfact for more information)
193
+
194
+ >
195
+ ```
196
+
197
+ At the `> ` prompt, you can enter JavaScript code to interact with the live [context object](#context-object). For example, here's a quick way to add a pet to the store.
198
+
199
+ ```js
200
+ context.addPet({ name: "Fluffy", photoUrls: [] });
201
+ ```
202
+
203
+ Or add 100 pets:
204
+
205
+ ```js
206
+ for (i = 0; i < 100; i++) context.addPet({ name: `Pet ${i}`, photoUrls: [] });
207
+ ```
208
+
209
+ Or get a list of pets whose names start with "F"
210
+
211
+ ```js
212
+ context.pets.find((pet) => pet.name.startsWith("F"));
213
+ ```
214
+
215
+ Using the REPL is a lot faster (and more fun) than wrangling config files and SQL and whatever else it takes to a real back end into the states you need to test your UI flows.
216
+
177
217
  ## No Cap Recap 🧢
178
218
 
179
- That's everything you need to know about Counterfact. Using convention over configuration and automatically generated types, it allows front-end developers to quickly build fake REST APIs for prototype and testing purposes.
219
+ Using convention over configuration and automatically generated types, Counterfact allows front-end developers to quickly build fake REST APIs for prototype and testing purposes.
180
220
 
181
221
  - Given an OpenAPI document, you can generate working TypeScript code and start up a server in seconds.
182
222
  - By default, the server returns random responses based on metadata in the OpenAPI document (e.g. it uses examples where provided).
183
223
  - Each endpoint is represented by a TypeScript file where the path to the file corresponds to the path of the endpoint.
184
224
  - You can change the implementation at any time by changing these files.
185
225
  - You can and should commit the generated code to source control. Files you change will not be overwritten when you start the server again. (The _types_ will be updated if the OpenAPI document changes, but you shouldn't need to edit the type definitions by hand.)
186
- - Put behavior in `$context.ts` files. These are created for you, but you should rewrite them to suit your needs. (At least update the root `$context.ts` file.)
226
+ - Put behavior in `$.context.ts` files. These are created for you, but you should rewrite them to suit your needs. (At least update the root `$.context.ts` file.)
227
+ - Use the REPL to manipulate the server's state at runtime
187
228
 
188
229
  ## We're Just Getting Started 🐣
189
230
 
190
- "Advanced" features are coming soon:
231
+ More features are coming soon:
191
232
 
192
233
  - Integrate Counterfact into your workflow (Express, Koa, Webpack Dev Server, etc.)
193
- - Use a REPL to interact with the context objects while the server is running
194
234
  - Use Counterfact in your automated tests
195
235
  - Record API calls while testing the front end manually and reuse those calls in automated tests (ala [Playwright](https://playwright.dev/))
196
236
  - Use [HAR](https://toolbox.googleapps.com/apps/har_analyzer/) files to recreate scenarios / bugs encountered by real users
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "counterfact",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "a library for building a fake REST API for testing",
5
5
  "type": "module",
6
6
  "main": "./src/server/counterfact.js",
@@ -41,7 +41,7 @@
41
41
  "@stryker-mutator/jest-runner": "6.3.0",
42
42
  "@types/koa": "2.13.5",
43
43
  "@types/koa-static": "^4.0.2",
44
- "eslint": "8.27.0",
44
+ "eslint": "8.28.0",
45
45
  "eslint-config-hardcore": "25.1.0",
46
46
  "eslint-formatter-github-annotations": "0.1.0",
47
47
  "eslint-import-resolver-typescript": "^3.2.5",
@@ -61,6 +61,7 @@
61
61
  "@types/json-schema": "^7.0.11",
62
62
  "chokidar": "^3.5.3",
63
63
  "commander": "^9.4.0",
64
+ "fetch": "^1.1.0",
64
65
  "fs-extra": "^10.1.0",
65
66
  "handlebars": "^4.7.7",
66
67
  "js-yaml": "^4.1.0",
@@ -69,6 +70,7 @@
69
70
  "jsonwebtoken": "^8.5.1",
70
71
  "koa": "^2.13.4",
71
72
  "koa-bodyparser": "^4.3.0",
73
+ "koa-proxy": "^1.0.0-alpha.3",
72
74
  "koa2-swagger-ui": "^5.6.0",
73
75
  "node-fetch": "^3.2.10",
74
76
  "open": "^8.4.0",
@@ -3,37 +3,43 @@
3
3
  <head>
4
4
  <title>Counterfact</title>
5
5
  <meta charset="utf-8">
6
- <meta name="description" content="Counterfact dashboard" />
6
+ <meta name="description" content="Counterfact Dashboard" />
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1" />
8
- <script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script>
9
8
 
10
-
11
-
12
- </head>
9
+ <style type="text/css">
10
+ body {
11
+ font-family: Helvetica, Arial, sans-serif;
12
+ max-width: 800px;
13
+ margin: 0 auto;
14
+ }
15
+ </style>
13
16
  <body>
14
17
 
18
+ <h1>Counterfact</h1>
15
19
 
20
+ <p>
21
+ This server implements the OpenAPI spec at <a href="{{openApiHref}}">{{openApiPath}}</a>.
22
+ You can try it out using your favorite REST client (curl, Postman, etc.) or browse the interactive API documentation in <a href="./swagger">Swagger UI</a> or <a href="./rapidoc">Rapidoc</a>.
16
23
 
17
- <rapi-doc spec-url = "/counterfact/openapi">
24
+ </p>
18
25
 
19
- <div slot="overview" style="background-color: #eee">
20
- <p>Put an explanation of Counterfact here.</p>
21
- <a
22
- href="https://github.com/pmcelhaney/counterfact/blob/main/docs/usage.md#generated-code-"
23
- >How does this work?</a
24
- >
25
- </div>
26
- {{#each routes as |route|}}
27
- <a slot="get-{{escape_route route}}" href="vscode://file/{{../basePath}}/paths/{{.}}.ts">Edit in VSCode</a>
28
- <a slot="put-{{escape_route route}}" href="vscode://file/{{../basePath}}/paths/{{.}}.ts">Edit in VSCode</a>
29
- <a slot="post-{{escape_route route}}" href="vscode://file/{{../basePath}}/paths/{{.}}.ts">Edit in VSCode</a>
30
- <a slot="delete-{{escape_route route}}" href="vscode://file/{{../basePath}}/paths/{{.}}.ts">Edit in VSCode</a>
31
- <a slot="patch-{{escape_route route}}" href="vscode://file/{{../basePath}}/paths/{{.}}.ts">Edit in VSCode</a>
26
+ <p>
27
+ The TypeScript code for each path can be found under <code>{{basePath}}</code>.
28
+ Click on a path below to edit its corresponding TypeScript file in VSCode.
29
+ For more information, see the <a href="https://counterfact.dev/docs/usage.html">usage guide</a>.
30
+ </p>
31
+
32
+ <ul>
33
+ {{#each routes as |route|}}
34
+ <li><a href="vscode://file/{{../basePath}}/paths/{{route}}.ts">{{route}}</a></li>
35
+ {{/each}}
36
+ </ul>
32
37
 
33
- {{/each}}
34
- </rapi-doc>
35
38
 
39
+ <hr>
36
40
 
41
+ <p>👋 Thanks for trying <a href="https://github.com/pmcelhaney/counterfact/">Counterfact</a>! Let me know how it goes at <a href="mailto:pmcelhaney@gmail.com">pmcelhaney@gmail.com</a> or <a href="https://github.com/pmcelhaney/counterfact/issues/new">Github</a> - <a href="https://patrickmcelhaney.com">Patrick</a></p>
37
42
 
43
+
38
44
  </body>
39
45
  </html>
@@ -0,0 +1,36 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title>Counterfact</title>
5
+ <meta charset="utf-8">
6
+ <meta name="description" content="Rapidoc" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
8
+ <script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script>
9
+ </head>
10
+ <body>
11
+
12
+
13
+
14
+ <rapi-doc spec-url="/counterfact/openapi" show-method-in-nav-bar="as-colored-block" nav-active-item-marker="left-bar" use-path-in-nav-bar="true">
15
+
16
+ <div slot="overview" style="background-color: #eee">
17
+ <p>Put an explanation of Counterfact here.</p>
18
+ <a
19
+ href="https://github.com/pmcelhaney/counterfact/blob/main/docs/usage.md#generated-code-"
20
+ >How does this work?</a
21
+ >
22
+ </div>
23
+ {{#each routes as |route|}}
24
+ <a slot="get-{{escape_route route}}" href="vscode://file/{{../basePath}}/paths/{{.}}.ts">Edit in VSCode</a>
25
+ <a slot="put-{{escape_route route}}" href="vscode://file/{{../basePath}}/paths/{{.}}.ts">Edit in VSCode</a>
26
+ <a slot="post-{{escape_route route}}" href="vscode://file/{{../basePath}}/paths/{{.}}.ts">Edit in VSCode</a>
27
+ <a slot="delete-{{escape_route route}}" href="vscode://file/{{../basePath}}/paths/{{.}}.ts">Edit in VSCode</a>
28
+ <a slot="patch-{{escape_route route}}" href="vscode://file/{{../basePath}}/paths/{{.}}.ts">Edit in VSCode</a>
29
+
30
+ {{/each}}
31
+ </rapi-doc>
32
+
33
+
34
+
35
+ </body>
36
+ </html>
@@ -1,4 +1,6 @@
1
1
  import Accept from "@hapi/accept";
2
+ // eslint-disable-next-line no-shadow
3
+ import fetch, { Headers } from "node-fetch";
2
4
 
3
5
  import { createResponseBuilder } from "./response-builder.js";
4
6
  import { Tools } from "./tools.js";
@@ -30,6 +32,9 @@ function parameterTypes(parameters) {
30
32
  export class Dispatcher {
31
33
  registry;
32
34
 
35
+ // alias the fetch function so we can mock it in tests
36
+ fetch = fetch;
37
+
33
38
  constructor(registry, contextRegistry, openApiDocument) {
34
39
  this.registry = registry;
35
40
  this.contextRegistry = contextRegistry;
@@ -61,6 +66,32 @@ export class Dispatcher {
61
66
  method
62
67
  )
63
68
  ),
69
+
70
+ proxy: async (hostOrOptions) => {
71
+ const options =
72
+ typeof hostOrOptions === "string"
73
+ ? { host: hostOrOptions }
74
+ : hostOrOptions;
75
+
76
+ const fetchResponse = await this.fetch(options.host, {
77
+ method,
78
+ path,
79
+ headers: new Headers(headers),
80
+ query,
81
+ ...options,
82
+ });
83
+
84
+ const responseHeaders = Object.fromEntries(
85
+ fetchResponse.headers.entries()
86
+ );
87
+
88
+ return {
89
+ status: fetchResponse.status,
90
+ contentType: responseHeaders["content-type"] ?? "unknown/unknown",
91
+ headers: responseHeaders,
92
+ body: await fetchResponse.text(),
93
+ };
94
+ },
64
95
  });
65
96
 
66
97
  const normalizedResponse = this.normalizeResponse(
@@ -1,12 +1,19 @@
1
+ import koaProxy from "koa-proxy";
2
+
1
3
  const HTTP_STATUS_CODE_OK = 200;
2
4
 
3
- export function koaMiddleware(dispatcher) {
4
- return async function middleware(ctx) {
5
- const { method, path, body, query } = ctx.request;
5
+ export function koaMiddleware(dispatcher, options = {}, proxy = koaProxy) {
6
+ return async function middleware(ctx, next) {
7
+ const { method, path, headers, body, query } = ctx.request;
8
+
9
+ if (options.proxyUrl) {
10
+ return proxy({ host: options.proxyUrl })(ctx, next);
11
+ }
6
12
 
7
13
  const response = await dispatcher.request({
8
14
  method,
9
15
  path,
16
+ headers,
10
17
  body,
11
18
  query,
12
19
  });
@@ -15,5 +22,7 @@ export function koaMiddleware(dispatcher) {
15
22
  ctx.body = response.body;
16
23
  ctx.status = response.status ?? HTTP_STATUS_CODE_OK;
17
24
  /* eslint-enable require-atomic-updates */
25
+
26
+ return undefined;
18
27
  };
19
28
  }