Обзор

Pipe

Compose behaviors top-to-bottom.

composition

Install

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

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

ts@lib/flow/pipe.ts
import {
type Behavior,
type BehaviorContext,
type BehaviorError,
type BehaviorInput,
type BehaviorOutput,
} from "./core/runtime";
import { sequence } from "./sequence";
/**
* Behavior shape accepted by `pipe`.
*
* This advanced type intentionally erases concrete input, output, error, and context details while
* variadic pipe types validate each adjacent step.
*/
type AnyBehavior = Behavior<never, unknown, unknown, never>;
/**
* Extracts the output type from the final behavior in a pipe.
*
* @typeParam Steps - Non-empty tuple of behaviors.
*/
type LastOutput<Steps extends readonly AnyBehavior[]> = Steps extends readonly [
...(readonly AnyBehavior[]),
infer Last extends AnyBehavior,
]
? BehaviorOutput<Last>
: never;
/**
* Computes the behavior type returned by `pipe`.
*
* @typeParam Steps - Non-empty tuple of compatible behaviors.
*/
type PipeResult<Steps extends readonly [AnyBehavior, ...AnyBehavior[]]> = Behavior<
BehaviorInput<Steps[0]>,
LastOutput<Steps>,
BehaviorError<Steps[number]>,
BehaviorContext<Steps[number]>
>;
/**
* Validates that each pipe step accepts the previous step's output.
*
* @typeParam Steps - Tuple of behaviors to validate.
*/
type ValidPipe<Steps extends readonly AnyBehavior[]> = Steps extends readonly [
infer First extends AnyBehavior,
infer Second extends AnyBehavior,
...infer Rest extends AnyBehavior[],
]
? BehaviorOutput<First> extends BehaviorInput<Second>
? readonly [First, ...ValidPipe<readonly [Second, ...Rest]>]
: never
: Steps;
/**
* Composes behaviors from top to bottom.
*
* Each step runs only when the previous step succeeds. The output type of each step must be
* assignable to the input type of the next step.
*
* @typeParam Steps - Non-empty tuple of compatible behaviors.
* @param steps - Behaviors to compose in execution order.
* @returns A behavior from the first input type to the last output type.
*
* @example
* ```ts
* const workflow = pipe(loadUser, map(loadUserDetails, (details) => details.email));
* ```
*/
export function pipe<const Steps extends readonly [AnyBehavior, ...AnyBehavior[]]>(
...steps: Steps & ValidPipe<Steps>
): PipeResult<Steps> {
const [first, ...rest] = steps;
const link = sequence as unknown as (first: AnyBehavior, second: AnyBehavior) => AnyBehavior;
return rest.reduce((current, next) => link(current, next), first) as unknown as PipeResult<Steps>;
}

Update the import paths to match your project setup.

Compose one or more compatible Behavior values into a top-to-bottom pipeline.

Usage

pipe generalizes sequence for longer workflows. TypeScript validates that each step output can feed the next step input.

Use it when:

  • Build a readable multi-step workflow.
  • Preserve output and error inference across many Behavior values.
  • Avoid deeply nested sequence(sequence(...)) calls.
import { pipe } from "@/lib/flow/pipe";
import { task } from "@/lib/flow/task";
const normalize = task((value: string) => value.trim());
const requireValue = task((value: string, context) =>
value ? context.ok(value) : context.fail({ kind: "empty" as const }),
);
const toLength = task((value: string) => value.length);
const workflow = pipe(normalize, requireValue, toLength);

Behavior

pipe links steps with sequence from left to right. A failure in any step stops the remaining steps.

The returned Behavior can fail with any step domain error. Runtime errors and cancellation follow the same path as the individual Behavior values.

Types

Each adjacent step is type-checked so previous output is assignable to next input.

Import from @/lib/flow/pipe.

Composition Notes

Use pipe as the default shape for readable multi-step workflows.

Do not use pipe for unrelated side effects; keep side effects inside tap or task boundaries.

Source

ts@lib/flow/pipe.ts
import {
type Behavior,
type BehaviorContext,
type BehaviorError,
type BehaviorInput,
type BehaviorOutput,
} from "./core/runtime";
import { sequence } from "./sequence";
/**
* Behavior shape accepted by `pipe`.
*
* This advanced type intentionally erases concrete input, output, error, and context details while
* variadic pipe types validate each adjacent step.
*/
type AnyBehavior = Behavior<never, unknown, unknown, never>;
/**
* Extracts the output type from the final behavior in a pipe.
*
* @typeParam Steps - Non-empty tuple of behaviors.
*/
type LastOutput<Steps extends readonly AnyBehavior[]> = Steps extends readonly [
...(readonly AnyBehavior[]),
infer Last extends AnyBehavior,
]
? BehaviorOutput<Last>
: never;
/**
* Computes the behavior type returned by `pipe`.
*
* @typeParam Steps - Non-empty tuple of compatible behaviors.
*/
type PipeResult<Steps extends readonly [AnyBehavior, ...AnyBehavior[]]> = Behavior<
BehaviorInput<Steps[0]>,
LastOutput<Steps>,
BehaviorError<Steps[number]>,
BehaviorContext<Steps[number]>
>;
/**
* Validates that each pipe step accepts the previous step's output.
*
* @typeParam Steps - Tuple of behaviors to validate.
*/
type ValidPipe<Steps extends readonly AnyBehavior[]> = Steps extends readonly [
infer First extends AnyBehavior,
infer Second extends AnyBehavior,
...infer Rest extends AnyBehavior[],
]
? BehaviorOutput<First> extends BehaviorInput<Second>
? readonly [First, ...ValidPipe<readonly [Second, ...Rest]>]
: never
: Steps;
/**
* Composes behaviors from top to bottom.
*
* Each step runs only when the previous step succeeds. The output type of each step must be
* assignable to the input type of the next step.
*
* @typeParam Steps - Non-empty tuple of compatible behaviors.
* @param steps - Behaviors to compose in execution order.
* @returns A behavior from the first input type to the last output type.
*
* @example
* ```ts
* const workflow = pipe(loadUser, map(loadUserDetails, (details) => details.email));
* ```
*/
export function pipe<const Steps extends readonly [AnyBehavior, ...AnyBehavior[]]>(
...steps: Steps & ValidPipe<Steps>
): PipeResult<Steps> {
const [first, ...rest] = steps;
const link = sequence as unknown as (first: AnyBehavior, second: AnyBehavior) => AnyBehavior;
return rest.reduce((current, next) => link(current, next), first) as unknown as PipeResult<Steps>;
}