Skip to content
中文

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');
      });
    },
  });
}

API reference, usage examples and developer guides.