User manual

Catch a contract change with TypeScript

Use generated handler types to surface changed assumptions before clients and services meet in a shared environment. Counterfact regenerates contracts; your editor or an explicit tsc invocation performs the type check. Serving and hot reload transpile routes without running a full project typecheck.

Generate a small typed operation

In a project with "type": "module", install exact versions and commit the lockfile:

npm install --save-dev --save-exact counterfact [email protected]

Save status.yaml:

openapi: 3.0.3
info: { title: Status API, version: 1.0.0 }
paths:
  /status:
    get:
      operationId: getStatus
      responses:
        "200":
          description: Service status
          content:
            application/json:
              schema:
                type: object
                required: [status]
                properties:
                  status: { type: string, enum: [ready] }

Generate, then replace the editable route:

npx counterfact status.yaml api --generate --no-update-check
// api/routes/status.ts
import type { getStatus } from "../types/paths/status.types.js";
export const GET: getStatus = function ($) {
  return $.response[200].json({ status: "ready" });
};

Save tsconfig.counterfact.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "noEmit": true,
    "skipLibCheck": true
  },
  "include": ["api/**/*.ts"]
}

Run npx tsc --project tsconfig.counterfact.json. It should exit successfully.

Change the response contract

In the response schema, replace required: [status] with required: [status, message] and add message: { type: string } under properties. Regenerate and check:

npx counterfact status.yaml api --generate --no-update-check
npx tsc --project tsconfig.counterfact.json

The edited handler stays intact while its generated type changes. TypeScript should now exit nonzero with a diagnostic identifying the missing message property in the JSON response. Exact diagnostic wording depends on the compiler version.

Update the handler:

export const GET: getStatus = function ($) {
  return $.response[200].json({ status: "ready", message: "Accepting requests" });
};

Run the same typecheck again; it should pass. In CI, run generation and this explicit check with pinned dependencies before the client tests. Commit the specification and authored changes together; generated types are derived artifacts, so follow your project’s source-control convention for them.

Understand the boundary

This demonstrates a structural authoring check for typed, included files. It does not prove every use of the API is correct: unchecked JavaScript, any, $.x, incomplete schemas, and unmodeled behavior can escape it. Runtime validation checks a different, limited set of traffic properties. Keep HTTP assertions and targeted real-backend tests alongside the typecheck; Counterfact provides earlier local feedback rather than replacing a gateway.

When checking responses, make advisory header errors fail your HTTP test:

const response = await fetch(`${baseUrl}/status`);
const body = await response.text();
assert.equal(response.status, 200, body);
assert.equal(response.headers.get("response-type-error"), null);

Here assert is node:assert/strict and baseUrl is the running simulator’s address. This checks the response status and supported header diagnostics; it is not a general response-body schema check.