import type { ComponentFlow, FlowStep, } from '@beehexa/hexasync-template-compose'; /** * A `ComponentFlow` fixture's authored record, rebuilt from its own phases (Story 4.6, AC-2). * * `buildMermaid` draws from `flow.source` — the composed component — because the shared flow models need the record and * not the `phases` projection. These fixtures predate that and describe the component in projection form only, so this * reconstitutes what the composer would have handed over: `{ : [ …steps ] }`. * * Deliberately mechanical and deliberately test-only. It exists so the assertions in three spec files keep testing what * they were written to test — that the REPORT draws this routing — rather than being rewritten as fixtures of a * different shape, which would have quietly changed what each case covers. * * ### The two vocabularies are NOT interchangeable (AD-21) * * A worker step says `rootStep`, `displayType`, `data`, `onError`; a frontend one says `root`, `type`, `metadata`, * `onCancelled`. `FlowStep` is the union of both, so a single spelling here would silently under-draw one runtime — the * first version wrote `displayType`/`data` for a creation flow and the frontend model, reading `type`/`metadata`, drew * a `SWITCH` with none of its branches and reported no error. That is the same class of loss AD-21 keeps the two * packages apart to prevent, reproduced in a test helper. */ export function withSource(flow: ComponentFlow): ComponentFlow { const frontend = flow.kind === 'creation'; const component: Record = {}; for (const phase of flow.phases) component[phase.name] = phase.steps.map((step) => frontend ? frontendStep(step) : workerStep(step), ); return { ...flow, source: frontend ? // No collection and no id, matching `buildCreationFlow`: the document is the composed root. { runtime: 'frontend', component } : { runtime: 'worker', collection: flow.kind === 'pusher' ? 'pushers' : 'pullers', component, componentId: 'FIXTURE', }, }; } /** The WORKER spelling of a step, as a puller/pusher YAML writes it. */ function workerStep(step: FlowStep): Record { return { key: step.key, ...named(step), ...(step.next !== undefined ? { next: step.next } : {}), ...(step.rootStep ? { rootStep: true } : {}), ...(step.stepType !== undefined ? { displayType: step.stepType } : {}), ...(step.data !== undefined ? { data: step.data } : {}), ...(step.onError !== undefined ? { onError: step.onError } : {}), ...(step.fromStep !== undefined ? { fromStep: step.fromStep } : {}), }; } /** The FRONTEND spelling, as a `creationSteps` document writes it. */ function frontendStep(step: FlowStep): Record { return { key: step.key, ...named(step), ...(step.next !== undefined ? { next: step.next } : {}), ...(step.rootStep ? { root: true } : {}), ...(step.stepType !== undefined ? { type: step.stepType } : {}), // `metadata` for an IF/SWITCH's branches, and a fixture that put them in `data` still means the same thing: the // projection has both fields and only one of them reaches a frontend reader. ...(step.metadata !== undefined ? { metadata: step.metadata } : step.data !== undefined ? { metadata: step.data } : {}), ...(step.onCancelled !== undefined ? { onCancelled: step.onCancelled } : {}), }; } /** `name` only when the fixture gave a label distinct from the key — `label` falls back to `key` upstream. */ function named(step: FlowStep): Record { return step.label !== step.key ? { name: step.label } : {}; }