Getting Started
bun add @stopcock/fpimport { flow, pipe } from '@stopcock/fp'import * as A from '@stopcock/fp/array'import * as O from '@stopcock/fp/option'import * as R from '@stopcock/fp/result'Pass a value through functions, left to right.
type User = { name: string; active: boolean; score: number }
const leaderboard = pipe( users, A.filter((u: User) => u.active && u.score > 0), A.sortBy((a: User, b: User) => b.score - a.score), A.take(10), A.map((u: User) => u.name),)Every operator is curried and data-last: A.take(10) returns a function
that takes the array, so it slots straight into pipe. There’s no
data-first form (A.take(users, 10)) any more.
Same as pipe but gives you a reusable function instead of running straight away.
const activeNames = flow( A.filter((u: User) => u.active), A.map((u: User) => u.name),)
activeNames(usersA)activeNames(usersB)Compiling pipelines
Section titled “Compiling pipelines”pipe and flow are plain function application. Nothing fuses at runtime
on its own:
const big = Array.from({ length: 1_000_000 }, (_, i) => i)
// three full passes over 1,000,000 items: filter, then map, then take(10)pipe( big, A.filter((x: number) => x % 7 === 0), A.map((x: number) => x * 2), A.take(10),)For a pipeline you’ve actually measured as hot, add @stopcock/fp-compiler
(a build-time Vite/Rollup/esbuild plugin). It recognizes this exact shape
and rewrites it into one inlined loop that stops after 10 matches, so the
call above only visits a few dozen items once it’s built. You don’t change
how you write the pipeline — run stopcock check to see which call sites
it recognized. See Fusion for how the compiler works.
Option and Result
Section titled “Option and Result”pipe( O.fromNullable(user.email), O.map((e: string) => e.split('@')[1]), O.getOrElse(() => 'unknown'),)
pipe( R.tryCatch(() => JSON.parse(input)), R.map((obj: { value: number }) => obj.value), R.getOrElse(() => null),)See Option & Result for the full API, or try the Cookbook for more patterns.