Skip to content

Export Context$ and Scenario$ interfaces for strongly-typed loadContext() in _.context.ts files#1822

Merged
pmcelhaney merged 14 commits intomainfrom
copilot/make-applycontext-type-available
Apr 13, 2026
Merged

Export Context$ and Scenario$ interfaces for strongly-typed loadContext() in _.context.ts files#1822
pmcelhaney merged 14 commits intomainfrom
copilot/make-applycontext-type-available

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 10, 2026

Summary

_.context.ts constructors receive { loadContext, readJson } from the module loader with no type information, making loadContext effectively untyped. This exports a Context$ interface and a Scenario$ interface from the generated types/_.context.ts so users can type their _.context.ts constructor arguments and get the same strongly-typed loadContext overloads — with no runtime overhead and no implementation to inherit.

The generated types/_.context.ts uses a shared LoadContextDefinitions interface to aggregate all loadContext overloads, which is referenced by both Scenario$ and Context$. All Scenario$ members are readonly. The default routes/_.context.ts scaffold is simplified to accept a Context$ argument without implementing any interface or assigning fields.

Original Prompt

_.context.ts constructors receive { loadContext, readJson } from the module loader with no type information, making loadContext effectively untyped. Export a type from the generated types file so users can annotate their constructors and get strongly-typed loadContext overloads.

Manual acceptance tests

  • The generated types/_.context.ts contains a LoadContextDefinitions interface with the generated loadContext overloads, export interface Scenario$ with all readonly members and loadContext: LoadContextDefinitions["loadContext"], and export interface Context$ with readonly loadContext: LoadContextDefinitions["loadContext"] and readonly readJson
  • The generated export type Scenario = ($: Scenario$) => Promise<void> | void references Scenario$
  • The generated routes/_.context.ts imports Context$ and defines export class Context with constructor($: Context$) { void $; } — no implements, no field assignment
  • In a _.context.ts that uses Context$ to type its constructor, TypeScript provides typed overloads for loadContext (e.g. loadContext("/pets") resolves to PetsContext when routes/pets/_.context.ts exists)
  • Existing _.context.ts files that do not use Context$ continue to work unchanged
  • Scenario, Scenario$, Context$, and existing loadContext overloads in types/_.context.ts are unaffected by changes to unrelated routes

Tasks

  • generate.ts — Restructured buildApplyContextContent to extract a LoadContextDefinitions interface containing all loadContext overloads (with a /* code generator adds additional signatures here */ comment). Renamed ApplyContextScenario$; all its members are readonly and loadContext references LoadContextDefinitions["loadContext"]. The Scenario type alias now uses Scenario$. Added export interface Context$ with readonly loadContext: LoadContextDefinitions["loadContext"] and readonly readJson. Updated the JSDoc on writeApplyContextType to reference Scenario$.

  • repository.ts — Simplified the default routes/_.context.ts scaffold to import Context$ and define export class Context with constructor($: Context$) { void $; } — no implements, no field declarations, no field assignment. Updated the doc link to point to the GitHub docs page.

  • generate.test.ts — Updated test assertions to verify export interface Context$, export interface Scenario$, LoadContextDefinitions["loadContext"], readonly context, and Scenario = ($: Scenario$) appear in the generated output.

  • context-registry.ts — Updated a code comment that referenced the old scenario-context name.

  • docs/features/repl.md — Updated prose to reference Scenario$ instead of ApplyContext, and import examples to reference types/_.context.ts / types/_.context.js.

  • .changeset/strongly-typed-load-context.md — Minor bump changeset updated to reference the new filename.

Copilot AI linked an issue Apr 10, 2026 that may be closed by this pull request
Copy link
Copy Markdown
Collaborator

@pmcelhaney pmcelhaney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create a BaseContext type and have the generated Context class extend that.

@pmcelhaney
Copy link
Copy Markdown
Collaborator

@copilot try again

@pmcelhaney
Copy link
Copy Markdown
Collaborator

@copilot try again

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 11, 2026

@copilot try again

Done in c259be8. Replaced ContextArgs type with a BaseContext class exported from scenario-context.ts. The generated Context class now extends BaseContext, which initializes protected readonly loadContext and protected readonly readJson via its constructor — no manual constructor typing needed in user files.

Copilot AI changed the title Export ContextArgs type for strongly-typed loadContext() in _.context.ts files Export BaseContext class for strongly-typed loadContext() in _.context.ts files Apr 11, 2026
@pmcelhaney
Copy link
Copy Markdown
Collaborator

@copilot BaseContext needs to be an interface, not a class. It will not have an implementation. It just needs to define the signature of the constructor.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 11, 2026

@copilot BaseContext needs to be an interface, not a class. It will not have an implementation. It just needs to define the signature of the constructor.

Done in 8579779. BaseContext is now an interface with no implementation — just readonly loadContext and readonly readJson. The scaffold uses implements BaseContext with a constructor that initializes those fields from the args.

Copilot AI changed the title Export BaseContext class for strongly-typed loadContext() in _.context.ts files Export BaseContext interface for strongly-typed loadContext() in _.context.ts files Apr 11, 2026
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 11, 2026

Rename scenario-context.ts to _.context.ts

Done in d6a7. The generated file is now types/_.context.ts and all imports reference "../types/_.context.js" — updated across generate.ts, repository.ts, the test file, docs, and changeset.

Copilot AI requested a review from pmcelhaney April 11, 2026 01:47
@pmcelhaney pmcelhaney marked this pull request as ready for review April 11, 2026 01:48
Copy link
Copy Markdown
Collaborator

@pmcelhaney pmcelhaney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generated types/_.context.ts file should look like this:

// This file is generated by Counterfact. Do not edit manually.
import type { Context } from "../routes/_.context";

interface LoadContextDefinitions {
  /* code generator adds additional signatures here */
  loadContext(path: "/" | `/${string}`): Context;
  loadContext(path: string): Record<string, unknown>;
}

export interface ApplyContext {
  /** Root context, same as loadContext("/") */
  readonly context: Context;
  readonly loadContext: LoadContextDefinitions["loadContext"];
  /** Named route builders stored in the REPL execution context */
  readonly routes: Record<string, unknown>;
  /** Create a new route builder for a given path */
  readonly route: (path: string) => unknown;
}

/** A scenario function that receives the live REPL environment */
export type Scenario = ($: ApplyContext) => Promise<void> | void;

/** Interface for Context objects defined in _.context.ts files */
export interface Context$ {
  /** Load a context object for a specific path */
  readonly loadContext: LoadContextDefinitions["loadContext"];
  /** Load a JSON file relative to this file's path */
  readonly readJson: (relativePath: string) => Promise<unknown>;
}

The generated routes/_.context.ts file should be exactly this:

import type { Context$ } from "../types/_.context.js";

/**
 * This is the default context for Counterfact.
 *
 * It defines the context object in the REPL
 * and the $.context object in the code.
 *
 * Add properties and methods to suit your needs.
 *
 * See https://github.com/counterfact/api-simulator/blob/main/docs/features/state.md
 */

export class Context {
  constructor($: Context$) {
    void $;
  }
}

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 13, 2026

The generated types/_.context.ts file should look like this:

Done in 82d2824. The generated types/_.context.ts now uses LoadContextDefinitions for overloads (shared by both ApplyContext and Context$), ApplyContext members are all readonly, and BaseContext is renamed to Context$. The scaffold is simplified to just constructor($: Context$) { void $; } with no field assignments.

Copilot AI changed the title Export BaseContext interface for strongly-typed loadContext() in _.context.ts files Export Context$ interface for strongly-typed loadContext() in _.context.ts files Apr 13, 2026
Copilot AI requested a review from pmcelhaney April 13, 2026 15:56
Copy link
Copy Markdown
Collaborator

@pmcelhaney pmcelhaney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename ApplyContext to Scenario$

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 13, 2026

Rename ApplyContext to Scenario$

Done in 036e92f. ApplyContext is renamed to Scenario$ in the generated types/_.context.ts, the Scenario type alias, tests, and docs.

Copilot AI changed the title Export Context$ interface for strongly-typed loadContext() in _.context.ts files Export Context$ and Scenario$ interfaces for strongly-typed loadContext() in _.context.ts files Apr 13, 2026
Copilot AI requested a review from pmcelhaney April 13, 2026 16:03
@pmcelhaney pmcelhaney enabled auto-merge April 13, 2026 16:38
@pmcelhaney pmcelhaney added this pull request to the merge queue Apr 13, 2026
Merged via the queue into main with commit e9bf98a Apr 13, 2026
8 of 10 checks passed
@pmcelhaney pmcelhaney deleted the copilot/make-applycontext-type-available branch April 13, 2026 16:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

loadContext() should be strongly typed

2 participants