Install
pnpm dlx shadcn@latest add https://archyn.net/r/pipe.jsonCopy the files below into the target paths shown in each tab.
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>;}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); };}import { type Awaitable, type Err, type Result, err } from "./result";
/** * Runtime error returned when execution is cancelled through an `AbortSignal`. * * Cancellation is cooperative. Flow helpers check the signal before and after async work and pass * linked signals to child behaviors where appropriate. */export type Cancelled = { kind: "cancelled"; reason?: unknown;};
/** * Runtime error returned when user code throws. * * Thrown exceptions are kept separate from expected domain errors so recovery helpers do not hide * programming or platform failures. */export type Thrown = { kind: "thrown"; error: unknown;};
/** * Runtime-level failure union added around every behavior's domain error type. * * Domain errors describe expected application failures. `RuntimeError` describes cancellation or * unexpected thrown exceptions observed by the Flow runtime. */export type RuntimeError = Cancelled | Thrown;
/** * Execution context passed to every behavior. * * The context always includes an `AbortSignal` and may include application-specific fields. * * @typeParam C - Custom context object type. */export type RuntimeContext<C extends object = {}> = C & { signal: AbortSignal;};
/** * Executable Flow workflow unit. * * A behavior receives typed input, reads runtime context, and resolves to a `Result` containing * either typed output, typed domain error, or a runtime error. * * @typeParam I - Input type. * @typeParam O - Output type. * @typeParam E - Expected domain error type. * @typeParam C - Custom context type. */export type Behavior<I, O, E = unknown, C extends object = {}> = ( input: I, context: RuntimeContext<C>,) => Promise<Result<O, E | RuntimeError>>;
/** * Extracts the input type from a behavior. * * @typeParam T - Behavior type to inspect. */export type BehaviorInput<T> = T extends Behavior<infer I, infer _O, infer _E, infer _C> ? I : never;
/** * Extracts the output type from a behavior. * * @typeParam T - Behavior type to inspect. */export type BehaviorOutput<T> = T extends Behavior<infer _I, infer O, infer _E, infer _C> ? O : never;
/** * Extracts the expected domain error type from a behavior. * * @typeParam T - Behavior type to inspect. */export type BehaviorError<T> = T extends Behavior<infer _I, infer _O, infer E, infer _C> ? E : never;
/** * Extracts the custom context type from a behavior. * * @typeParam T - Behavior type to inspect. */export type BehaviorContext<T> = T extends Behavior<infer _I, infer _O, infer _E, infer C> ? C : never;
/** * Executes a behavior with runtime error protection. * * The runner checks cancellation before and after behavior execution and converts thrown * exceptions into `Thrown` results. * * @typeParam I - Input type. * @typeParam O - Output type. * @typeParam E - Expected domain error type. * @typeParam C - Custom context type. * @param behavior - Behavior to execute. * @param input - Input passed to the behavior. * @param context - Runtime context containing an `AbortSignal`. * @returns The behavior result, cancellation result, or thrown runtime result. */export async function runBehavior<I, O, E, C extends object>( behavior: Behavior<I, O, E, C>, input: I, context: RuntimeContext<C>,): Promise<Result<O, E | RuntimeError>> { if (context.signal.aborted) { return resultFromSignal(context.signal); }
try { const result = await behavior(input, context);
if (context.signal.aborted) { return resultFromSignal(context.signal); }
return result; } catch (error) { if (context.signal.aborted) { return resultFromSignal(context.signal); }
return thrown(error); }}
/** * Defines a behavior from a callback that returns a `Result`. * * Use `define` for low-level helpers that already operate in the result channel. Most application * code should use `task`, which also accepts plain successful values. * * @typeParam I - Input type. * @typeParam O - Output type. * @typeParam E - Expected domain error type. * @typeParam C - Custom context type. * @param run - Callback invoked for each behavior execution. * @returns A behavior protected by cancellation and thrown-exception handling. */export function define<I, O, E = unknown, C extends object = {}>( run: (input: I, context: RuntimeContext<C>) => Awaitable<Result<O, E>>,): Behavior<I, O, E, C> { return async (input, context) => { if (context.signal.aborted) { return resultFromSignal(context.signal); }
try { const result = await run(input, context);
if (context.signal.aborted) { return resultFromSignal(context.signal); }
return result; } catch (error) { if (context.signal.aborted) { return cancelled(context.signal.reason); }
return thrown(error); } };}
/** * Runs a behavior with an optional application context. * * If no signal is provided, Flow creates a fresh non-aborted signal. Expected failures remain in * the returned `Result`; thrown exceptions are converted to `Thrown`. * * @typeParam I - Input type. * @typeParam O - Output type. * @typeParam E - Expected domain error type. * @typeParam C - Custom context type. * @param behavior - Behavior to execute. * @param input - Input passed to the behavior. * @param context - Optional custom context and optional `AbortSignal`. * @returns A result containing behavior output, domain error, or runtime error. * * @example * ```ts * const result = await run(loadUser, "user_1"); * ``` */export async function run<I, O, E, C extends object = {}>( behavior: Behavior<I, O, E, C>, input: I, context: C & { signal?: AbortSignal } = {} as C & { signal?: AbortSignal },): Promise<Result<O, E | RuntimeError>> { const { signal = new AbortController().signal, ...rest } = context;
return runBehavior(behavior, input, { ...(rest as C), signal, });}
/** * Creates a cancellation result. * * @param reason - Optional cancellation reason from an `AbortSignal`. * @returns A failed result containing `Cancelled`. */export function cancelled(reason?: unknown): Err<Cancelled> { if (reason === undefined) { return err({ kind: "cancelled" }); }
return err({ kind: "cancelled", reason });}
/** * Narrows an error value to `Cancelled`. * * @param error - Error value to inspect. * @returns `true` when the value is a cancellation runtime error. */export function isCancelled(error: unknown): error is Cancelled { return ( typeof error === "object" && error !== null && "kind" in error && error.kind === "cancelled" );}
/** * Creates a thrown-exception runtime result. * * @param error - Exception or thrown value. * @returns A failed result containing `Thrown`. */export function thrown(error: unknown): Err<Thrown> { return err({ kind: "thrown", error });}
/** * Narrows an error value to `Thrown`. * * @param error - Error value to inspect. * @returns `true` when the value is a thrown runtime error. */export function isThrown(error: unknown): error is Thrown { return typeof error === "object" && error !== null && "kind" in error && error.kind === "thrown";}
/** * Narrows an error value to a Flow runtime error. * * Recovery helpers use this guard so they only handle expected domain errors. * * @param error - Error value to inspect. * @returns `true` for `Cancelled` or `Thrown`. */export function isRuntimeError(error: unknown): error is RuntimeError { return isCancelled(error) || isThrown(error);}
/** * Converts an aborted signal into a cancellation result. * * @param signal - Aborted signal whose reason should be preserved. * @returns A failed result containing `Cancelled`. */export function resultFromSignal(signal: AbortSignal): Err<Cancelled> { return cancelled(signal.reason);}
/** * Replaces the signal on a runtime context. * * This advanced helper is used by timeout and parallel helpers when they create child abort * controllers. * * @typeParam C - Custom context type. * @param context - Existing runtime context. * @param signal - Replacement signal. * @returns A context with the same custom fields and the replacement signal. */export function contextWithSignal<C extends object>( context: RuntimeContext<C>, signal: AbortSignal,): RuntimeContext<C> { return { ...context, signal };}
/** * Aborts a controller only when it has not already been aborted. * * @param controller - Controller to abort. * @param reason - Optional abort reason. */export function abortController(controller: AbortController, reason?: unknown): void { if (!controller.signal.aborted) { controller.abort(reason); }}
/** * Links a parent signal to a child controller. * * The returned cleanup function removes the listener when the child work has settled. * * @param parent - Parent signal to observe. * @param controller - Child controller to abort when the parent aborts. * @returns Cleanup function that unlinks the abort listener. */export function linkAbortSignal(parent: AbortSignal, controller: AbortController): () => void { if (parent.aborted) { abortController(controller, parent.reason); return () => {}; }
const onAbort = () => abortController(controller, parent.reason); parent.addEventListener("abort", onAbort, { once: true });
return () => parent.removeEventListener("abort", onAbort);}
/** * Creates a child controller linked to a parent signal. * * This advanced helper powers timeout, race, and parallel execution helpers. * * @param parent - Parent signal to link from. * @returns The child controller and an unlink cleanup function. */export function createLinkedController(parent: AbortSignal): { controller: AbortController; unlink: () => void;} { const controller = new AbortController(); const unlink = linkAbortSignal(parent, controller);
return { controller, unlink };}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
Behaviorvalues. - 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.
Related APIs
Source
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>;}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); };}import { type Awaitable, type Err, type Result, err } from "./result";
/** * Runtime error returned when execution is cancelled through an `AbortSignal`. * * Cancellation is cooperative. Flow helpers check the signal before and after async work and pass * linked signals to child behaviors where appropriate. */export type Cancelled = { kind: "cancelled"; reason?: unknown;};
/** * Runtime error returned when user code throws. * * Thrown exceptions are kept separate from expected domain errors so recovery helpers do not hide * programming or platform failures. */export type Thrown = { kind: "thrown"; error: unknown;};
/** * Runtime-level failure union added around every behavior's domain error type. * * Domain errors describe expected application failures. `RuntimeError` describes cancellation or * unexpected thrown exceptions observed by the Flow runtime. */export type RuntimeError = Cancelled | Thrown;
/** * Execution context passed to every behavior. * * The context always includes an `AbortSignal` and may include application-specific fields. * * @typeParam C - Custom context object type. */export type RuntimeContext<C extends object = {}> = C & { signal: AbortSignal;};
/** * Executable Flow workflow unit. * * A behavior receives typed input, reads runtime context, and resolves to a `Result` containing * either typed output, typed domain error, or a runtime error. * * @typeParam I - Input type. * @typeParam O - Output type. * @typeParam E - Expected domain error type. * @typeParam C - Custom context type. */export type Behavior<I, O, E = unknown, C extends object = {}> = ( input: I, context: RuntimeContext<C>,) => Promise<Result<O, E | RuntimeError>>;
/** * Extracts the input type from a behavior. * * @typeParam T - Behavior type to inspect. */export type BehaviorInput<T> = T extends Behavior<infer I, infer _O, infer _E, infer _C> ? I : never;
/** * Extracts the output type from a behavior. * * @typeParam T - Behavior type to inspect. */export type BehaviorOutput<T> = T extends Behavior<infer _I, infer O, infer _E, infer _C> ? O : never;
/** * Extracts the expected domain error type from a behavior. * * @typeParam T - Behavior type to inspect. */export type BehaviorError<T> = T extends Behavior<infer _I, infer _O, infer E, infer _C> ? E : never;
/** * Extracts the custom context type from a behavior. * * @typeParam T - Behavior type to inspect. */export type BehaviorContext<T> = T extends Behavior<infer _I, infer _O, infer _E, infer C> ? C : never;
/** * Executes a behavior with runtime error protection. * * The runner checks cancellation before and after behavior execution and converts thrown * exceptions into `Thrown` results. * * @typeParam I - Input type. * @typeParam O - Output type. * @typeParam E - Expected domain error type. * @typeParam C - Custom context type. * @param behavior - Behavior to execute. * @param input - Input passed to the behavior. * @param context - Runtime context containing an `AbortSignal`. * @returns The behavior result, cancellation result, or thrown runtime result. */export async function runBehavior<I, O, E, C extends object>( behavior: Behavior<I, O, E, C>, input: I, context: RuntimeContext<C>,): Promise<Result<O, E | RuntimeError>> { if (context.signal.aborted) { return resultFromSignal(context.signal); }
try { const result = await behavior(input, context);
if (context.signal.aborted) { return resultFromSignal(context.signal); }
return result; } catch (error) { if (context.signal.aborted) { return resultFromSignal(context.signal); }
return thrown(error); }}
/** * Defines a behavior from a callback that returns a `Result`. * * Use `define` for low-level helpers that already operate in the result channel. Most application * code should use `task`, which also accepts plain successful values. * * @typeParam I - Input type. * @typeParam O - Output type. * @typeParam E - Expected domain error type. * @typeParam C - Custom context type. * @param run - Callback invoked for each behavior execution. * @returns A behavior protected by cancellation and thrown-exception handling. */export function define<I, O, E = unknown, C extends object = {}>( run: (input: I, context: RuntimeContext<C>) => Awaitable<Result<O, E>>,): Behavior<I, O, E, C> { return async (input, context) => { if (context.signal.aborted) { return resultFromSignal(context.signal); }
try { const result = await run(input, context);
if (context.signal.aborted) { return resultFromSignal(context.signal); }
return result; } catch (error) { if (context.signal.aborted) { return cancelled(context.signal.reason); }
return thrown(error); } };}
/** * Runs a behavior with an optional application context. * * If no signal is provided, Flow creates a fresh non-aborted signal. Expected failures remain in * the returned `Result`; thrown exceptions are converted to `Thrown`. * * @typeParam I - Input type. * @typeParam O - Output type. * @typeParam E - Expected domain error type. * @typeParam C - Custom context type. * @param behavior - Behavior to execute. * @param input - Input passed to the behavior. * @param context - Optional custom context and optional `AbortSignal`. * @returns A result containing behavior output, domain error, or runtime error. * * @example * ```ts * const result = await run(loadUser, "user_1"); * ``` */export async function run<I, O, E, C extends object = {}>( behavior: Behavior<I, O, E, C>, input: I, context: C & { signal?: AbortSignal } = {} as C & { signal?: AbortSignal },): Promise<Result<O, E | RuntimeError>> { const { signal = new AbortController().signal, ...rest } = context;
return runBehavior(behavior, input, { ...(rest as C), signal, });}
/** * Creates a cancellation result. * * @param reason - Optional cancellation reason from an `AbortSignal`. * @returns A failed result containing `Cancelled`. */export function cancelled(reason?: unknown): Err<Cancelled> { if (reason === undefined) { return err({ kind: "cancelled" }); }
return err({ kind: "cancelled", reason });}
/** * Narrows an error value to `Cancelled`. * * @param error - Error value to inspect. * @returns `true` when the value is a cancellation runtime error. */export function isCancelled(error: unknown): error is Cancelled { return ( typeof error === "object" && error !== null && "kind" in error && error.kind === "cancelled" );}
/** * Creates a thrown-exception runtime result. * * @param error - Exception or thrown value. * @returns A failed result containing `Thrown`. */export function thrown(error: unknown): Err<Thrown> { return err({ kind: "thrown", error });}
/** * Narrows an error value to `Thrown`. * * @param error - Error value to inspect. * @returns `true` when the value is a thrown runtime error. */export function isThrown(error: unknown): error is Thrown { return typeof error === "object" && error !== null && "kind" in error && error.kind === "thrown";}
/** * Narrows an error value to a Flow runtime error. * * Recovery helpers use this guard so they only handle expected domain errors. * * @param error - Error value to inspect. * @returns `true` for `Cancelled` or `Thrown`. */export function isRuntimeError(error: unknown): error is RuntimeError { return isCancelled(error) || isThrown(error);}
/** * Converts an aborted signal into a cancellation result. * * @param signal - Aborted signal whose reason should be preserved. * @returns A failed result containing `Cancelled`. */export function resultFromSignal(signal: AbortSignal): Err<Cancelled> { return cancelled(signal.reason);}
/** * Replaces the signal on a runtime context. * * This advanced helper is used by timeout and parallel helpers when they create child abort * controllers. * * @typeParam C - Custom context type. * @param context - Existing runtime context. * @param signal - Replacement signal. * @returns A context with the same custom fields and the replacement signal. */export function contextWithSignal<C extends object>( context: RuntimeContext<C>, signal: AbortSignal,): RuntimeContext<C> { return { ...context, signal };}
/** * Aborts a controller only when it has not already been aborted. * * @param controller - Controller to abort. * @param reason - Optional abort reason. */export function abortController(controller: AbortController, reason?: unknown): void { if (!controller.signal.aborted) { controller.abort(reason); }}
/** * Links a parent signal to a child controller. * * The returned cleanup function removes the listener when the child work has settled. * * @param parent - Parent signal to observe. * @param controller - Child controller to abort when the parent aborts. * @returns Cleanup function that unlinks the abort listener. */export function linkAbortSignal(parent: AbortSignal, controller: AbortController): () => void { if (parent.aborted) { abortController(controller, parent.reason); return () => {}; }
const onAbort = () => abortController(controller, parent.reason); parent.addEventListener("abort", onAbort, { once: true });
return () => parent.removeEventListener("abort", onAbort);}
/** * Creates a child controller linked to a parent signal. * * This advanced helper powers timeout, race, and parallel execution helpers. * * @param parent - Parent signal to link from. * @returns The child controller and an unlink cleanup function. */export function createLinkedController(parent: AbortSignal): { controller: AbortController; unlink: () => void;} { const controller = new AbortController(); const unlink = linkAbortSignal(parent, controller);
return { controller, unlink };}