Skip to content

Latest commit

 

History

History
133 lines (98 loc) · 5.02 KB

File metadata and controls

133 lines (98 loc) · 5.02 KB

Scenario Scripts

You want to automate repetitive REPL interactions — seeding data, configuring state, building reusable request sequences — so you don't have to type the same commands every session.

Problem

The REPL is powerful for ad-hoc exploration, but many setup tasks are predictable: seed a set of pets before a demo, reset state to a known baseline for a test, or configure a reusable route builder for a common request. Retyping these commands every time the server restarts is tedious and error-prone.

Solution

Write scenario scripts — TypeScript files in the scenarios/ directory that export named functions. Each function receives a single $ argument that exposes the full live context and route builder. Run any export on demand from the REPL with .scenario, or have one run automatically at startup.

Writing a scenario

Scenario functions are typed with the generated Scenario type:

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

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

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

Running scenarios from the REPL

Use the .scenario command. The argument is a slash-separated path where the last segment is the function name and everything before it is the file path relative to scenarios/ (with index.ts as the default):

⬣> .scenario soldPets
Applied soldPets
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

After running, anything stored in $.routes is immediately available in the REPL:

> routes.findSold.send()

Automatic startup

Export a function named startup from scenarios/index.ts and Counterfact calls it automatically when the server initializes, right before the REPL prompt appears — no manual command required:

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

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

If startup is not exported, the server starts normally with no error.

Delegating to helpers with extra arguments

Split large scenarios into focused helper functions in separate files and pass $ (and any extra arguments) through. This keeps each file small and lets you call the same helpers from both startup and the REPL with different parameters:

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

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

export function addPets($: ApplyContext, count: number, species: string) {
  for (let i = 0; i < count; i++) {
    $.context.addPet({
      name: `${species} ${i + 1}`,
      status: "available",
      photoUrls: [],
    });
  }
}
// scenarios/orders.ts
import type { ApplyContext } from "../types/scenario-context.js";

export function addOrders($: ApplyContext, count: number) {
  for (let i = 0; i < count; i++) {
    $.context.addOrder({ petId: i + 1, quantity: 1, status: "placed" });
  }
}

Helpers can also be called as standalone .scenario commands:

⬣> .scenario pets/addPets

Consequences

  • Scenario functions are plain TypeScript — no special framework, fully type-checked, easy to test in isolation.
  • .scenario provides on-demand state changes without editing handler files or restarting the server.
  • startup gives the server a deterministic initial state on every restart, eliminating manual seeding steps.
  • Helper functions accepting extra arguments let the same logic be used for both realistic production volumes (at startup) and minimal data (in the REPL during debugging).
  • Scenarios live alongside your handler code, making them easy to discover and keep in sync with the API.

Related Patterns