Genel bakış

Sequence

Run two behaviors left-to-right, skipping the second on failure.

composition

Install

$
Terminal window
pnpm dlx shadcn@latest add https://archyn.net/r/sequence.json

Copy the files below into the target paths shown in each tab.

ts@lib/flow/sequence.ts
import { type Behavior, type RuntimeContext, resultFromSignal, runBehavior } from "./core/runtime";
/**
* Runs two behaviors left-to-right.
*
* The second behavior receives the first behavior's successful output. If the first behavior
* fails, the second behavior is skipped and the failure passes through.
*
* @typeParam A - First behavior input type.
* @typeParam B - First output and second input type.
* @typeParam D - Second behavior output type.
* @typeParam E1 - First behavior domain error type.
* @typeParam E2 - Second behavior domain error type.
* @typeParam C - Shared custom context type.
* @param first - Behavior to run first.
* @param second - Behavior to run after first success.
* @returns A behavior with the first input type, second output type, and unioned domain errors.
*
* @example
* ```ts
* const loadName = sequence(loadUser, extractName);
* ```
*/
export function sequence<A, B, D, E1, E2, C extends object>(
first: Behavior<A, B, E1, C>,
second: Behavior<B, D, E2, C>,
): Behavior<A, D, E1 | E2, C> {
return async (input: A, context: RuntimeContext<C>) => {
if (context.signal.aborted) {
return resultFromSignal(context.signal);
}
const firstResult = await runBehavior(first, input, context);
if (!firstResult.ok) {
return firstResult;
}
if (context.signal.aborted) {
return resultFromSignal(context.signal);
}
return runBehavior(second, firstResult.value, context);
};
}

Update the import paths to match your project setup.

Run two Behavior values left-to-right and skip the second when the first fails.

Usage

sequence is the smallest composition primitive in Flow. It passes the successful output of the first Behavior into the second Behavior.

Use it when:

  • Connect two dependent steps.
  • Preserve both domain error types in one returned Behavior.
  • Make early failure explicit without try/catch nesting.
import { sequence } from "@/lib/flow/sequence";
import { task } from "@/lib/flow/task";
const parseId = task((value: string) => Number.parseInt(value, 10));
const loadUser = task((id: number) => fetch(`/api/users/${id}`).then((r) => r.json()));
const parseAndLoadUser = sequence(parseId, loadUser);

Behavior

Flow runs the first Behavior and returns early on failure. If it succeeds and the signal is still active, the second Behavior receives the first output.

First or second domain errors pass through. Cancellation before either phase returns Cancelled; thrown exceptions are captured by runBehavior as runtime errors.

Types

The second input must accept the first output, and domain errors are unioned.

Import from @/lib/flow/sequence.

Composition Notes

Use sequence for two-step dependent flows; use pipe for longer chains.

Do not use sequence when steps are independent; use all or allSettled for parallel work.

Source

ts@lib/flow/sequence.ts
import { type Behavior, type RuntimeContext, resultFromSignal, runBehavior } from "./core/runtime";
/**
* Runs two behaviors left-to-right.
*
* The second behavior receives the first behavior's successful output. If the first behavior
* fails, the second behavior is skipped and the failure passes through.
*
* @typeParam A - First behavior input type.
* @typeParam B - First output and second input type.
* @typeParam D - Second behavior output type.
* @typeParam E1 - First behavior domain error type.
* @typeParam E2 - Second behavior domain error type.
* @typeParam C - Shared custom context type.
* @param first - Behavior to run first.
* @param second - Behavior to run after first success.
* @returns A behavior with the first input type, second output type, and unioned domain errors.
*
* @example
* ```ts
* const loadName = sequence(loadUser, extractName);
* ```
*/
export function sequence<A, B, D, E1, E2, C extends object>(
first: Behavior<A, B, E1, C>,
second: Behavior<B, D, E2, C>,
): Behavior<A, D, E1 | E2, C> {
return async (input: A, context: RuntimeContext<C>) => {
if (context.signal.aborted) {
return resultFromSignal(context.signal);
}
const firstResult = await runBehavior(first, input, context);
if (!firstResult.ok) {
return firstResult;
}
if (context.signal.aborted) {
return resultFromSignal(context.signal);
}
return runBehavior(second, firstResult.value, context);
};
}