Skip to content

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";

Factory function that creates a type-safe GeneratorDefinition. This is the primary entry point for defining a new algorithm generator.

function createGenerator<TInputs extends Record<string, unknown>>(
config: CreateGeneratorConfig<TInputs>,
): GeneratorDefinition<TInputs>
ParameterTypeDescription
configCreateGeneratorConfig<TInputs>The algorithm ID and generator function. See below.

GeneratorDefinition<TInputs> — An object that the runner can execute.

  • Error if config.id does not match the pattern ^[a-z0-9][a-z0-9-]*$.
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",
});
},
});

Executes a generator definition with the given inputs and returns a validated StepSequence.

function runGenerator<TInputs extends Record<string, unknown>>(
definition: GeneratorDefinition<TInputs>,
inputs: TInputs,
options?: RunOptions,
): StepSequence
ParameterTypeRequiredDescription
definitionGeneratorDefinition<TInputs>YesThe generator definition (from createGenerator()).
inputsTInputsYesThe algorithm’s input parameters.
optionsRunOptionsNoOptional configuration. See RunOptions below.

StepSequence — A complete, validated step sequence ready for rendering.

GeneratorError in the following cases:

  • The generator function itself throws an exception.
  • The generator exceeds the maxSteps limit.
  • 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., highlightRange with from > to, showAttentionWeights with weights not summing to 1.0).

The runner validates the complete step sequence before returning:

  1. Non-empty: At least one step must exist.
  2. Index contiguity: steps[i].index === i for all i.
  3. Single terminal: Exactly one step has isTerminal === true.
  4. Terminal is last: The terminal step is steps[steps.length - 1].
  5. Visual action constraints: Action-specific rules (range ordering, attention weight summation, label/value length matching).
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); // 1
console.log(result.algorithmId); // "binary-search"
console.log(result.steps.length); // e.g., 5
console.log(result.generatedBy); // "typescript"
console.log(result.steps[result.steps.length - 1].isTerminal); // true

Configuration object for runGenerator().

interface RunOptions {
readonly maxSteps?: number;
}
FieldTypeDefaultDescription
maxStepsnumber10_000Maximum 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.
// Allow up to 50,000 steps for a complex algorithm
const result = runGenerator(complexGenerator, inputs, {
maxSteps: 50_000,
});

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>;
}
FieldTypeDescription
idstringThe algorithm’s URL-safe identifier (e.g., "binary-search"). Must match the id field in the algorithm’s meta.json.
generateGeneratorFunction<TInputs>The generator function.

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:

  1. inputs — The algorithm’s typed input parameters.
  2. step — The step builder function (see StepBuilderFn below).

It must be a JavaScript generator function (function*) that yields Step objects created via the step builder.

  1. The generator must yield at least one step.
  2. The last yielded step must have isTerminal: true.
  3. No step other than the last may have isTerminal: true.
  4. The generator must terminate (no infinite loops).
  5. State objects in steps must be independent snapshots (deep copies of mutable data).

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:

  1. Assigns the correct index (auto-incrementing from 0).
  2. Sets isTerminal to false if not provided.
  3. Validates the step ID format, title length, and code highlight line numbers.

Generators should never construct Step objects directly. Always use the step builder.


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;
}
FieldTypeRequiredDescription
idstringYesTemplate identifier (e.g., "compare_mid"). Must match ^[a-z0-9][a-z0-9_-]*$. Not unique within a sequence.
titlestringYesShort heading (1—200 characters).
explanationstringYesPlain-language narration of what is happening.
stateRecord<string, unknown>YesSnapshot of all algorithm variables. Must be an independent copy — never pass mutable references.
visualActionsreadonly VisualAction[]YesOrdered array of rendering instructions.
codeHighlightCodeHighlightYesSource code line mapping. Lines are 1-indexed positive integers.
isTerminalbooleanNoSet to true only on the final step. Defaults to false.
phasestringNoGrouping label (e.g., "initialization", "search", "result").

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 copy
yield step({
state: { array: [...array], target, left, right, mid },
// ...
});
// CORRECT: structuredClone for deeply nested state
yield step({
state: structuredClone({ graph, visited, distances }),
// ...
});
// WRONG: array is a mutable reference
yield step({
state: { array, target, left, right },
// ...
});

Configuration object passed to createGenerator().

interface CreateGeneratorConfig<TInputs extends Record<string, unknown>> {
readonly id: string;
readonly generate: GeneratorFunction<TInputs>;
}
FieldTypeDescription
idstringURL-safe algorithm identifier. Must match ^[a-z0-9][a-z0-9-]*$. Must match the id in the algorithm’s meta.json.
generateGeneratorFunction<TInputs>The generator function (must use function* syntax).

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,
);
}
PropertyTypeDescription
algorithmIdstringThe algorithm that caused the error.
stepIndexnumberThe step index at which the error occurred. -1 if before any step was created.
causeErrorThe original error, if this wraps an unexpected exception.
namestringAlways "GeneratorError".
messagestringFormatted as [algorithmId] Step stepIndex: description.
[binary-search] Step 3: Invalid step ID "COMPARE". Must match pattern: ^[a-z0-9][a-z0-9_-]*$
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);
}
}
}

The generator module re-exports these types from @/shared/types/step for convenience, so generators can import everything from a single location:

TypeDescription
StepA single step in an algorithm’s execution trace.
VisualActionA rendering instruction within a step.
CodeHighlightSource code line mapping for a step.
StepSequenceA complete, validated sequence of steps.

See the Step Format Specification for full documentation of these types.