counterfact 2.5.1 β†’ 2.7.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.
Files changed (94) hide show
  1. package/README.md +103 -140
  2. package/bin/README.md +25 -4
  3. package/bin/counterfact.js +208 -24
  4. package/bin/register-ts-loader.mjs +17 -0
  5. package/bin/ts-loader.mjs +31 -0
  6. package/dist/app.js +31 -21
  7. package/dist/counterfact-types/cookie-options.js +1 -0
  8. package/dist/counterfact-types/counterfact-response.js +7 -0
  9. package/dist/counterfact-types/example-names.js +1 -0
  10. package/dist/counterfact-types/example.js +1 -0
  11. package/dist/counterfact-types/generic-response-builder.js +1 -0
  12. package/dist/counterfact-types/http-status-code.js +1 -0
  13. package/dist/counterfact-types/if-has-key.js +1 -0
  14. package/dist/counterfact-types/index.js +0 -1
  15. package/dist/counterfact-types/maybe-promise.js +1 -0
  16. package/dist/counterfact-types/media-type.js +1 -0
  17. package/dist/counterfact-types/omit-all.js +1 -0
  18. package/dist/counterfact-types/omit-value-when-never.js +1 -0
  19. package/dist/counterfact-types/open-api-content.js +1 -0
  20. package/dist/counterfact-types/open-api-operation.js +1 -0
  21. package/dist/counterfact-types/open-api-parameters.js +1 -0
  22. package/dist/counterfact-types/open-api-response.js +1 -0
  23. package/dist/counterfact-types/random-function.js +1 -0
  24. package/dist/counterfact-types/response-builder-factory.js +1 -0
  25. package/dist/counterfact-types/response-builder.js +1 -0
  26. package/dist/counterfact-types/wide-operation-argument.js +1 -0
  27. package/dist/counterfact-types/wide-response-builder.js +1 -0
  28. package/dist/migrate/update-route-types.js +30 -10
  29. package/dist/repl/raw-http-client.js +14 -14
  30. package/dist/repl/repl.js +119 -4
  31. package/dist/repl/route-builder.js +270 -0
  32. package/dist/server/config.js +1 -1
  33. package/dist/server/context-registry.js +44 -4
  34. package/dist/server/counterfact-types/cookie-options.ts +14 -0
  35. package/dist/server/counterfact-types/counterfact-response.ts +15 -0
  36. package/dist/server/counterfact-types/example-names.ts +13 -0
  37. package/dist/server/counterfact-types/example.ts +10 -0
  38. package/dist/server/counterfact-types/generic-response-builder.ts +164 -0
  39. package/dist/server/counterfact-types/http-status-code.ts +62 -0
  40. package/dist/server/counterfact-types/if-has-key.ts +19 -0
  41. package/dist/server/counterfact-types/index.ts +20 -328
  42. package/dist/server/counterfact-types/maybe-promise.ts +6 -0
  43. package/dist/server/counterfact-types/media-type.ts +6 -0
  44. package/dist/server/counterfact-types/omit-all.ts +11 -0
  45. package/dist/server/counterfact-types/omit-value-when-never.ts +11 -0
  46. package/dist/server/counterfact-types/open-api-content.ts +8 -0
  47. package/dist/server/counterfact-types/open-api-operation.ts +36 -0
  48. package/dist/server/counterfact-types/open-api-parameters.ts +16 -0
  49. package/dist/server/counterfact-types/open-api-response.ts +22 -0
  50. package/dist/server/counterfact-types/random-function.ts +9 -0
  51. package/dist/server/counterfact-types/response-builder-factory.ts +16 -0
  52. package/dist/server/counterfact-types/response-builder.ts +31 -0
  53. package/dist/server/counterfact-types/wide-operation-argument.ts +17 -0
  54. package/dist/server/counterfact-types/wide-response-builder.ts +26 -0
  55. package/dist/server/create-koa-app.js +1 -20
  56. package/dist/server/determine-module-kind.js +1 -1
  57. package/dist/server/dispatcher.js +39 -15
  58. package/dist/server/file-discovery.js +34 -0
  59. package/dist/server/json-to-xml.js +1 -1
  60. package/dist/server/koa-middleware.js +7 -1
  61. package/dist/server/load-openapi-document.js +13 -0
  62. package/dist/server/middleware-detector.js +8 -0
  63. package/dist/server/module-dependency-graph.js +4 -1
  64. package/dist/server/module-loader.js +81 -33
  65. package/dist/server/module-tree.js +26 -23
  66. package/dist/server/openapi-middleware.js +2 -2
  67. package/dist/server/openapi-watcher.js +35 -0
  68. package/dist/server/registry.js +2 -2
  69. package/dist/server/request-validator.js +57 -0
  70. package/dist/server/response-builder.js +3 -0
  71. package/dist/server/response-validator.js +58 -0
  72. package/dist/server/scenario-registry.js +29 -0
  73. package/dist/server/tools.js +2 -2
  74. package/dist/server/transpiler.js +13 -5
  75. package/dist/typescript-generator/coder.js +7 -2
  76. package/dist/typescript-generator/generate.js +155 -0
  77. package/dist/typescript-generator/jsdoc.js +45 -0
  78. package/dist/typescript-generator/operation-coder.js +1 -1
  79. package/dist/typescript-generator/operation-type-coder.js +5 -49
  80. package/dist/typescript-generator/parameters-type-coder.js +5 -1
  81. package/dist/typescript-generator/prune.js +2 -1
  82. package/dist/typescript-generator/read-only-comments.js +1 -1
  83. package/dist/typescript-generator/requirement.js +8 -1
  84. package/dist/typescript-generator/reserved-words.js +50 -0
  85. package/dist/typescript-generator/schema-type-coder.js +7 -1
  86. package/dist/typescript-generator/script.js +5 -3
  87. package/dist/typescript-generator/specification.js +7 -1
  88. package/dist/util/load-config-file.js +44 -0
  89. package/dist/util/runtime-can-execute-erasable-ts.js +22 -0
  90. package/package.json +12 -12
  91. package/dist/client/README.md +0 -14
  92. package/dist/client/index.html.hbs +0 -244
  93. package/dist/client/rapi-doc.html.hbs +0 -36
  94. package/dist/server/page-middleware.js +0 -23
package/README.md CHANGED
@@ -4,226 +4,189 @@
4
4
 
5
5
  <br>
6
6
 
7
- ![MIT License](https://img.shields.io/badge/license-MIT-blue) [![TypeScript](./typescript-badge.png)](https://github.com/ellerbrock/typescript-badges/) [![Coverage Status](https://coveralls.io/repos/github/pmcelhaney/counterfact/badge.svg)](https://coveralls.io/github/pmcelhaney/counterfact)
7
+ **Your backend isn't ready. Your frontend can't wait.**
8
8
 
9
- </div>
9
+ **Counterfact turns your OpenAPI spec into a live, stateful API you can program in TypeScript.**
10
10
 
11
- ---
11
+ <br>
12
12
 
13
- **Counterfact instantly turns an [OpenAPI/Swagge](https://www.openapis.org) spec into a live, working API you can run locally.**
13
+ ![MIT License](https://img.shields.io/badge/license-MIT-blue) [![TypeScript](./typescript-badge.png)](https://github.com/ellerbrock/typescript-badges/) [![Coverage Status](https://coveralls.io/repos/github/pmcelhaney/counterfact/badge.svg)](https://coveralls.io/github/pmcelhaney/counterfact)
14
14
 
15
- Instead of waiting for a backendβ€”or wiring up brittle mocksβ€”it generates a server where every endpoint is backed by TypeScript code. Responses are valid by default, but fully customizable, and the system is stateful, interactive, and hot-reloading.
15
+ </div>
16
16
 
17
- It’s not just a mock server.
17
+ This is a five-minute walkthrough. By the end, you’ll have a **stateful, type-safe, hot-reloading API simulator** running locallyβ€”and you’ll understand why it’s different from traditional mock servers.
18
18
 
19
- It’s a controllable API environment you can shape in real time.
19
+ Built for frontend developers, test engineers, and AI agents that need a predictable API to work against.
20
20
 
21
- > Built by Patrick McElhaney Β· Currently available for the right opportunity β†’ https://patrickmcelhaney.org
22
21
 
23
- ---
24
22
 
25
- ## Quick Start
23
+ ## Minute 1 β€” Start the server
26
24
 
27
25
  ```sh
28
26
  npx counterfact@latest https://petstore3.swagger.io/api/v3/openapi.json api
29
27
  ```
30
28
 
31
- That's it. Counterfact reads your OpenAPI spec, generates TypeScript route files in `api/`, and starts a mock server β€” all in one command. Point it at your own spec instead of the Petstore whenever you're ready.
32
-
33
- > **Requires Node β‰₯ 17.0.0**
29
+ > **Requires Node β‰₯ 22.0.0**
34
30
 
35
- ---
31
+ That’s it.
36
32
 
37
- ## Features
33
+ Counterfact reads your spec, generates a TypeScript handler for every endpoint, and starts a server at `http://localhost:3100`.
38
34
 
39
- - ⚑ **Zero config** β€” one command to generate and start a simulated api
40
- - πŸ”’ **Type-safe by default** β€” route handlers are typed directly from your OpenAPI spec
41
- - πŸ”„ **Hot reload** β€” edit route files while the server is running; state is preserved
42
- - 🧠 **State management** β€” POST data and GET it back; share state across routes with context objects
43
- - πŸ–₯ **Live REPL** β€” inspect and modify server state from your terminal without touching files
44
- - πŸ”€ **Hybrid proxy** β€” route some paths to the real API while mocking others
45
- - 🎲 **Smart random data** β€” uses OpenAPI examples and schema metadata to generate realistic responses
46
- - πŸ“– **Built-in Swagger UI** β€” browse and test your mock API in a browser automatically
47
- - πŸ”Œ **Middleware support** β€” add custom middleware with `_.middleware.ts` files
35
+ Open `http://localhost:3100/counterfact/swagger/`.
48
36
 
49
- ---
37
+ Every endpoint is already live, returning random, schema-valid responses. No code written yet.
50
38
 
51
- ## How It Works
52
39
 
53
- 1. **Generate** β€” Counterfact reads your OpenAPI spec and creates a `routes/` directory with a `.ts` file for each path, plus a `types/` directory with fully typed request/response interfaces.
54
- 2. **Customize** β€” Edit the route files to return exactly the data your frontend needs. The full power of TypeScript is at your disposal.
55
- 3. **Run** β€” The server hot-reloads on every save. No restart, no lost state.
56
40
 
57
- ---
41
+ ## Minute 2 β€” Make a route return real data
58
42
 
59
- ## Examples
60
-
61
- ### Zero effort: random responses out of the box
62
-
63
- Generated route files return random, schema-valid responses immediately β€” no editing required.
43
+ Open the generated file for `GET /pet/{petId}`:
64
44
 
65
45
  ```ts
66
- // mock-api/routes/store/order/{orderID}.ts
67
- import type { HTTP_GET } from "../../../types/paths/store/order/{orderId}.types.js";
46
+ import type { HTTP_GET } from "../../types/paths/pet/{petId}.types.js";
68
47
 
69
- export const GET: HTTP_GET = ($) => {
70
- return $.response[200].random();
71
- };
48
+ export const GET: HTTP_GET = ($) => $.response[200].random();
72
49
  ```
73
50
 
74
- ### Typed custom responses
75
-
76
- Replace `.random()` with `.json()` to return specific data. TypeScript (via your IDE's autocomplete) guides you to a valid response.
51
+ Replace `.random()` with your own logic:
77
52
 
78
53
  ```ts
79
- import type { HTTP_GET } from "../../../types/paths/store/order/{orderId}.types.js";
80
- import type { HTTP_DELETE } from "../../../types/paths/store/order/{orderId}.types.js";
81
-
82
54
  export const GET: HTTP_GET = ($) => {
83
- const orders: Record<number, Order> = {
84
- 1: { petId: 100, status: "placed" },
85
- 2: { petId: 999, status: "approved" },
86
- 3: { petId: 1234, status: "delivered" },
87
- };
88
-
89
- const order = orders[$.path.orderID];
90
- if (order === undefined) return $.response[404];
91
- return $.response[200].json(order);
92
- };
93
-
94
- export const DELETE: HTTP_DELETE = ($) => {
95
- return $.response[200];
55
+ if ($.path.petId === 99) {
56
+ return $.response[404].text("Pet not found");
57
+ }
58
+ return $.response[200].json({
59
+ id: $.path.petId,
60
+ name: "Fluffy",
61
+ status: "available",
62
+ photoUrls: []
63
+ });
96
64
  };
97
65
  ```
98
66
 
99
- ### Returning named examples
67
+ Save the file. The server reloads instantlyβ€”no restart, no lost state.
100
68
 
101
- If your OpenAPI spec defines named examples, use `.example(name)` to return a specific one. The name is autocompleted and type-checked from your spec:
69
+ TypeScript enforces the contract. If your response doesn’t match the spec, you’ll know before you make the request.
102
70
 
103
- ```ts
104
- export const GET: HTTP_GET = ($) => {
105
- return $.response[200].example("successResponse");
106
- };
107
- ```
71
+ ## Minute 3 β€” Add state that survives across requests
108
72
 
109
- ### State management with plain old objects
73
+ Real APIs have memory. Yours should too.
110
74
 
111
- Use a `_.context.ts` file to share in-memory state across routes. POST data and GET it back, just like a real API.
75
+ Create `api/routes/_.context.ts`:
112
76
 
113
77
  ```ts
114
- // mock-api/routes/_.context.ts
78
+ import type { Pet } from "../types/components/pet.types.js";
79
+
115
80
  export class Context {
116
- pets: Pet[] = [];
81
+ private pets = new Map<number, Pet>();
82
+ private nextId = 1;
117
83
 
118
- addPet(pet: Pet) {
119
- const id = this.pets.length;
120
- this.pets.push({ ...pet, id });
121
- return this.pets[id];
84
+ add(data: Omit<Pet, "id">): Pet {
85
+ const pet = { ...data, id: this.nextId++ };
86
+ this.pets.set(pet.id, pet);
87
+ return pet;
122
88
  }
123
89
 
124
- getPetById(id: number) {
125
- return this.pets[id];
126
- }
90
+ get(id: number): Pet | undefined { return this.pets.get(id); }
91
+ list(): Pet[] { return [...this.pets.values()]; }
92
+ remove(id: number): void { this.pets.delete(id); }
127
93
  }
128
94
  ```
129
95
 
130
- ```ts
131
- // mock-api/routes/pet.ts
132
- export const POST: HTTP_POST = ($) => {
133
- return $.response[200].json($.context.addPet($.body));
134
- };
96
+ Use it in your routes:
135
97
 
136
- // mock-api/routes/pet/{petId}.ts
137
- export const GET: HTTP_GET = ($) => {
138
- const pet = $.context.getPetById($.path.petId);
139
- if (!pet) return $.response[404].text(`Pet ${$.path.petId} not found.`);
140
- return $.response[200].json(pet);
141
- };
98
+ ```ts
99
+ export const GET: HTTP_GET = ($) => $.response[200].json($.context.list());
100
+ export const POST: HTTP_POST = ($) => $.response[200].json($.context.add($.body));
142
101
  ```
143
102
 
144
- You can also interact with the context object using a REPL. It's like DevTools on the server side. (See "Live REPL" below.)
103
+ Now your API behaves like a real system:
104
+ - POST creates data
105
+ - GET returns it
106
+ - DELETE removes it
107
+
108
+ State survives hot reloads. Restarting resets everythingβ€”perfect for clean test runs.
145
109
 
146
- ---
147
110
 
148
- ## Key Capabilities
149
111
 
150
- ### πŸ”„ Hot Reload
112
+ ## Minute 4 β€” Control the system at runtime (REPL)
151
113
 
152
- Save a route file and the server picks it up instantly β€” no restart, no lost state. Your in-memory context survives every reload.
114
+ This is where Counterfact becomes more than a mock.
153
115
 
154
- ### πŸ–₯ Live REPL
116
+ The built-in REPL lets you inspect and control the system while it’s running.
155
117
 
156
- The REPL gives you a JavaScript prompt connected directly to your running server. Inspect state, trigger edge cases, or adjust proxy settings without touching a file.
118
+ Seed data:
157
119
 
158
120
  ```
159
- ⬣> context.pets.length
160
- 3
161
- ⬣> context.addPet({ name: "Fluffy", photoUrls: [] })
162
- ⬣> client.get("/pet/3")
163
- ⬣> .proxy on /payments # forward /payments to the real API
164
- ⬣> .proxy off # stop all proxying
121
+ ⬣> context.add({ name: "Fluffy", status: "available", photoUrls: [] })
122
+ ⬣> context.add({ name: "Rex", status: "pending", photoUrls: [] })
165
123
  ```
166
124
 
167
- ### πŸ”€ Hybrid Proxy
125
+ Make requests:
168
126
 
169
- Mock the paths that aren't ready yet while forwarding everything else to the real backend. See [Proxying](./docs/usage.md#proxy-peek-a-boo-) for details.
127
+ ```
128
+ ⬣> client.get("/pet/1")
129
+ ```
170
130
 
171
- ```sh
172
- npx counterfact@latest openapi.yaml mock-api --proxy-url https://api.example.com
131
+ Simulate failures instantly:
132
+
133
+ ```
134
+ ⬣> context.rateLimitExceeded = true
135
+ ⬣> client.get("/pet/1")
136
+ { status: 429, body: "Too Many Requests" }
173
137
  ```
174
138
 
175
- ### πŸ”’ Type Safety
139
+ No HTTP scripts. No restarts. Just direct control.
176
140
 
177
- Every route handler is typed to match your OpenAPI spec. When the spec changes, regenerating the types surfaces any mismatches at compile time β€” before they become bugs.
178
141
 
179
- ```ts
180
- export const GET: HTTP_GET = ($) => {
181
- return $.response[200]
182
- .header("x-request-id", $.headers["x-request-id"])
183
- .json({
184
- id: $.path.userId,
185
- });
186
- };
187
- ```
188
142
 
189
- ---
143
+ ## Minute 5 β€” Proxy to the real backend
144
+
145
+ When parts of your backend are ready, forward them through.
190
146
 
191
- ## CLI Reference
147
+ Everything else stays simulated.
192
148
 
193
149
  ```sh
194
- npx counterfact@latest [openapi.yaml] [destination] [options]
150
+ npx counterfact@latest openapi.yaml api --proxy-url https://api.example.com
195
151
  ```
196
152
 
197
- | Option | Description |
198
- | ------------------- | ------------------------------------------- |
199
- | `--port <number>` | Server port (default: `3100`) |
200
- | `-o, --open` | Open browser automatically |
201
- | `-g, --generate` | Generate route and type files |
202
- | `-w, --watch` | Generate and watch for spec changes |
203
- | `-s, --serve` | Start the mock server |
204
- | `-r, --repl` | Start the interactive REPL |
205
- | `--spec <path>` | Path or URL to the OpenAPI document |
206
- | `--proxy-url <url>` | Forward all requests to this URL by default |
207
- | `--prefix <path>` | Base path prefix (e.g. `/api/v1`) |
153
+ Toggle paths live:
154
+
155
+ ```
156
+ ⬣> .proxy on /payments
157
+ ⬣> .proxy on /auth
158
+ ⬣> .proxy off
159
+ ```
208
160
 
209
- Run `npx counterfact@latest --help` for the full list of options.
210
161
 
211
- ---
212
162
 
213
- ## About the Author
163
+ ## What you just built
214
164
 
215
- Counterfact came out of a pattern I kept seeing: teams are slowed down more by coordination than by code.
165
+ In five minutes, you turned a static spec into a working system:
216
166
 
217
- I’ve spent 25+ years building software and improving how engineering organizations operate across large enterprises, regulated industries, and complex systems. Most of that time, the real constraint wasn’t technologyβ€”it was dependency and coordination.
167
+ - **Schema-valid responses** from the moment it starts
168
+ - **Type-safe handlers** generated from your spec
169
+ - **Shared state** across all routes
170
+ - **Hot reloading** without losing that state
171
+ - A **live control surface (REPL)** for runtime behavior
172
+ - **Selective proxying** to real services
218
173
 
219
- Counterfact is one way of removing that friction.
220
174
 
221
- I’m currently available β€” not for long.
222
175
 
223
- β†’ https://patrickmcelhaney.org
176
+ ## Go deeper
177
+
178
+ | | |
179
+ |---|---|
180
+ | [Getting started](./docs/getting-started.md) | Detailed walkthrough with state, REPL, and proxy |
181
+ | [Usage](./docs/usage.md) | Feature index: routes, context, REPL, proxy, middleware, and more |
182
+ | [Patterns](./docs/patterns/index.md) | Failures, latency, AI sandboxes, integration tests |
183
+ | [Reference](./docs/reference.md) | `$` API, CLI flags, architecture |
184
+ | [How it compares](./docs/comparison.md) | json-server, WireMock, Prism, Microcks, MSW |
185
+ | [FAQ](./docs/faq.md) | State, types, regeneration |
186
+ | [Petstore example](https://github.com/counterfact/example-petstore) | Full working example |
224
187
 
225
188
  <div align="center" markdown="1">
226
189
 
227
- [Documentation](./docs/usage.md) | [Changelog](./CHANGELOG.md) | [Contributing](./CONTRIBUTING.md)
190
+ [Changelog](./CHANGELOG.md) Β· [Contributing](./CONTRIBUTING.md)
228
191
 
229
- </div>
192
+ </div>
package/bin/README.md CHANGED
@@ -18,11 +18,13 @@ npx counterfact@latest openapi.yaml ./api [options]
18
18
  β”‚ counterfact.js β”‚
19
19
  β”‚ β”‚
20
20
  β”‚ 1. Parse args (Commander) β”‚
21
- β”‚ 2. Resolve paths β”‚
22
- β”‚ 3. Build Config object β”‚
23
- β”‚ 4. Run migrations if β”‚
21
+ β”‚ 2. Load counterfact.yaml β”‚
22
+ β”‚ 3. Merge config + args β”‚
23
+ β”‚ 4. Resolve paths β”‚
24
+ β”‚ 5. Build Config object β”‚
25
+ β”‚ 6. Run migrations if β”‚
24
26
  β”‚ old layout detected β”‚
25
- β”‚ 5. Call start(config) β”‚
27
+ β”‚ 7. Call start(config) β”‚
26
28
  β”‚ from src/app.ts β”‚
27
29
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
28
30
  ```
@@ -41,5 +43,24 @@ npx counterfact@latest openapi.yaml ./api [options]
41
43
  | `--proxy-url <url>` | Forward all unmatched requests to this upstream URL |
42
44
  | `--prefix <path>` | Base path prefix for all routes (e.g. `/api/v1`) |
43
45
  | `--no-update-check` | Disable the npm update check on startup |
46
+ | `--no-validate-request` | Disable request validation against the OpenAPI spec |
47
+ | `--config <path>` | Path to a `counterfact.yaml` config file (default: `counterfact.yaml` in the current directory) |
44
48
 
45
49
  Run `npx counterfact@latest --help` to see the full option list.
50
+
51
+ ### Config File
52
+
53
+ Any CLI option can also be specified in a `counterfact.yaml` file in the current working directory. Command-line options always take precedence.
54
+
55
+ ```yaml
56
+ # counterfact.yaml
57
+ spec: ./openapi.yaml
58
+ port: 8080
59
+ serve: true
60
+ repl: true
61
+ watch: true
62
+ proxy-url: https://api.example.com
63
+ prefix: /api/v1
64
+ ```
65
+
66
+ Use `--config <path>` to load a config file from a non-default location.