Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/refactor-docs-ia.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"counterfact": patch
---

Refactor docs information architecture: `docs/usage.md` is now a central hub page linking to individual feature pages under `docs/features/`, and pattern pages are consolidated under `docs/patterns/index.md`.
2 changes: 1 addition & 1 deletion .github/issue-proposals/docs-pattern-persistent-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ A `Persistent State` pattern document would describe:
- [ ] `--persist-state <path>` CLI flag (or equivalent) is implemented
- [ ] Context classes can opt in to persistence by implementing a documented interface
- [ ] `docs/patterns/persistent-state.md` is added following the established pattern format
- [ ] The new pattern is linked in `docs/usage-patterns.md`
- [ ] The new pattern is linked in `docs/patterns/index.md`
- [ ] The reference doc is updated to describe the new CLI flag and context interface
2 changes: 1 addition & 1 deletion .github/issue-proposals/docs-pattern-record-and-replay.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ A `Record and Replay` usage pattern document would describe:

- [ ] A `--record` (or equivalent) CLI flag is implemented that writes captured responses to handler files
- [ ] `docs/patterns/record-and-replay.md` is added following the established pattern format (Context, Problem, Solution, Example, Consequences, Related Patterns)
- [ ] The new pattern is linked in `docs/usage-patterns.md`
- [ ] The new pattern is linked in `docs/patterns/index.md`
- [ ] The reference doc is updated to describe the new CLI flag
2 changes: 1 addition & 1 deletion .github/issue-proposals/docs-pattern-webhook-simulation.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@ A `Webhook Simulation` pattern document would describe:

- [ ] An outbound-call mechanism (e.g., `$.webhook()` or `$.emit()`) is implemented and documented
- [ ] `docs/patterns/webhook-simulation.md` is added following the established pattern format
- [ ] The new pattern is linked in `docs/usage-patterns.md`
- [ ] The new pattern is linked in `docs/patterns/index.md`
- [ ] The reference doc is updated to describe the new API
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,8 @@ In five minutes, you turned a static spec into a working system:
| | |
|---|---|
| [Getting started](./docs/getting-started.md) | Detailed walkthrough with state, REPL, and proxy |
| [Usage guide](./docs/usage.md) | Routes, context, REPL, proxy, middleware, programmatic API |
| [Usage patterns](./docs/usage-patterns.md) | Failures, latency, AI sandboxes, integration tests |
| [Usage](./docs/usage.md) | Feature index: routes, context, REPL, proxy, middleware, and more |
| [Patterns](./docs/patterns/index.md) | Failures, latency, AI sandboxes, integration tests |
| [Reference](./docs/reference.md) | `$` API, CLI flags, architecture |
| [How it compares](./docs/comparison.md) | json-server, WireMock, Prism, Microcks, MSW |
| [FAQ](./docs/faq.md) | State, types, regeneration |
Expand Down
4 changes: 2 additions & 2 deletions docs/comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ npx counterfact@latest https://petstore3.swagger.io/api/v3/openapi.json api
## See also

- [Getting started](./getting-started.md)
- [Usage patterns](./usage-patterns.md)
- [Patterns](./patterns/index.md)
- [Reference](./reference.md)
- [FAQ](./faq.md)
- [Usage guide](./usage.md)
- [Usage](./usage.md)
42 changes: 0 additions & 42 deletions docs/context-change.md

This file was deleted.

60 changes: 0 additions & 60 deletions docs/faq-generated-code.md

This file was deleted.

5 changes: 2 additions & 3 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,7 @@ Wherever you tell it. The second argument to the CLI is the output directory. Us
## See also

- [Getting started](./getting-started.md)
- [Usage patterns](./usage-patterns.md)
- [Patterns](./patterns/index.md)
- [Reference](./reference.md)
- [How it compares](./comparison.md)
- [Generated code FAQ](./faq-generated-code.md)
- [Usage guide](./usage.md)
- [Usage](./usage.md)
16 changes: 16 additions & 0 deletions docs/features/generated-code.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Generated Code

Counterfact generates two directories from your OpenAPI document:

- 📂 **`types/`** — fully typed request/response interfaces, auto-regenerated whenever the OpenAPI document changes. Don't edit these by hand.
- 📂 **`routes/`** — one TypeScript file per API path. These are yours to edit. Out of the box each file returns a random, schema-valid response. You can leave them as-is or customize as much as you like.

See the [FAQ](../faq.md) for common questions about source control, editing, and regeneration.

No OpenAPI document? See [using Counterfact without OpenAPI](./without-openapi.md).

## See also

- [Routes](./routes.md) — writing route handlers, reading request data, building responses
- [State](./state.md) — sharing state across routes with context objects
- [Usage](../usage.md)
19 changes: 19 additions & 0 deletions docs/features/hot-reload.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Hot Reload 🔥

Save a file — any route or context file — and the running server picks it up immediately. No restart needed, and in-memory state is preserved across reloads.

This makes it fast to set up edge cases like:

- What does the UI do 8 clicks deep when the server returns a 500?
- What if there are zero results? What if there are 10,000?
- What if the server is slow?

Find the file corresponding to the route, change behavior by editing the TypeScript code, and continue testing.

Depending on the scenario, you may want to commit your changes to source control or throw them away.

## See also

- [State](./state.md) — in-memory state that survives reloads
- [REPL](./repl.md) — make changes without touching files at all
- [Usage](../usage.md)
20 changes: 20 additions & 0 deletions docs/features/middleware.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Middleware

Place a `_.middleware.ts` file in any `routes/` subdirectory to intercept requests and responses for that subtree. Middleware applies from the root down — a `_.middleware.ts` at the root runs for every request.

```ts
// routes/_.middleware.ts
export async function middleware($, respondTo) {
const response = await respondTo($);
return response.header("X-Custom-Header", "Custom Value");
}
```

`respondTo($)` passes the request to the next middleware layer or the route handler, and returns the response. You can modify `$` before calling `respondTo`, modify the response after, or both.

## See also

- [Patterns: Custom Middleware](../patterns/custom-middleware.md) — authentication, headers, and logging across route groups
- [Routes](./routes.md)
- [Reference](../reference.md)
- [Usage](../usage.md)
106 changes: 106 additions & 0 deletions docs/features/programmatic-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Programmatic API

Counterfact can be used as a library — for example, from [Playwright](https://playwright.dev/) or [Cypress](https://www.cypress.io/) tests. This lets you manipulate context state directly in test code without relying on special magic values in mock logic.

```ts
import { counterfact } from "counterfact";

const config = {
basePath: "./api", // directory containing your routes/
openApiPath: "./api.yaml", // optional; pass "_" to run without a spec
port: 8100,
alwaysFakeOptionals: false,
generate: { routes: false, types: false },
proxyPaths: new Map(),
proxyUrl: "",
routePrefix: "",
startAdminApi: false,
startRepl: false, // do not auto-start the REPL
startServer: true,
watch: { routes: false, types: false },
};

const { contextRegistry, start } = await counterfact(config);
const { stop } = await start(config);

// Get the root context — the object your routes see as $.context
const rootContext = contextRegistry.find("/");
```

Once you have `rootContext` you can read and write any state that your route handlers expose.

## Example: parameterised auth scenario with Playwright

Given this route handler:

```ts
// routes/auth/login.ts
export const POST: HTTP_POST = ($) => {
if ($.context.passwordResponse === "ok") return $.response[200];
if ($.context.passwordResponse === "expired")
return $.response[403].header("reason", "expired-password");
return $.response[401];
};
```

A Playwright test can flip between scenarios without hard-coded usernames:

```ts
import { counterfact } from "counterfact";
import { chromium } from "playwright";

let page;
let rootContext;
let stop;
let browser;

beforeAll(async () => {
browser = await chromium.launch({ headless: true });
page = await (await browser.newContext()).newPage();

const { contextRegistry, start } = await counterfact(config);
({ stop } = await start(config));
rootContext = contextRegistry.find("/");
});

afterAll(async () => {
await stop();
await browser.close();
});

it("rejects an incorrect password", async () => {
rootContext.passwordResponse = "incorrect";
await attemptToLogIn();
expect(await page.isVisible("#authentication-error")).toBe(true);
});

it("loads the dashboard on success", async () => {
rootContext.passwordResponse = "ok";
await attemptToLogIn();
expect(await page.isVisible("#dashboard")).toBe(true);
});

it("prompts for a password change when the password has expired", async () => {
rootContext.passwordResponse = "expired";
await attemptToLogIn();
expect(await page.isVisible("#password-change-form")).toBe(true);
});
```

## Return value of `counterfact()`

| Property | Type | Description |
| ----------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `contextRegistry` | `ContextRegistry` | Registry of all context objects keyed by path. Call `.find(path)` to get the context for a given route prefix. |
| `registry` | `Registry` | Registry of all loaded route modules. |
| `koaApp` | `Koa` | The underlying Koa application. |
| `koaMiddleware` | `Koa.Middleware` | The Counterfact request-dispatch middleware. |
| `start(config)` | `async (config) => { stop() }` | Starts the server (and optionally the file watcher and code generator). Returns a `stop()` function to gracefully shut down. |
| `startRepl()` | `() => REPLServer` | Starts the interactive REPL. Returns the REPL server instance. |

## See also

- [State](./state.md) — the context objects you manipulate from test code
- [Patterns: Automated Integration Tests](../patterns/automated-integration-tests.md) — using the programmatic API in a CI-friendly test suite
- [Reference](../reference.md) — CLI flags, architecture
- [Usage](../usage.md)
35 changes: 35 additions & 0 deletions docs/features/proxy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Proxy 🔀

You can mix real backend calls with mocks — useful when some endpoints are not finished or you need to test edge cases like 500 errors.

To proxy a single endpoint from within a route file:

```ts
// routes/pet/{petId}.ts
export const GET: HTTP_GET = ($) => {
return $.proxy("https://uat.petstore.example.com/pet");
};
```

To set a proxy for the entire API at startup, pass `--proxy-url` on the CLI:

```sh
npx counterfact@latest openapi.yaml api --proxy-url https://uat.petstore.example.com
```

From the REPL, you can toggle proxying for the whole API or specific routes:

```
⬣> .proxy on /payments # forward /payments to the real API
⬣> .proxy off /payments # let Counterfact handle /payments
⬣> .proxy off # stop proxying everything
```

Type `.proxy help` in the REPL for the full list of proxy commands.

## See also

- [Patterns: Hybrid Proxy](../patterns/hybrid-proxy.md) — when to mix real and mocked endpoints
- [REPL](./repl.md) — toggle proxying at runtime
- [Reference](../reference.md) — CLI flags
- [Usage](../usage.md)
Loading
Loading