Skip to content

Commit e0414c7

Browse files
authored
1 parent 8422a82 commit e0414c7

37 files changed

+1083
-0
lines changed

.changeset/add-internal-jsdocs.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"counterfact": patch
3+
---
4+
5+
Add JSDoc comments throughout the codebase, covering all major classes, functions, and interfaces in `src/server/`, `src/typescript-generator/`, `src/repl/`, and `src/util/`.

src/app.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ export type MockRequest = DispatcherRequest & { rawPath: string };
3939

4040
const mswHandlers: MswHandlerMap = {};
4141

42+
/**
43+
* Dispatches a single MSW (Mock Service Worker) intercepted request to the
44+
* matching Counterfact route handler registered via {@link createMswHandlers}.
45+
*
46+
* @param request - The intercepted request, including the HTTP method, path,
47+
* headers, query, body, and a `rawPath` that preserves the original URL
48+
* before base-path stripping.
49+
* @returns The response produced by the matching handler, or a 404 object when
50+
* no handler has been registered for the given method and path.
51+
*/
4252
export async function handleMswRequest(request: MockRequest) {
4353
const { method, rawPath } = request;
4454
const handler = mswHandlers[`${method}:${rawPath}`];
@@ -49,6 +59,18 @@ export async function handleMswRequest(request: MockRequest) {
4959
return { error: `No handler found for ${method} ${rawPath}`, status: 404 };
5060
}
5161

62+
/**
63+
* Loads an OpenAPI document, registers all routes from it as MSW handlers, and
64+
* returns the list of registered routes so callers (e.g. Vitest Browser mode)
65+
* can mount them on their own request-interception layer.
66+
*
67+
* @param config - Counterfact configuration; `openApiPath` and `basePath` are
68+
* the most important fields for this function.
69+
* @param ModuleLoaderClass - Injectable module-loader constructor, primarily
70+
* used in tests to substitute a test-friendly implementation.
71+
* @returns An array of `{ method, path }` objects describing every registered
72+
* MSW handler.
73+
*/
5274
export async function createMswHandlers(
5375
config: Config,
5476
ModuleLoaderClass = ModuleLoader,
@@ -99,6 +121,22 @@ export async function createMswHandlers(
99121
return handlers;
100122
}
101123

124+
/**
125+
* Creates and configures a full Counterfact server instance.
126+
*
127+
* Sets up the route registry, context registry, scenario registry, code
128+
* generator, transpiler, module loader, Koa application, and OpenAPI watcher.
129+
* The returned object exposes handles for starting the server, stopping it, and
130+
* launching the interactive REPL.
131+
*
132+
* @param config - Runtime configuration (port, paths, feature flags, etc.).
133+
* @returns An object containing the configured sub-systems and two entry-point
134+
* functions:
135+
* - `start(options)` — generates/watches code and optionally starts the HTTP
136+
* server; returns a `stop()` handle.
137+
* - `startRepl()` — launches the interactive Node.js REPL connected to the
138+
* live server state.
139+
*/
102140
export async function counterfact(config: Config) {
103141
const modulesPath = config.basePath;
104142

src/repl/raw-http-client.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ function stringifyBody(body: string | object) {
6262
return JSON.stringify(body);
6363
}
6464

65+
/**
66+
* A minimal HTTP/1.1 client that communicates over a raw TCP socket.
67+
*
68+
* Used in the Counterfact REPL (`client.*`) to send requests to the local mock
69+
* server and pretty-print the request and response to `stdout` with ANSI
70+
* colours.
71+
*
72+
* Unlike `fetch` or Axios, `RawHttpClient` does not buffer or parse the
73+
* response — the raw HTTP response string is returned from every method.
74+
*/
6575
export class RawHttpClient {
6676
host: string;
6777
port: number;
@@ -72,38 +82,47 @@ export class RawHttpClient {
7282
this.port = port;
7383
}
7484

85+
/** Sends a `GET` request and returns the raw HTTP response string. */
7586
get(path: string, headers = {}) {
7687
return this.#send("GET", path, "", headers);
7788
}
7889

90+
/** Sends a `HEAD` request and returns the raw HTTP response string. */
7991
head(path: string, headers = {}) {
8092
return this.#send("HEAD", path, "", headers);
8193
}
8294

95+
/** Sends a `POST` request with `body` and returns the raw HTTP response string. */
8396
post(path: string, body: string | object = "", headers = {}) {
8497
return this.#send("POST", path, body, headers);
8598
}
8699

100+
/** Sends a `PUT` request with `body` and returns the raw HTTP response string. */
87101
put(path: string, body: string | object = "", headers = {}) {
88102
return this.#send("PUT", path, body, headers);
89103
}
90104

105+
/** Sends a `DELETE` request and returns the raw HTTP response string. */
91106
delete(path: string, headers = {}) {
92107
return this.#send("DELETE", path, "", headers);
93108
}
94109

110+
/** Sends a `CONNECT` request and returns the raw HTTP response string. */
95111
connect(path: string, headers = {}) {
96112
return this.#send("CONNECT", path, "", headers);
97113
}
98114

115+
/** Sends an `OPTIONS` request and returns the raw HTTP response string. */
99116
options(path: string, headers = {}) {
100117
return this.#send("OPTIONS", path, "", headers);
101118
}
102119

120+
/** Sends a `TRACE` request and returns the raw HTTP response string. */
103121
trace(path: string, headers = {}) {
104122
return this.#send("TRACE", path, "", headers);
105123
}
106124

125+
/** Sends a `PATCH` request with `body` and returns the raw HTTP response string. */
107126
patch(path: string, body: string | object = "", headers = {}) {
108127
return this.#send("PATCH", path, body, headers);
109128
}

src/repl/repl.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,25 @@ export function createCompleter(
121121
};
122122
}
123123

124+
/**
125+
* Launches the interactive Counterfact REPL.
126+
*
127+
* The REPL is a standard Node.js REPL augmented with:
128+
* - `context` / `loadContext(path)` globals wired to the {@link ContextRegistry}.
129+
* - `client` — a {@link RawHttpClient} pre-configured for `localhost`.
130+
* - `route(path)` — creates a {@link RouteBuilder} for the given path.
131+
* - `.counterfact` — help command.
132+
* - `.proxy` — proxy configuration command.
133+
* - `.scenario` — runs a named scenario function from the scenarios directory.
134+
*
135+
* @param contextRegistry - The live context registry.
136+
* @param registry - The route registry (used for tab completion).
137+
* @param config - Server configuration.
138+
* @param print - Output function; defaults to writing to `stdout`.
139+
* @param openApiDocument - Optional OpenAPI document for tab completion.
140+
* @param scenarioRegistry - Optional scenario registry for `.scenario` support.
141+
* @returns The configured Node.js REPL server instance.
142+
*/
124143
export function startRepl(
125144
contextRegistry: ContextRegistry,
126145
registry: Registry,

src/repl/route-builder.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ export interface MissingParams {
3333
query?: MissingParam[];
3434
}
3535

36+
/**
37+
* Immutable fluent builder for constructing and sending HTTP requests from the
38+
* Counterfact REPL.
39+
*
40+
* Each builder method returns a **new** `RouteBuilder` instance with the
41+
* updated field — the original is never mutated. When all required parameters
42+
* are set, call {@link send} to execute the request.
43+
*
44+
* ```ts
45+
* // Inside the REPL:
46+
* route("/pets/{petId}").method("get").path({ petId: 1 }).send();
47+
* ```
48+
*/
3649
export class RouteBuilder {
3750
public readonly routePath: string;
3851

@@ -116,22 +129,47 @@ export class RouteBuilder {
116129
});
117130
}
118131

132+
/**
133+
* Returns a new builder with the HTTP method set.
134+
*
135+
* @param method - HTTP method name (case-insensitive, e.g. `"get"`, `"POST"`).
136+
*/
119137
public method(method: string): RouteBuilder {
120138
return this.clone({ method: method.toUpperCase() });
121139
}
122140

141+
/**
142+
* Returns a new builder with additional path parameters merged in.
143+
*
144+
* @param params - Key/value map of path variable names to values.
145+
*/
123146
public path(params: Params): RouteBuilder {
124147
return this.clone({ pathParams: { ...this._pathParams, ...params } });
125148
}
126149

150+
/**
151+
* Returns a new builder with additional query parameters merged in.
152+
*
153+
* @param params - Key/value map of query parameter names to values.
154+
*/
127155
public query(params: Params): RouteBuilder {
128156
return this.clone({ queryParams: { ...this._queryParams, ...params } });
129157
}
130158

159+
/**
160+
* Returns a new builder with additional request headers merged in.
161+
*
162+
* @param params - Key/value map of header names to values.
163+
*/
131164
public headers(params: Params): RouteBuilder {
132165
return this.clone({ headerParams: { ...this._headerParams, ...params } });
133166
}
134167

168+
/**
169+
* Returns a new builder with the request body set.
170+
*
171+
* @param body - The request body (will be serialised to JSON or sent as-is).
172+
*/
135173
public body(body: unknown): RouteBuilder {
136174
return this.clone({ body });
137175
}
@@ -140,12 +178,21 @@ export class RouteBuilder {
140178
return this._operation;
141179
}
142180

181+
/**
182+
* Returns `true` when a method is set and no required parameters are
183+
* missing.
184+
*/
143185
public ready(): boolean {
144186
if (!this._method) return false;
145187

146188
return this.missing() === undefined;
147189
}
148190

191+
/**
192+
* Returns a {@link MissingParams} object describing all required parameters
193+
* that have not yet been set, or `undefined` when nothing is missing (or
194+
* when the operation has no parameters).
195+
*/
149196
public missing(): MissingParams | undefined {
150197
const operation = this.getOperation();
151198

@@ -177,6 +224,10 @@ export class RouteBuilder {
177224
return missingParams;
178225
}
179226

227+
/**
228+
* Returns a human-readable help string describing the operation, its
229+
* parameters, and the expected responses.
230+
*/
180231
public help(): string {
181232
const method = this._method ?? "[no method set]";
182233
const operation = this.getOperation();
@@ -260,6 +311,13 @@ export class RouteBuilder {
260311
return lines.join("\n");
261312
}
262313

314+
/**
315+
* Executes the HTTP request and returns the parsed response body.
316+
*
317+
* @throws When no HTTP method has been set.
318+
* @throws When required parameters are missing.
319+
* @throws When an unsupported HTTP method is used.
320+
*/
263321
public async send(): Promise<unknown> {
264322
if (!this._method) {
265323
throw new Error(
@@ -389,6 +447,16 @@ export class RouteBuilder {
389447
}
390448
}
391449

450+
/**
451+
* Creates a factory function that constructs a {@link RouteBuilder} for a
452+
* given route path, pre-configured with the server's host, port, and OpenAPI
453+
* document.
454+
*
455+
* @param port - The port the Counterfact server is listening on.
456+
* @param host - The server hostname (default `"localhost"`).
457+
* @param openApiDocument - Optional OpenAPI document for parameter introspection.
458+
* @returns A function `(routePath: string) => RouteBuilder`.
459+
*/
392460
export function createRouteFunction(
393461
port: number,
394462
host?: string,

src/server/config.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,50 @@
1+
/** Runtime configuration for a Counterfact server instance. */
12
export interface Config {
3+
/** Optional bearer token that protects the Admin API endpoints. */
24
adminApiToken?: string;
5+
/** When `true`, JSON Schema Faker generates values for all optional fields. */
36
alwaysFakeOptionals: boolean;
7+
/** Absolute path to the directory that contains generated route files. */
48
basePath: string;
9+
/** When `true`, transpile TypeScript route files to a `.cache/` directory. */
510
buildCache: boolean;
11+
/** Controls which artefacts are (re-)generated from the OpenAPI spec. */
612
generate: {
13+
/** Remove route files that no longer correspond to a spec path. */
714
prune?: boolean;
15+
/** Generate route handler stubs. */
816
routes: boolean;
17+
/** Generate TypeScript type files. */
918
types: boolean;
1019
};
20+
/** Path or URL to the OpenAPI document. Use `"_"` to skip spec loading. */
1121
openApiPath: string;
22+
/** TCP port the HTTP server listens on. */
1223
port: number;
24+
/**
25+
* Per-path proxy toggle map. `true` means requests to that path are
26+
* forwarded to `proxyUrl`; `false` means they are handled locally.
27+
*/
1328
proxyPaths: Map<string, boolean>;
29+
/** Base URL of the upstream server used when proxying is enabled. */
1430
proxyUrl: string;
31+
/** URL prefix that Counterfact intercepts (default `""`). */
1532
routePrefix: string;
33+
/** When `true`, mount the Admin API at `/_counterfact/api/`. */
1634
startAdminApi: boolean;
35+
/** When `true`, launch the interactive REPL after the server starts. */
1736
startRepl: boolean;
37+
/** When `true`, start the Koa HTTP server. */
1838
startServer: boolean;
39+
/** When `true`, validate incoming requests against the OpenAPI spec. */
1940
validateRequests: boolean;
41+
/** When `true`, validate outgoing responses against the OpenAPI spec. */
2042
validateResponses: boolean;
43+
/** Controls which artefacts are watched for live reload. */
2144
watch: {
45+
/** Re-generate route stubs when the OpenAPI spec changes. */
2246
routes: boolean;
47+
/** Re-generate type files when the OpenAPI spec changes. */
2348
types: boolean;
2449
};
2550
}

src/server/constants.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
/**
2+
* Default options passed to every chokidar watcher in Counterfact.
3+
*
4+
* - `ignoreInitial: true` — suppresses the initial `"add"` events emitted for
5+
* files already present when the watcher starts.
6+
* - `usePolling: true` on Windows — chokidar's native FSEvents are unreliable
7+
* on Windows; polling is more reliable there.
8+
*/
19
export const CHOKIDAR_OPTIONS = {
210
ignoreInitial: true,
311
usePolling: process.platform === "win32",

0 commit comments

Comments
 (0)