73 lines
2.4 KiB
TypeScript
73 lines
2.4 KiB
TypeScript
|
|
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<string, unknown>): 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);
|
||
|
|
}
|