counterfact 2.2.0 → 2.2.1
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
CHANGED
|
@@ -96,6 +96,16 @@ export const DELETE: HTTP_DELETE = ($) => {
|
|
|
96
96
|
};
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
+
### Returning named examples
|
|
100
|
+
|
|
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:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
export const GET: HTTP_GET = ($) => {
|
|
105
|
+
return $.response[200].example("successResponse");
|
|
106
|
+
};
|
|
107
|
+
```
|
|
108
|
+
|
|
99
109
|
### State management with plain old objects
|
|
100
110
|
|
|
101
111
|
Use a `_.context.ts` file to share in-memory state across routes. POST data and GET it back, just like a real API.
|
package/bin/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# `bin/` — CLI Entry Point
|
|
2
|
+
|
|
3
|
+
This directory contains the executable script that is run when a developer invokes `npx counterfact` (or `counterfact` after a global install).
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
| File | Description |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `counterfact.js` | Parses command-line arguments with [Commander](https://github.com/tj/commander.js), validates inputs, and calls `counterfact()` from `src/app.ts` to start the server, code generator, file watcher, and/or REPL |
|
|
10
|
+
|
|
11
|
+
## How It Works
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
npx counterfact openapi.yaml ./api [options]
|
|
15
|
+
│
|
|
16
|
+
▼
|
|
17
|
+
┌────────────────────────────┐
|
|
18
|
+
│ counterfact.js │
|
|
19
|
+
│ │
|
|
20
|
+
│ 1. Parse args (Commander) │
|
|
21
|
+
│ 2. Resolve paths │
|
|
22
|
+
│ 3. Build Config object │
|
|
23
|
+
│ 4. Run migrations if │
|
|
24
|
+
│ old layout detected │
|
|
25
|
+
│ 5. Call start(config) │
|
|
26
|
+
│ from src/app.ts │
|
|
27
|
+
└────────────────────────────┘
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Key CLI Options
|
|
31
|
+
|
|
32
|
+
| Option | Description |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `--port <number>` | HTTP server port (default: `3100`) |
|
|
35
|
+
| `-o, --open` | Open the dashboard in a browser after startup |
|
|
36
|
+
| `-g, --generate` | Generate route and type files from the OpenAPI spec |
|
|
37
|
+
| `-w, --watch` | Re-generate whenever the spec changes |
|
|
38
|
+
| `-s, --serve` | Start the HTTP server |
|
|
39
|
+
| `-r, --repl` | Start the interactive REPL |
|
|
40
|
+
| `--proxy-url <url>` | Forward all unmatched requests to this upstream URL |
|
|
41
|
+
| `--prefix <path>` | Base path prefix for all routes (e.g. `/api/v1`) |
|
|
42
|
+
|
|
43
|
+
Run `npx counterfact --help` to see the full option list.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# `src/client/` — Built-in UI Templates
|
|
2
|
+
|
|
3
|
+
This directory contains [Handlebars](https://handlebarsjs.com/) (`.hbs`) templates that are rendered by `page-middleware.ts` to produce the browser-facing pages bundled with Counterfact.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
| File | Description |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `index.html.hbs` | Template for the Counterfact dashboard (`/counterfact/`); lists registered routes and shows server status |
|
|
10
|
+
| `rapi-doc.html.hbs` | Template for the interactive API documentation page (`/counterfact/swagger/`); embeds the [RapiDoc](https://rapidocweb.com/) viewer and adds VSCode "open file" links |
|
|
11
|
+
|
|
12
|
+
## How It Works
|
|
13
|
+
|
|
14
|
+
When a request arrives for `/counterfact/` or `/counterfact/swagger/`, `page-middleware.ts` compiles the appropriate template with runtime data (routes, port, base path, etc.) and sends the resulting HTML to the browser. No build step is required; templates are rendered on the fly.
|
|
@@ -37,6 +37,7 @@ type OmitValueWhenNever<Base> = Pick<
|
|
|
37
37
|
|
|
38
38
|
interface OpenApiResponse {
|
|
39
39
|
content: { [key: MediaType]: OpenApiContent };
|
|
40
|
+
examples?: { [key: string]: unknown };
|
|
40
41
|
headers: { [key: string]: OpenApiHeader };
|
|
41
42
|
requiredHeaders: string;
|
|
42
43
|
}
|
|
@@ -105,9 +106,16 @@ type RandomFunction<Response extends OpenApiResponse> = <
|
|
|
105
106
|
Header extends string & keyof Response["headers"],
|
|
106
107
|
>() => COUNTERFACT_RESPONSE;
|
|
107
108
|
|
|
109
|
+
type ExampleNames<Response extends OpenApiResponse> = Response extends {
|
|
110
|
+
examples: infer E;
|
|
111
|
+
}
|
|
112
|
+
? keyof E & string
|
|
113
|
+
: never;
|
|
114
|
+
|
|
108
115
|
interface ResponseBuilder {
|
|
109
116
|
[status: number | `${number} ${string}`]: ResponseBuilder;
|
|
110
117
|
content?: { body: unknown; type: string }[];
|
|
118
|
+
example: (name: string) => ResponseBuilder;
|
|
111
119
|
header: (name: string, value: string) => ResponseBuilder;
|
|
112
120
|
headers: { [name: string]: string };
|
|
113
121
|
html: (body: unknown) => ResponseBuilder;
|
|
@@ -143,6 +151,9 @@ export type GenericResponseBuilderInner<
|
|
|
143
151
|
random: [keyof Response["content"]] extends [never]
|
|
144
152
|
? never
|
|
145
153
|
: RandomFunction<Response>;
|
|
154
|
+
example: [ExampleNames<Response>] extends [never]
|
|
155
|
+
? never
|
|
156
|
+
: (name: ExampleNames<Response>) => COUNTERFACT_RESPONSE;
|
|
146
157
|
text: MaybeShortcut<["text/plain"], Response>;
|
|
147
158
|
xml: MaybeShortcut<["application/xml", "text/xml"], Response>;
|
|
148
159
|
}>;
|
|
@@ -252,6 +263,7 @@ interface OpenApiOperation {
|
|
|
252
263
|
}
|
|
253
264
|
|
|
254
265
|
interface WideResponseBuilder {
|
|
266
|
+
example: (name: string) => WideResponseBuilder;
|
|
255
267
|
header: (body: unknown) => WideResponseBuilder;
|
|
256
268
|
html: (body: unknown) => WideResponseBuilder;
|
|
257
269
|
json: (body: unknown) => WideResponseBuilder;
|
|
@@ -274,6 +286,7 @@ interface WideOperationArgument {
|
|
|
274
286
|
export type { COUNTERFACT_RESPONSE };
|
|
275
287
|
|
|
276
288
|
export type {
|
|
289
|
+
ExampleNames,
|
|
277
290
|
HttpStatusCode,
|
|
278
291
|
MaybePromise,
|
|
279
292
|
MediaType,
|
|
@@ -63,6 +63,24 @@ export function createResponseBuilder(operation, config) {
|
|
|
63
63
|
],
|
|
64
64
|
};
|
|
65
65
|
},
|
|
66
|
+
example(name) {
|
|
67
|
+
if (operation.produces) {
|
|
68
|
+
return unknownStatusCodeResponse(this.status);
|
|
69
|
+
}
|
|
70
|
+
const response = operation.responses[this.status ?? "default"] ??
|
|
71
|
+
operation.responses.default;
|
|
72
|
+
if (response?.content === undefined) {
|
|
73
|
+
return unknownStatusCodeResponse(this.status);
|
|
74
|
+
}
|
|
75
|
+
const { content } = response;
|
|
76
|
+
return {
|
|
77
|
+
...this,
|
|
78
|
+
content: Object.keys(content).map((type) => ({
|
|
79
|
+
body: convertToXmlIfNecessary(type, content[type]?.examples?.[name]?.value, content[type]?.schema),
|
|
80
|
+
type,
|
|
81
|
+
})),
|
|
82
|
+
};
|
|
83
|
+
},
|
|
66
84
|
random() {
|
|
67
85
|
if (config?.alwaysFakeOptionals) {
|
|
68
86
|
JSONSchemaFaker.option("alwaysFakeOptionals", true);
|
|
@@ -52,6 +52,25 @@ export class ResponseTypeCoder extends TypeCoder {
|
|
|
52
52
|
.map(({ name }) => `"${name}"`);
|
|
53
53
|
return requiredHeaders.length === 0 ? "never" : requiredHeaders.join(" | ");
|
|
54
54
|
}
|
|
55
|
+
buildExamplesObjectType(response) {
|
|
56
|
+
if (!response.has("content")) {
|
|
57
|
+
return "{}";
|
|
58
|
+
}
|
|
59
|
+
const exampleNames = [];
|
|
60
|
+
response.get("content").forEach((content) => {
|
|
61
|
+
if (content.has("examples")) {
|
|
62
|
+
content.get("examples").forEach((_, name) => {
|
|
63
|
+
if (!exampleNames.includes(name)) {
|
|
64
|
+
exampleNames.push(name);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
if (exampleNames.length === 0) {
|
|
70
|
+
return "{}";
|
|
71
|
+
}
|
|
72
|
+
return printObject(exampleNames.map((name) => [name, "unknown"]));
|
|
73
|
+
}
|
|
55
74
|
modulePath() {
|
|
56
75
|
return `types/${this.requirement.data.$ref}.ts`;
|
|
57
76
|
}
|
|
@@ -60,6 +79,7 @@ export class ResponseTypeCoder extends TypeCoder {
|
|
|
60
79
|
headers: ${this.printHeaders(script, this.requirement)};
|
|
61
80
|
requiredHeaders: ${this.printRequiredHeaders(this.requirement)};
|
|
62
81
|
content: ${this.printContentObjectType(script, this.requirement)};
|
|
82
|
+
examples: ${this.buildExamplesObjectType(this.requirement)};
|
|
63
83
|
}`;
|
|
64
84
|
}
|
|
65
85
|
}
|
package/package.json
CHANGED