Skip to content

Latest commit

 

History

History
178 lines (129 loc) · 5.8 KB

File metadata and controls

178 lines (129 loc) · 5.8 KB

REPL ⬣

The REPL is a JavaScript prompt connected directly to the running server — like the browser DevTools console, but for your mock API. After starting Counterfact you'll see:

____ ____ _  _ _ _ ___ ____ ____ ____ ____ ____ ___
|___ [__] |__| |\|  |  |=== |--< |--- |--| |___  |
            Storybook for the back-end

| API Base URL  ==> http://localhost:3100
| Admin Console ==> http://localhost:3100/counterfact/

⬣>

At the prompt you can interact with the live context:

// add a single pet
context.addPet({ name: "Fluffy", photoUrls: [] });

// add 100 pets
for (let i = 0; i < 100; i++)
  context.addPet({ name: `Pet ${i}`, photoUrls: [] });

// query state
context.pets.filter((pet) => pet.name.startsWith("F"));

To access context from a subdirectory:

const petsContext = loadContext("/pets");

When running multiple APIs in one process, REPL state is grouped by API key:

context.billing
context.inventory
routes.billing
routes.inventory

In this mode, loadContext and route are also grouped by API key:

loadContext.billing("/pets")
route.inventory("/stock/{sku}")

When configuring multiple APIs, each API must define a non-empty, unique group name.

The built-in client object lets you make HTTP requests from the prompt without leaving the terminal:

client.get("/users");
client.post("/users", { name: "bob" });
client.put("/users/1", { name: "robert" }, { "x-api-version": "2" });

All standard HTTP methods are supported. Arguments are: path, body (where applicable), headers.

When an OpenAPI document is loaded, pressing Tab while typing client.get("... or route("... autocompletes using OpenAPI path templates (for example, /example/hello/{name}).

The built-in route() function creates a fluent request builder that validates required parameters against your OpenAPI document before sending:

// Build and inspect before sending
const req = route("/pet/{petId}").method("get").path({ petId: 42 })
req.ready()    // true / false
req.missing()  // lists missing required parameters
req.help()     // prints OpenAPI docs for the operation
await req.send()

See the Route Builder guide for full documentation.

Scenario scripts with .scenario

For more complex setups you can automate REPL interactions by writing scenario scripts — plain TypeScript files that export named functions. Run them with .scenario:

⬣> .scenario soldPets

When running multiple APIs in one process, qualify the command with the API group:

> .scenario billing soldPets

Tab completion supports both modes: in single-API sessions, .scenario <Tab> suggests scenario paths/functions; in multi-API sessions, .scenario <Tab> suggests API groups first, then .scenario <group> <Tab> suggests scenario paths/functions for that group.

Path resolution: the argument to .scenario is a slash-separated path. The last segment is the function name; everything before it is the file path, resolved relative to <basePath>/scenarios/ (with index.ts as the default file).

Command File Function
.scenario soldPets scenarios/index.ts soldPets
.scenario pets/resetAll scenarios/pets.ts resetAll
.scenario pets/orders/pending scenarios/pets/orders.ts pending

A scenario function receives a single argument with { context, loadContext, routes, route }:

// scenarios/index.ts
import type { Scenario } from "../types/_.context.js";

export const soldPets: Scenario = ($) => {
  // Mutate context directly — same as typing in the REPL
  $.context.petService.reset();
  $.context.petService.addPet({ id: 1, status: "sold" });
  $.context.petService.addPet({ id: 2, status: "available" });

  // Store a pre-configured route builder for later use in the REPL
  $.routes.findSold = $
    .route("/pet/findByStatus")
    .method("get")
    .query({ status: "sold" });
}

After the command runs you can immediately use anything stored in $.routes:

> routes.findSold.send()

The Scenario type and Scenario$ interface are generated automatically into types/_.context.ts when you run Counterfact with type generation enabled.

Startup scenario

The startup export in scenarios/index.ts is special: it runs automatically when the server initializes, right before the REPL prompt appears. Use it to seed dummy data so the server is immediately useful without any manual REPL commands.

// scenarios/index.ts
import type { Scenario } from "../types/_.context.js";

export const startup: Scenario = ($) => {
  $.context.addPet({ name: "Fluffy", status: "available", photoUrls: [] });
  $.context.addPet({ name: "Rex", status: "sold", photoUrls: [] });
};

Delegating to other scenario functions keeps startup focused and readable. Pass $ (and any extra arguments) to each helper:

// scenarios/index.ts
import type { Scenario } from "../types/_.context.js";
import { addPets } from "./pets.js";
import { addOrders } from "./orders.js";

export const startup: Scenario = ($) => {
  addPets($, 20, "dog");
  addOrders($, 5);
};
// scenarios/pets.ts
import type { Scenario$ } from "../types/_.context.js";

export function addPets($: Scenario$, count: number, species: string) {
  for (let i = 0; i < count; i++) {
    $.context.addPet({ name: `${species} ${i + 1}`, status: "available", photoUrls: [] });
  }
}

If startup is not exported from scenarios/index.ts, it is silently skipped — no error is thrown.

See also