import type { ExecutionPlan, ExecutionPlanNode, PlanEdge, BoundaryNode } from '../../src/generated/common.js'; /** * Small fluent builder for ExecutionPlans, so scenario journeys read like the * DAGs they model rather than walls of object literals. * * plan('offer_flow') * .node('wait1', 'wait', { duration: 1, unit: 'minutes' }) * .node('offer', 'store', { storeSlug: 'starter', offerId: 'pack_1' }) * .edge('wait1', 'onComplete', 'offer') * .build(); * * The first node added becomes the start node unless `.start(id)` is called. */ export class PlanBuilder { private readonly nodes: ExecutionPlanNode[] = []; private readonly edges: PlanEdge[] = []; private readonly boundaryNodes: BoundaryNode[] = []; private startNodeId?: string; private runIdValue?: string; constructor( private readonly scenarioId: string, private readonly opts: { planId?: string; userId?: string } = {}, ) {} /** Sets a specific run ID (default: auto-derived from scenarioId). */ runId(id: string): this { this.runIdValue = id; return this; } node(id: string, type: string, data?: Record): this { this.nodes.push({ id, type, data: data as ExecutionPlanNode['data'] }); if (!this.startNodeId) this.startNodeId = id; return this; } edge(source: string, sourceHandle: string, target: string): this { this.edges.push({ id: `e-${source}-${sourceHandle}-${target}`, source, sourceHandle, target }); return this; } /** Registers a server-side boundary node (handle that calls back to the gateway). */ boundary(sourceNodeId: string, sourceHandle: string, nodeId = `b-${sourceNodeId}-${sourceHandle}`): this { this.boundaryNodes.push({ sourceNodeId, sourceHandle, nodeId }); return this; } start(id: string): this { this.startNodeId = id; return this; } build(): ExecutionPlan { return { planId: this.opts.planId ?? `${this.scenarioId}-plan`, scenarioId: this.scenarioId, userId: this.opts.userId ?? 'player-1', startNodeId: this.startNodeId, runId: this.runIdValue ?? `${this.scenarioId}-run`, nodes: this.nodes, edges: this.edges, boundaryNodes: this.boundaryNodes, context: undefined, }; } } export function plan(scenarioId: string, opts?: { planId?: string; userId?: string }): PlanBuilder { return new PlanBuilder(scenarioId, opts); }