Обзор

Tap

Run a side effect for successful output without changing it.

compositionside-effects

Install

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

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

ts@lib/flow/tap.ts
import { type Awaitable } from "./core/result";
import { type Behavior, type RuntimeContext } from "./core/runtime";
import { pipe } from "./pipe";
import { task } from "./task";
/**
* Runs a side effect after source success without changing the output.
*
* Source failures pass through unchanged. Thrown effect errors are captured by the underlying task
* runtime as `Thrown`.
*
* @typeParam I - Source input type.
* @typeParam O - Source output type.
* @typeParam E - Source domain error type.
* @typeParam C - Custom context type.
* @param source - Behavior whose successful output should be observed.
* @param effect - Side-effect callback invoked only after source success.
* @returns A behavior with the same input, output, and domain error types.
*
* @example
* ```ts
* const auditedLoad = tap(loadUser, (user) => audit("loaded", user.id));
* ```
*/
export function tap<I, O, E, C extends object>(
source: Behavior<I, O, E, C>,
effect: (output: O, context: RuntimeContext<C>) => Awaitable<void>,
): Behavior<I, O, E, C> {
return pipe(
source,
task<O, O, never, C>(async (output, context) => {
await effect(output, context);
return context.ok(output);
}),
);
}

Update the import paths to match your project setup.

Run a side effect for successful output without changing the output value.

Usage

tap is for logging, metrics, cache writes, and other success-only side effects. It keeps the original success value in the pipeline.

Use it when:

  • Add observability to a successful step.
  • Trigger a side effect without changing the value passed downstream.
  • Keep domain error types unchanged.
import { tap } from "@/lib/flow/tap";
import { task } from "@/lib/flow/task";
const saveUser = task((user: { id: string }) =>
fetch("/api/users", {
method: "POST",
body: JSON.stringify(user),
}),
);
const saveAndTrack = tap(saveUser, (_result, context) => {
console.log("saved", context.signal.aborted);
});

Behavior

tap runs the source, calls the effect on success, then returns the original output value.

Source failures pass through unchanged. Side-effect exceptions become runtime errors; cancellation remains cooperative through the runtime context.

Types

The source input, output, context, and domain error types are preserved.

Import from @/lib/flow/tap.

Composition Notes

Use tap for logging, metrics, auditing, and cache warming.

Do not use tap when the side effect determines the next value; use task or map.

Source

ts@lib/flow/tap.ts
import { type Awaitable } from "./core/result";
import { type Behavior, type RuntimeContext } from "./core/runtime";
import { pipe } from "./pipe";
import { task } from "./task";
/**
* Runs a side effect after source success without changing the output.
*
* Source failures pass through unchanged. Thrown effect errors are captured by the underlying task
* runtime as `Thrown`.
*
* @typeParam I - Source input type.
* @typeParam O - Source output type.
* @typeParam E - Source domain error type.
* @typeParam C - Custom context type.
* @param source - Behavior whose successful output should be observed.
* @param effect - Side-effect callback invoked only after source success.
* @returns A behavior with the same input, output, and domain error types.
*
* @example
* ```ts
* const auditedLoad = tap(loadUser, (user) => audit("loaded", user.id));
* ```
*/
export function tap<I, O, E, C extends object>(
source: Behavior<I, O, E, C>,
effect: (output: O, context: RuntimeContext<C>) => Awaitable<void>,
): Behavior<I, O, E, C> {
return pipe(
source,
task<O, O, never, C>(async (output, context) => {
await effect(output, context);
return context.ok(output);
}),
);
}