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.
- package/README.md +103 -140
- package/bin/README.md +25 -4
- package/bin/counterfact.js +208 -24
- package/bin/register-ts-loader.mjs +17 -0
- package/bin/ts-loader.mjs +31 -0
- package/dist/app.js +31 -21
- package/dist/counterfact-types/cookie-options.js +1 -0
- package/dist/counterfact-types/counterfact-response.js +7 -0
- package/dist/counterfact-types/example-names.js +1 -0
- package/dist/counterfact-types/example.js +1 -0
- package/dist/counterfact-types/generic-response-builder.js +1 -0
- package/dist/counterfact-types/http-status-code.js +1 -0
- package/dist/counterfact-types/if-has-key.js +1 -0
- package/dist/counterfact-types/index.js +0 -1
- package/dist/counterfact-types/maybe-promise.js +1 -0
- package/dist/counterfact-types/media-type.js +1 -0
- package/dist/counterfact-types/omit-all.js +1 -0
- package/dist/counterfact-types/omit-value-when-never.js +1 -0
- package/dist/counterfact-types/open-api-content.js +1 -0
- package/dist/counterfact-types/open-api-operation.js +1 -0
- package/dist/counterfact-types/open-api-parameters.js +1 -0
- package/dist/counterfact-types/open-api-response.js +1 -0
- package/dist/counterfact-types/random-function.js +1 -0
- package/dist/counterfact-types/response-builder-factory.js +1 -0
- package/dist/counterfact-types/response-builder.js +1 -0
- package/dist/counterfact-types/wide-operation-argument.js +1 -0
- package/dist/counterfact-types/wide-response-builder.js +1 -0
- package/dist/migrate/update-route-types.js +30 -10
- package/dist/repl/raw-http-client.js +14 -14
- package/dist/repl/repl.js +119 -4
- package/dist/repl/route-builder.js +270 -0
- package/dist/server/config.js +1 -1
- package/dist/server/context-registry.js +44 -4
- package/dist/server/counterfact-types/cookie-options.ts +14 -0
- package/dist/server/counterfact-types/counterfact-response.ts +15 -0
- package/dist/server/counterfact-types/example-names.ts +13 -0
- package/dist/server/counterfact-types/example.ts +10 -0
- package/dist/server/counterfact-types/generic-response-builder.ts +164 -0
- package/dist/server/counterfact-types/http-status-code.ts +62 -0
- package/dist/server/counterfact-types/if-has-key.ts +19 -0
- package/dist/server/counterfact-types/index.ts +20 -328
- package/dist/server/counterfact-types/maybe-promise.ts +6 -0
- package/dist/server/counterfact-types/media-type.ts +6 -0
- package/dist/server/counterfact-types/omit-all.ts +11 -0
- package/dist/server/counterfact-types/omit-value-when-never.ts +11 -0
- package/dist/server/counterfact-types/open-api-content.ts +8 -0
- package/dist/server/counterfact-types/open-api-operation.ts +36 -0
- package/dist/server/counterfact-types/open-api-parameters.ts +16 -0
- package/dist/server/counterfact-types/open-api-response.ts +22 -0
- package/dist/server/counterfact-types/random-function.ts +9 -0
- package/dist/server/counterfact-types/response-builder-factory.ts +16 -0
- package/dist/server/counterfact-types/response-builder.ts +31 -0
- package/dist/server/counterfact-types/wide-operation-argument.ts +17 -0
- package/dist/server/counterfact-types/wide-response-builder.ts +26 -0
- package/dist/server/create-koa-app.js +1 -20
- package/dist/server/determine-module-kind.js +1 -1
- package/dist/server/dispatcher.js +39 -15
- package/dist/server/file-discovery.js +34 -0
- package/dist/server/json-to-xml.js +1 -1
- package/dist/server/koa-middleware.js +7 -1
- package/dist/server/load-openapi-document.js +13 -0
- package/dist/server/middleware-detector.js +8 -0
- package/dist/server/module-dependency-graph.js +4 -1
- package/dist/server/module-loader.js +81 -33
- package/dist/server/module-tree.js +26 -23
- package/dist/server/openapi-middleware.js +2 -2
- package/dist/server/openapi-watcher.js +35 -0
- package/dist/server/registry.js +2 -2
- package/dist/server/request-validator.js +57 -0
- package/dist/server/response-builder.js +3 -0
- package/dist/server/response-validator.js +58 -0
- package/dist/server/scenario-registry.js +29 -0
- package/dist/server/tools.js +2 -2
- package/dist/server/transpiler.js +13 -5
- package/dist/typescript-generator/coder.js +7 -2
- package/dist/typescript-generator/generate.js +155 -0
- package/dist/typescript-generator/jsdoc.js +45 -0
- package/dist/typescript-generator/operation-coder.js +1 -1
- package/dist/typescript-generator/operation-type-coder.js +5 -49
- package/dist/typescript-generator/parameters-type-coder.js +5 -1
- package/dist/typescript-generator/prune.js +2 -1
- package/dist/typescript-generator/read-only-comments.js +1 -1
- package/dist/typescript-generator/requirement.js +8 -1
- package/dist/typescript-generator/reserved-words.js +50 -0
- package/dist/typescript-generator/schema-type-coder.js +7 -1
- package/dist/typescript-generator/script.js +5 -3
- package/dist/typescript-generator/specification.js +7 -1
- package/dist/util/load-config-file.js +44 -0
- package/dist/util/runtime-can-execute-erasable-ts.js +22 -0
- package/package.json +12 -12
- package/dist/client/README.md +0 -14
- package/dist/client/index.html.hbs +0 -244
- package/dist/client/rapi-doc.html.hbs +0 -36
- package/dist/server/page-middleware.js +0 -23
package/README.md
CHANGED
|
@@ -4,226 +4,189 @@
|
|
|
4
4
|
|
|
5
5
|
<br>
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Your backend isn't ready. Your frontend can't wait.**
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
**Counterfact turns your OpenAPI spec into a live, stateful API you can program in TypeScript.**
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
<br>
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
 [](https://github.com/ellerbrock/typescript-badges/) [](https://coveralls.io/github/pmcelhaney/counterfact)
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
</div>
|
|
16
16
|
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
> **Requires Node β₯ 17.0.0**
|
|
29
|
+
> **Requires Node β₯ 22.0.0**
|
|
34
30
|
|
|
35
|
-
|
|
31
|
+
Thatβs it.
|
|
36
32
|
|
|
37
|
-
|
|
33
|
+
Counterfact reads your spec, generates a TypeScript handler for every endpoint, and starts a server at `http://localhost:3100`.
|
|
38
34
|
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
67
|
+
Save the file. The server reloads instantlyβno restart, no lost state.
|
|
100
68
|
|
|
101
|
-
If your
|
|
69
|
+
TypeScript enforces the contract. If your response doesnβt match the spec, youβll know before you make the request.
|
|
102
70
|
|
|
103
|
-
|
|
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
|
-
|
|
73
|
+
Real APIs have memory. Yours should too.
|
|
110
74
|
|
|
111
|
-
|
|
75
|
+
Create `api/routes/_.context.ts`:
|
|
112
76
|
|
|
113
77
|
```ts
|
|
114
|
-
|
|
78
|
+
import type { Pet } from "../types/components/pet.types.js";
|
|
79
|
+
|
|
115
80
|
export class Context {
|
|
116
|
-
pets
|
|
81
|
+
private pets = new Map<number, Pet>();
|
|
82
|
+
private nextId = 1;
|
|
117
83
|
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
this.pets.
|
|
121
|
-
return
|
|
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
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
|
|
137
|
-
export const GET: HTTP_GET = ($) =>
|
|
138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
112
|
+
## Minute 4 β Control the system at runtime (REPL)
|
|
151
113
|
|
|
152
|
-
|
|
114
|
+
This is where Counterfact becomes more than a mock.
|
|
153
115
|
|
|
154
|
-
|
|
116
|
+
The built-in REPL lets you inspect and control the system while itβs running.
|
|
155
117
|
|
|
156
|
-
|
|
118
|
+
Seed data:
|
|
157
119
|
|
|
158
120
|
```
|
|
159
|
-
⬣> context.
|
|
160
|
-
|
|
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
|
-
|
|
125
|
+
Make requests:
|
|
168
126
|
|
|
169
|
-
|
|
127
|
+
```
|
|
128
|
+
⬣> client.get("/pet/1")
|
|
129
|
+
```
|
|
170
130
|
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
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
|
-
|
|
147
|
+
Everything else stays simulated.
|
|
192
148
|
|
|
193
149
|
```sh
|
|
194
|
-
npx counterfact@latest
|
|
150
|
+
npx counterfact@latest openapi.yaml api --proxy-url https://api.example.com
|
|
195
151
|
```
|
|
196
152
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
##
|
|
163
|
+
## What you just built
|
|
214
164
|
|
|
215
|
-
|
|
165
|
+
In five minutes, you turned a static spec into a working system:
|
|
216
166
|
|
|
217
|
-
|
|
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
|
-
|
|
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
|
-
[
|
|
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.
|
|
22
|
-
β 3.
|
|
23
|
-
β 4.
|
|
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
|
-
β
|
|
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.
|