Interleaved hooks, stage short-circuiting, and cancellation checks
The policy implementation follows.
ts
export function defineDelivery<Input, State, Result, StageId extends string>(
options: DeliveryOptions<Input, State, Result, StageId>,
) {
const ids = new Set<string>();
const stages = options.stages.map((stage) => {
const { id } = stage;
if (
!id?.trim() ||
ids.has(id) ||
(stage.handle !== undefined && typeof stage.handle !== 'function')
)
throw new TypeError('Invalid or duplicate delivery stage');
ids.add(id);
return Object.freeze({ id, handle: stage.handle?.bind(stage) });
});
const createState = options.createState.bind(options),
beforeStage = options.beforeStage?.bind(options),
missingResult = options.missingResult.bind(options);
return Object.freeze({
stages: Object.freeze(stages.map((stage) => ({ id: stage.id }))),
/**
* Build the state and run the stages in order.
*
* The first `complete` outcome ends the pipeline and returns its value; if none completes,
* `missingResult` supplies the error.
*
* @param input - Host input; it is exposed to stages as `context.app`.
* @param execution - Execution context; cancellation is checked before every hook.
* @throws The configured error when no stage completes, or a `TypeError` for invalid
* handler results.
*/
deliver(input: Input, execution: ExecutionContext): Result {
return nativeSubFlowSync(execution, 'delivery', 'delivery', () => {
execution.signal.throwIfAborted();
const state = synchronous(
createState(input, execution),
'Delivery state creation must be synchronous',
);
const context = {
...execution,
signal: execution.signal,
diagnostics: execution.diagnostics,
app: input,
};
for (const stage of stages) {
execution.signal.throwIfAborted();
synchronous(
beforeStage?.({ stageId: stage.id, state }, context),
'Delivery hooks must be synchronous',
);
execution.signal.throwIfAborted();
if (!stage.handle) continue;
const outcome = synchronous(
stage.handle(state, context),
'Delivery handlers must be synchronous',
);
execution.signal.throwIfAborted();
if (
!outcome ||
!['continue', 'complete'].includes(outcome.type) ||
(outcome.type === 'complete' && !('value' in outcome))
)
throw new TypeError('Delivery handlers require explicit continue/complete');
if (outcome.type === 'complete') {
const value = outcome.value;
execution.signal.throwIfAborted();
return value;
}
}
throw synchronous(missingResult(), 'Delivery errors must be synchronous');
});
},
});
}