TypeScript Generator API
The TypeScript Generator API provides the framework for writing algorithm generators that produce step sequences. Generators are JavaScript generator functions (function*) that yield Step objects via a provided step builder. The runner executes these generators, validates the output, and packages it into a StepSequence.
All exports are available from @/engine/generator:
import { createGenerator, runGenerator, GeneratorError,} from "@/engine/generator";
import type { StepInput, StepBuilderFn, GeneratorFunction, GeneratorDefinition, CreateGeneratorConfig, RunOptions, Step, VisualAction, CodeHighlight, StepSequence,} from "@/engine/generator";createGenerator
Section titled “createGenerator”Factory function that creates a type-safe GeneratorDefinition. This is the primary entry point for defining a new algorithm generator.
Signature
Section titled “Signature”function createGenerator<TInputs extends Record<string, unknown>>( config: CreateGeneratorConfig<TInputs>,): GeneratorDefinition<TInputs>Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
config | CreateGeneratorConfig<TInputs> | The algorithm ID and generator function. See below. |
Returns
Section titled “Returns”GeneratorDefinition<TInputs> — An object that the runner can execute.
Throws
Section titled “Throws”Errorifconfig.iddoes not match the pattern^[a-z0-9][a-z0-9-]*$.
Example
Section titled “Example”import { createGenerator } from "@/engine/generator";
interface BinarySearchInputs { array: number[]; target: number;}
export default createGenerator<BinarySearchInputs>({ id: "binary-search", *generate(inputs, step) { const { array, target } = inputs; let left = 0; let right = array.length - 1;
yield step({ id: "init", title: "Initialize Binary Search", explanation: `Set left = 0, right = ${right}, searching for ${target}.`, state: { array: [...array], target, left, right, mid: null }, visualActions: [ { type: "highlightRange", from: 0, to: right, color: "highlight" }, { type: "movePointer", id: "left", to: 0 }, { type: "movePointer", id: "right", to: right }, ], codeHighlight: { language: "pseudocode", lines: [2, 3] }, phase: "initialization", });
// ... search loop steps ...
yield step({ id: "result", title: "Search Complete", explanation: "The algorithm has finished.", state: { array: [...array], target, left, right, mid: null }, visualActions: [], codeHighlight: { language: "pseudocode", lines: [15] }, isTerminal: true, phase: "result", }); },});runGenerator
Section titled “runGenerator”Executes a generator definition with the given inputs and returns a validated StepSequence.
Signature
Section titled “Signature”function runGenerator<TInputs extends Record<string, unknown>>( definition: GeneratorDefinition<TInputs>, inputs: TInputs, options?: RunOptions,): StepSequenceParameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
definition | GeneratorDefinition<TInputs> | Yes | The generator definition (from createGenerator()). |
inputs | TInputs | Yes | The algorithm’s input parameters. |
options | RunOptions | No | Optional configuration. See RunOptions below. |
Returns
Section titled “Returns”StepSequence — A complete, validated step sequence ready for rendering.
Throws
Section titled “Throws”GeneratorError in the following cases:
- The generator function itself throws an exception.
- The generator exceeds the
maxStepslimit. - The generated steps violate any step format invariant (empty sequence, non-contiguous indices, missing terminal step, multiple terminal steps, terminal step not last).
- A visual action violates its constraints (e.g.,
highlightRangewithfrom > to,showAttentionWeightswith weights not summing to 1.0).
Invariants Enforced
Section titled “Invariants Enforced”The runner validates the complete step sequence before returning:
- Non-empty: At least one step must exist.
- Index contiguity:
steps[i].index === ifor alli. - Single terminal: Exactly one step has
isTerminal === true. - Terminal is last: The terminal step is
steps[steps.length - 1]. - Visual action constraints: Action-specific rules (range ordering, attention weight summation, label/value length matching).
Example
Section titled “Example”import binarySearchGenerator from "@/algorithms/classical/binary-search/generator";import { runGenerator } from "@/engine/generator";
const result = runGenerator(binarySearchGenerator, { array: [1, 3, 5, 7, 9, 11, 13], target: 7,});
console.log(result.formatVersion); // 1console.log(result.algorithmId); // "binary-search"console.log(result.steps.length); // e.g., 5console.log(result.generatedBy); // "typescript"console.log(result.steps[result.steps.length - 1].isTerminal); // trueRunOptions
Section titled “RunOptions”Configuration object for runGenerator().
interface RunOptions { readonly maxSteps?: number;}| Field | Type | Default | Description |
|---|---|---|---|
maxSteps | number | 10_000 | Maximum number of steps the generator may produce. Exceeding this limit throws GeneratorError. This is a safety limit to prevent infinite loops from locking the browser. |
Example
Section titled “Example”// Allow up to 50,000 steps for a complex algorithmconst result = runGenerator(complexGenerator, inputs, { maxSteps: 50_000,});GeneratorDefinition
Section titled “GeneratorDefinition”The output of createGenerator(). Bundles the algorithm ID with its generate function.
interface GeneratorDefinition<TInputs extends Record<string, unknown>> { readonly id: string; readonly generate: GeneratorFunction<TInputs>;}| Field | Type | Description |
|---|---|---|
id | string | The algorithm’s URL-safe identifier (e.g., "binary-search"). Must match the id field in the algorithm’s meta.json. |
generate | GeneratorFunction<TInputs> | The generator function. |
GeneratorFunction
Section titled “GeneratorFunction”The type signature for generator functions.
type GeneratorFunction<TInputs extends Record<string, unknown>> = (inputs: TInputs, step: StepBuilderFn) => Generator<Step, void, undefined>;A generator function receives:
inputs— The algorithm’s typed input parameters.step— The step builder function (seeStepBuilderFnbelow).
It must be a JavaScript generator function (function*) that yields Step objects created via the step builder.
- The generator must yield at least one step.
- The last yielded step must have
isTerminal: true. - No step other than the last may have
isTerminal: true. - The generator must terminate (no infinite loops).
- State objects in steps must be independent snapshots (deep copies of mutable data).
StepBuilderFn
Section titled “StepBuilderFn”The function signature provided to generators for creating steps.
type StepBuilderFn = (input: StepInput) => Step;The step builder is provided by the runner. Generators call it to convert a StepInput into a complete Step object. The builder automatically:
- Assigns the correct
index(auto-incrementing from 0). - Sets
isTerminaltofalseif not provided. - Validates the step ID format, title length, and code highlight line numbers.
Generators should never construct Step objects directly. Always use the step builder.
StepInput
Section titled “StepInput”The fields a generator provides when yielding a step. This is the input to the step builder function.
interface StepInput { readonly id: string; readonly title: string; readonly explanation: string; readonly state: Record<string, unknown>; readonly visualActions: readonly VisualAction[]; readonly codeHighlight: CodeHighlight; readonly isTerminal?: boolean; readonly phase?: string;}| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Template identifier (e.g., "compare_mid"). Must match ^[a-z0-9][a-z0-9_-]*$. Not unique within a sequence. |
title | string | Yes | Short heading (1—200 characters). |
explanation | string | Yes | Plain-language narration of what is happening. |
state | Record<string, unknown> | Yes | Snapshot of all algorithm variables. Must be an independent copy — never pass mutable references. |
visualActions | readonly VisualAction[] | Yes | Ordered array of rendering instructions. |
codeHighlight | CodeHighlight | Yes | Source code line mapping. Lines are 1-indexed positive integers. |
isTerminal | boolean | No | Set to true only on the final step. Defaults to false. |
phase | string | No | Grouping label (e.g., "initialization", "search", "result"). |
State Snapshot Warning
Section titled “State Snapshot Warning”The state field must contain an independent snapshot of the algorithm’s variables. If you pass a reference to a mutable object, all steps will point to the same (mutated) object, producing incorrect behavior when the user steps backward.
// CORRECT: spread creates a shallow copyyield step({ state: { array: [...array], target, left, right, mid }, // ...});
// CORRECT: structuredClone for deeply nested stateyield step({ state: structuredClone({ graph, visited, distances }), // ...});
// WRONG: array is a mutable referenceyield step({ state: { array, target, left, right }, // ...});CreateGeneratorConfig
Section titled “CreateGeneratorConfig”Configuration object passed to createGenerator().
interface CreateGeneratorConfig<TInputs extends Record<string, unknown>> { readonly id: string; readonly generate: GeneratorFunction<TInputs>;}| Field | Type | Description |
|---|---|---|
id | string | URL-safe algorithm identifier. Must match ^[a-z0-9][a-z0-9-]*$. Must match the id in the algorithm’s meta.json. |
generate | GeneratorFunction<TInputs> | The generator function (must use function* syntax). |
GeneratorError
Section titled “GeneratorError”Error class thrown when a generator produces invalid output or encounters a runtime failure. Includes structured context for debugging.
class GeneratorError extends Error { readonly algorithmId: string; readonly stepIndex: number; readonly cause?: Error;
constructor( algorithmId: string, stepIndex: number, message: string, cause?: Error, );}| Property | Type | Description |
|---|---|---|
algorithmId | string | The algorithm that caused the error. |
stepIndex | number | The step index at which the error occurred. -1 if before any step was created. |
cause | Error | The original error, if this wraps an unexpected exception. |
name | string | Always "GeneratorError". |
message | string | Formatted as [algorithmId] Step stepIndex: description. |
Error Message Format
Section titled “Error Message Format”[binary-search] Step 3: Invalid step ID "COMPARE". Must match pattern: ^[a-z0-9][a-z0-9_-]*$Example
Section titled “Example”import { runGenerator, GeneratorError } from "@/engine/generator";
try { const result = runGenerator(myGenerator, inputs);} catch (error) { if (error instanceof GeneratorError) { console.error(`Algorithm: ${error.algorithmId}`); console.error(`Failed at step: ${error.stepIndex}`); console.error(`Reason: ${error.message}`); if (error.cause) { console.error(`Original error:`, error.cause); } }}Re-exported Types
Section titled “Re-exported Types”The generator module re-exports these types from @/shared/types/step for convenience, so generators can import everything from a single location:
| Type | Description |
|---|---|
Step | A single step in an algorithm’s execution trace. |
VisualAction | A rendering instruction within a step. |
CodeHighlight | Source code line mapping for a step. |
StepSequence | A complete, validated sequence of steps. |
See the Step Format Specification for full documentation of these types.