fp-pack AI Agent Skills
Document Version: {{version}}
Additional materials (optional):
- constraints/ (rules, mistakes, troubleshooting)
- reference/ (composition, currying, TypeScript inference)
- examples/ (quick examples)
⚠️ Activation Condition (Read First)
These guidelines apply only when fp-pack is installed in the current project.
Before following this document:
- Check
package.json for fp-pack in dependencies/devDependencies
- Check
node_modules/fp-pack exists
- Check existing code imports from
fp-pack or fp-pack/stream
If fp-pack is not installed, use the project's existing conventions. Do not suggest adding fp-pack unless the user asks.
Core Rules (Keep In Memory)
- Use
pipe/pipeAsync for 2+ steps; for a single step, call the function directly.
- Use
pipeStrict/pipeAsyncStrict when you want stricter mismatch detection; otherwise stick to pipe/pipeAsync.
- Prefer value-first:
pipe(value, ...) / pipeAsync(value, ...) runs immediately and improves inference (the input anchors types). Use functions-first only when you need a reusable pipeline.
- If the first arg is a function, it's treated as composition; wrap function values with
from().
- Keep pipeline functions unary; prefer data-last, curried helpers.
map/filter are for arrays/iterables, not single values.
- Use
from() only for constants or 0-arg pipelines (including function values you need to pass as data). Otherwise pass data as the first argument.
- Use
pipeSideEffect* only when you need early exit; otherwise use pipe/pipeAsync.
- Never call
runPipeResult/matchSideEffect inside pipelines; call at boundaries.
- Prefer
isSideEffect for precise narrowing; runPipeResult for unwrapping (use generics if widened).
SideEffect is an instance type: use SideEffect<E> (not typeof SideEffect).
- If TS inference stalls in data-last generics, use
pipeHint or a tiny wrapper.
- Use
fp-pack/stream for large/lazy iterables; array/object utils for small/eager data.
- Keep DOM/imperative work at the edge; use fp-pack for data transforms.
- Avoid mutation; return new objects/arrays.
- When unsure, check
dist/index.d.ts or dist/stream/index.d.ts.
Note: For trivial one-liners, using native JS directly is fine.
Reach for fp-pack when composition adds clarity or reuse.
Keep pipelines short and readable.
Common Mistakes & Fixes (Top Issues)
Don't wrap data in zero-arg functions
// ❌ BAD
pipe(() => [1, 2, 3], filter((n: number) => n % 2 === 0));
// ✅ GOOD (value-first)
pipe([1, 2, 3], filter((n: number) => n % 2 === 0));
// ✅ GOOD (no-input pipeline)
pipe(from([1, 2, 3]), filter((n: number) => n % 2 === 0))();
ifElse/cond need total branches
const status = ifElse((n: number) => n > 0, from('ok'), from('fail'));
const label = cond<number, string>([
[(n) => n > 0, () => 'positive'],
[() => true, () => 'non-positive'] // default keeps it total
]);
map is for arrays/iterables (not single values)
const save = pipe(
(s: AppState) => JSON.stringify({ todos: s.todos, nextId: s.nextId }),
tap((json) => localStorage.setItem(STORAGE_KEY, json))
);
save(state);
SideEffect pipelines + runPipeResult belong at the boundary
const pipeline = pipeSideEffect(findUser, (user) => user.email);
const result = runPipeResult(pipeline(input)); // outside the pipeline
DOM APIs are imperative by nature—keep them outside or at the boundary (use tap for final effects).
Troubleshooting Type Errors (Fast Checks)
- Is every step unary? (Pipelines expect one input each.)
- Are you using array/iterable helpers (
map, filter, reduce) on non-arrays?
- Did you forget a default case in
cond ([() => true, () => ...])?
- Are you returning
SideEffect from a pipe pipeline? (Use pipeSideEffect*.)
- Are you calling
runPipeResult inside a pipeline? (Move it to the boundary.)
- Are you mixing async steps in
pipe instead of pipeAsync?
- Did a data-last generic fail to infer? (Use
pipeHint or a tiny wrapper.)
- Are you using
from() for constants only, not normal input?
- Is a DOM/imperative step inside the pipeline? (Move it to the edge or use
tap.)
- Check
dist/index.d.ts / dist/stream/index.d.ts for the expected signature.
If the error persists, reduce the pipeline to the smallest failing step and add types there first.
Quick Examples (3)
Example 1: User Data Processing Pipeline
import { pipe, filter, map, take, sortBy } from 'fp-pack';
const result = pipe(
users,
filter((user: User) => user.active),
sortBy((user) => -user.activityScore),
map((user) => user.name),
take(10)
);
Example 2: API Request with Early Exit
import { pipeAsyncSideEffect, SideEffect, runPipeResult } from 'fp-pack';
const result = runPipeResult(
await pipeAsyncSideEffect(
'user-123',
async (userId: string) => {
const res = await fetch(`/api/users/${userId}`);
return res.ok ? res : SideEffect.of(() => `HTTP ${res.status}`);
},
async (res) => res.json()
)
);
Example 3: Value-first pipeline
import { pipe, filter, map } from 'fp-pack';
const result = pipe(
[1, 2, 3, 4, 5],
filter((n: number) => n % 2 === 0),
map((n) => n * 2)
);
Core Composition
pipe (sync)
import { pipe, filter, map, take } from 'fp-pack';
const result = pipe(
users,
filter((u: User) => u.active),
map((u) => u.name),
take(10)
);
pipeAsync (async)
import { pipeAsync } from 'fp-pack';
const user = await pipeAsync(
userId,
async (id: string) => fetch(`/api/users/${id}`),
async (res) => res.json(),
(data) => data.user
);
Currying & Data-Last
Most multi-arg helpers are data-last and curried. Pair them with value-first pipe(value, ...) to anchor types:
- Good:
map(fn), filter(pred), replace(from, to), assoc('k', v), path(['a','b'])
- Single-arg helpers are already unary—just use them directly
TypeScript: Data-last Generic Inference
Some data-last helpers return a generic function whose type is only determined by the final data argument. Prefer value-first pipe(value, ...) so the input anchors generics; use hints when needed.
Quick fix (pipeHint or wrapper)
import { pipe, pipeHint, zip, some } from 'fp-pack';
// Prefer value-first to anchor generics
const values: number[] = [1, 2, 3];
const withValueFirst = pipe(
values,
zip([1, 2, 3]),
some(([a, b]) => a > b)
);
const withPipeHint = pipe(
pipeHint<number[], Array<[number, number]>>(zip([1, 2, 3])),
some(([a, b]) => a > b)
);
If you prefer, a tiny wrapper like (values) => zip([1, 2, 3], values) works too.
Utilities that may need a hint in data-last pipelines:
- Array:
chunk, drop, take, zip
- Object:
assoc, assocPath, dissocPath, evolve, mapValues, merge, mergeDeep, omit, path, pick, prop
- Async:
timeout
- Stream:
chunk, drop, take, zip
SideEffect Pattern (Use Only When Needed)
Most code should use pipe / pipeAsync. Use SideEffect-aware pipes only when you need early termination:
- validation pipelines that should stop early
- recoverable errors you want to model as data
- branching flows where you want to short-circuit
SideEffect-aware pipes
pipeSideEffect / pipeAsyncSideEffect: convenient, but may widen effects to any
pipeSideEffectStrict / pipeAsyncSideEffectStrict: preserves strict union effects (recommended)
Key functions
SideEffect.of(effectFn, label?)
isSideEffect(value) (type guard)
runPipeResult(result) (execute effect or return value; outside pipelines)
Example
import { pipeSideEffectStrict, SideEffect, isSideEffect, runPipeResult } from 'fp-pack';
const validate = (n: number) => (n > 0 ? n : SideEffect.of(() => 'NEG' as const));
const result = pipeSideEffectStrict(
-1,
validate,
(n) => n + 1
); // number | SideEffect<'NEG'>
if (isSideEffect(result)) {
const err = runPipeResult(result); // 'NEG'
} else {
// result is number
}
Type Safety Notes
pipeSideEffect/pipeAsyncSideEffect can widen effects to any in complex pipelines.
pipeSideEffectStrict/pipeAsyncSideEffectStrict preserve strict effect unions.
runPipeResult returns R when input is SideEffect<R>, but becomes any if the input is widened to SideEffect<any>/any.
- Prefer
isSideEffect for precise branch narrowing.
Stream Functions (fp-pack/stream)
Use stream utilities when:
- data is large or unbounded
- you want lazy evaluation
- you want to support
Iterable and AsyncIterable
If any input is async, the output is async. Use toAsync to normalize inputs when needed.
import { pipe } from 'fp-pack';
import { range, filter, map, take, toArray } from 'fp-pack/stream';
const result = pipe(
range(Infinity),
filter((n: number) => n % 2 === 0),
map((n) => n * n),
take(100),
toArray
);
Available Functions (Quick Index)
Composition
pipe, pipeStrict, pipeAsync, pipeAsyncStrict
- SideEffect-aware:
pipeSideEffect, pipeSideEffectStrict, pipeAsyncSideEffect, pipeAsyncSideEffectStrict
- Utilities:
from, tap, tap0, once, memoize, identity, constant, curry, compose
- SideEffect helpers:
SideEffect, isSideEffect, matchSideEffect, runPipeResult
Array
- Transforms:
map, filter, flatMap, reduce, scan
- Queries:
find, some, every
- Slicing:
take, drop, chunk
- Ordering:
sort, sortBy, groupBy, uniqBy
- Combining:
zip, concat, append, flatten
Object
- Access:
prop, path, propOr, pathOr
- Pick/drop:
pick, omit
- Updates:
assoc, assocPath, dissocPath
- Merge:
merge, mergeDeep
- Transforms:
mapValues, evolve
Control Flow
ifElse, when, unless, cond, guard, tryCatch
Async
retry, timeout, delay
debounce*, throttle
Stream (Lazy Iterables)
- Building:
range
- Transforms:
map, filter, flatMap, flatten
- Slicing:
take, drop, chunk
- Queries:
find, some, every, reduce
- Combining:
zip, concat
- Utilities:
toArray, toAsync
Others
- Math:
add, sub, mul, div, clamp
- String:
split, join, replace, trim
- Equality:
equals, isNil
- Debug:
assert, invariant
Micro-Patterns (Optional)
Boundary handling
const pipeline = pipeSideEffectStrict(validate, process);
export const handler = (data) => {
const result = pipeline(data);
if (isSideEffect(result)) return runPipeResult(result);
return result;
};
Value-first execution
const result = pipe(
[1, 2, 3, 4, 5],
filter((n: number) => n % 2 === 0),
map((n) => n * 10)
); // [20, 40]
from() for constants / 0-arg pipelines
const result = pipe(
from([1, 2, 3, 4, 5]),
filter((n: number) => n % 2 === 0),
map((n) => n * 10)
)(); // [20, 40]
Stream to array
const toIds = pipe(
filter((u: User) => u.active),
map((u) => u.id),
toArray
);
Object updates
const updateAccount = pipe(
assocPath(['profile', 'role'], 'member'),
merge({ updatedAt: Date.now() })
);
Decision Guide
- Is everything sync and pure? →
pipe
- Any step async? →
pipeAsync
- Need early-exit + typed effect unions? →
pipeSideEffectStrict / pipeAsyncSideEffectStrict
- Need early-exit but type precision doesn't matter? →
pipeSideEffect / pipeAsyncSideEffect
- Only one step? → call the function directly (no
pipe)
- Handling result at boundary? →
isSideEffect for branching, runPipeResult to unwrap
- Large/unbounded/iterable data? →
fp-pack/stream
Import Paths
- Main:
import { pipe, map, filter } from 'fp-pack'
- SideEffect:
import { pipeSideEffect, SideEffect } from 'fp-pack'
- Async:
import { pipeAsync, retry, timeout } from 'fp-pack'
- Stream:
import { map, filter, toArray } from 'fp-pack/stream'
Quick Signature Lookup (When Unsure)
If TypeScript inference is stuck or you need to verify a function signature:
In fp-pack project:
- Main types:
dist/index.d.ts
- Stream types:
dist/stream/index.d.ts
In consumer project:
- Main types:
node_modules/fp-pack/dist/index.d.ts
- Stream types:
node_modules/fp-pack/dist/stream/index.d.ts
Summary
Default to value-first pipe / pipeAsync for inference, keep helpers data-last and unary, switch to stream/* when laziness matters, and reserve SideEffect-aware pipelines for true early-exit flows. Use functions-first only for reusable pipelines. Use isSideEffect for precise narrowing and call runPipeResult only at the boundary.
Source: superlucky84/fp-pack — distributed by TomeVault.
1---2name: fp-pack3description: Use when working in projects that use fp-pack; follow pipe, SideEffect, and curry guidelines.4---56# fp-pack AI Agent Skills78Document Version: {{version}}910Additional materials (optional):11- constraints/ (rules, mistakes, troubleshooting)12- reference/ (composition, currying, TypeScript inference)13- examples/ (quick examples)1415## ⚠️ Activation Condition (Read First)1617These guidelines apply **only when `fp-pack` is installed** in the current project.1819Before following this document:20- Check `package.json` for `fp-pack` in dependencies/devDependencies21- Check `node_modules/fp-pack` exists22- Check existing code imports from `fp-pack` or `fp-pack/stream`2324If `fp-pack` is **not** installed, use the project's existing conventions. Do **not** suggest adding fp-pack unless the user asks.2526---2728## Core Rules (Keep In Memory)2930- Use `pipe`/`pipeAsync` for 2+ steps; for a single step, call the function directly.31- Use `pipeStrict`/`pipeAsyncStrict` when you want stricter mismatch detection; otherwise stick to `pipe`/`pipeAsync`.32- Prefer value-first: `pipe(value, ...)` / `pipeAsync(value, ...)` runs immediately and improves inference (the input anchors types). Use functions-first only when you need a reusable pipeline.33- If the first arg is a function, it's treated as composition; wrap function values with `from()`.34- Keep pipeline functions **unary**; prefer data-last, curried helpers.35- `map`/`filter` are for arrays/iterables, not single values.36- Use `from()` only for constants or 0-arg pipelines (including function values you need to pass as data). Otherwise pass data as the first argument.37- Use `pipeSideEffect*` only when you need early exit; otherwise use `pipe`/`pipeAsync`.38- Never call `runPipeResult`/`matchSideEffect` inside pipelines; call at boundaries.39- Prefer `isSideEffect` for precise narrowing; `runPipeResult` for unwrapping (use generics if widened).40- `SideEffect` is an instance type: use `SideEffect<E>` (not `typeof SideEffect`).41- If TS inference stalls in data-last generics, use `pipeHint` or a tiny wrapper.42- Use `fp-pack/stream` for large/lazy iterables; array/object utils for small/eager data.43- Keep DOM/imperative work at the edge; use fp-pack for data transforms.44- Avoid mutation; return new objects/arrays.45- When unsure, check `dist/index.d.ts` or `dist/stream/index.d.ts`.4647Note: For trivial one-liners, using native JS directly is fine.48Reach for fp-pack when composition adds clarity or reuse.49Keep pipelines short and readable.5051---5253## Common Mistakes & Fixes (Top Issues)5455### Don't wrap data in zero-arg functions5657```ts58// ❌ BAD59pipe(() => [1, 2, 3], filter((n: number) => n % 2 === 0));6061// ✅ GOOD (value-first)62pipe([1, 2, 3], filter((n: number) => n % 2 === 0));6364// ✅ GOOD (no-input pipeline)65pipe(from([1, 2, 3]), filter((n: number) => n % 2 === 0))();66```6768### `ifElse`/`cond` need total branches6970```ts71const status = ifElse((n: number) => n > 0, from('ok'), from('fail'));7273const label = cond<number, string>([74 [(n) => n > 0, () => 'positive'],75 [() => true, () => 'non-positive'] // default keeps it total76]);77```7879### `map` is for arrays/iterables (not single values)8081```ts82const save = pipe(83 (s: AppState) => JSON.stringify({ todos: s.todos, nextId: s.nextId }),84 tap((json) => localStorage.setItem(STORAGE_KEY, json))85);86save(state);87```8889### SideEffect pipelines + `runPipeResult` belong at the boundary9091```ts92const pipeline = pipeSideEffect(findUser, (user) => user.email);93const result = runPipeResult(pipeline(input)); // outside the pipeline94```9596DOM APIs are imperative by nature—keep them outside or at the boundary (use `tap` for final effects).9798---99100## Troubleshooting Type Errors (Fast Checks)101102- Is every step unary? (Pipelines expect one input each.)103- Are you using array/iterable helpers (`map`, `filter`, `reduce`) on non-arrays?104- Did you forget a default case in `cond` (`[() => true, () => ...]`)?105- Are you returning `SideEffect` from a `pipe` pipeline? (Use `pipeSideEffect*`.)106- Are you calling `runPipeResult` inside a pipeline? (Move it to the boundary.)107- Are you mixing async steps in `pipe` instead of `pipeAsync`?108- Did a data-last generic fail to infer? (Use `pipeHint` or a tiny wrapper.)109- Are you using `from()` for constants only, not normal input?110- Is a DOM/imperative step inside the pipeline? (Move it to the edge or use `tap`.)111- Check `dist/index.d.ts` / `dist/stream/index.d.ts` for the expected signature.112113If the error persists, reduce the pipeline to the smallest failing step and add types there first.114115---116117## Quick Examples (3)118119### Example 1: User Data Processing Pipeline120121```ts122import { pipe, filter, map, take, sortBy } from 'fp-pack';123124const result = pipe(125 users,126 filter((user: User) => user.active),127 sortBy((user) => -user.activityScore),128 map((user) => user.name),129 take(10)130);131```132133### Example 2: API Request with Early Exit134135```ts136import { pipeAsyncSideEffect, SideEffect, runPipeResult } from 'fp-pack';137138const result = runPipeResult(139 await pipeAsyncSideEffect(140 'user-123',141 async (userId: string) => {142 const res = await fetch(`/api/users/${userId}`);143 return res.ok ? res : SideEffect.of(() => `HTTP ${res.status}`);144 },145 async (res) => res.json()146 )147);148```149150### Example 3: Value-first pipeline151152```ts153import { pipe, filter, map } from 'fp-pack';154155const result = pipe(156 [1, 2, 3, 4, 5],157 filter((n: number) => n % 2 === 0),158 map((n) => n * 2)159);160```161162---163164## Core Composition165166### `pipe` (sync)167168```ts169import { pipe, filter, map, take } from 'fp-pack';170171const result = pipe(172 users,173 filter((u: User) => u.active),174 map((u) => u.name),175 take(10)176);177```178179### `pipeAsync` (async)180181```ts182import { pipeAsync } from 'fp-pack';183184const user = await pipeAsync(185 userId,186 async (id: string) => fetch(`/api/users/${id}`),187 async (res) => res.json(),188 (data) => data.user189);190```191192---193194## Currying & Data-Last195196Most multi-arg helpers are **data-last** and **curried**. Pair them with value-first `pipe(value, ...)` to anchor types:197- Good: `map(fn)`, `filter(pred)`, `replace(from, to)`, `assoc('k', v)`, `path(['a','b'])`198- Single-arg helpers are already unary—just use them directly199200---201202## TypeScript: Data-last Generic Inference203204Some data-last helpers return a **generic function** whose type is only determined by the final data argument. Prefer value-first `pipe(value, ...)` so the input anchors generics; use hints when needed.205206### Quick fix (pipeHint or wrapper)207208```ts209import { pipe, pipeHint, zip, some } from 'fp-pack';210211// Prefer value-first to anchor generics212const values: number[] = [1, 2, 3];213const withValueFirst = pipe(214 values,215 zip([1, 2, 3]),216 some(([a, b]) => a > b)217);218219const withPipeHint = pipe(220 pipeHint<number[], Array<[number, number]>>(zip([1, 2, 3])),221 some(([a, b]) => a > b)222);223```224225If you prefer, a tiny wrapper like `(values) => zip([1, 2, 3], values)` works too.226227**Utilities that may need a hint in data-last pipelines:**228- Array: `chunk`, `drop`, `take`, `zip`229- Object: `assoc`, `assocPath`, `dissocPath`, `evolve`, `mapValues`, `merge`, `mergeDeep`, `omit`, `path`, `pick`, `prop`230- Async: `timeout`231- Stream: `chunk`, `drop`, `take`, `zip`232233---234235## SideEffect Pattern (Use Only When Needed)236237Most code should use `pipe` / `pipeAsync`. Use SideEffect-aware pipes only when you need **early termination**:238- validation pipelines that should stop early239- recoverable errors you want to model as data240- branching flows where you want to short-circuit241242### SideEffect-aware pipes243- `pipeSideEffect` / `pipeAsyncSideEffect`: convenient, but may widen effects to `any`244- `pipeSideEffectStrict` / `pipeAsyncSideEffectStrict`: preserves strict union effects (recommended)245246### Key functions247- `SideEffect.of(effectFn, label?)`248- `isSideEffect(value)` (type guard)249- `runPipeResult(result)` (execute effect or return value; **outside** pipelines)250251### Example252253```ts254import { pipeSideEffectStrict, SideEffect, isSideEffect, runPipeResult } from 'fp-pack';255256const validate = (n: number) => (n > 0 ? n : SideEffect.of(() => 'NEG' as const));257const result = pipeSideEffectStrict(258 -1,259 validate,260 (n) => n + 1261); // number | SideEffect<'NEG'>262263if (isSideEffect(result)) {264 const err = runPipeResult(result); // 'NEG'265} else {266 // result is number267}268```269270### Type Safety Notes271272- `pipeSideEffect`/`pipeAsyncSideEffect` can widen effects to `any` in complex pipelines.273- `pipeSideEffectStrict`/`pipeAsyncSideEffectStrict` preserve strict effect unions.274- `runPipeResult` returns `R` when input is `SideEffect<R>`, but becomes `any` if the input is widened to `SideEffect<any>`/`any`.275- Prefer `isSideEffect` for precise branch narrowing.276277---278279## Stream Functions (`fp-pack/stream`)280281Use stream utilities when:282- data is large or unbounded283- you want lazy evaluation284- you want to support `Iterable` and `AsyncIterable`285286If any input is async, the output is async. Use `toAsync` to normalize inputs when needed.287288```ts289import { pipe } from 'fp-pack';290import { range, filter, map, take, toArray } from 'fp-pack/stream';291292const result = pipe(293 range(Infinity),294 filter((n: number) => n % 2 === 0),295 map((n) => n * n),296 take(100),297 toArray298);299```300301---302303## Available Functions (Quick Index)304305### Composition306- `pipe`, `pipeStrict`, `pipeAsync`, `pipeAsyncStrict`307- SideEffect-aware: `pipeSideEffect`, `pipeSideEffectStrict`, `pipeAsyncSideEffect`, `pipeAsyncSideEffectStrict`308- Utilities: `from`, `tap`, `tap0`, `once`, `memoize`, `identity`, `constant`, `curry`, `compose`309- SideEffect helpers: `SideEffect`, `isSideEffect`, `matchSideEffect`, `runPipeResult`310311### Array312- Transforms: `map`, `filter`, `flatMap`, `reduce`, `scan`313- Queries: `find`, `some`, `every`314- Slicing: `take`, `drop`, `chunk`315- Ordering: `sort`, `sortBy`, `groupBy`, `uniqBy`316- Combining: `zip`, `concat`, `append`, `flatten`317318### Object319- Access: `prop`, `path`, `propOr`, `pathOr`320- Pick/drop: `pick`, `omit`321- Updates: `assoc`, `assocPath`, `dissocPath`322- Merge: `merge`, `mergeDeep`323- Transforms: `mapValues`, `evolve`324325### Control Flow326- `ifElse`, `when`, `unless`, `cond`, `guard`, `tryCatch`327328### Async329- `retry`, `timeout`, `delay`330- `debounce*`, `throttle`331332### Stream (Lazy Iterables)333- Building: `range`334- Transforms: `map`, `filter`, `flatMap`, `flatten`335- Slicing: `take`, `drop`, `chunk`336- Queries: `find`, `some`, `every`, `reduce`337- Combining: `zip`, `concat`338- Utilities: `toArray`, `toAsync`339340### Others341- Math: `add`, `sub`, `mul`, `div`, `clamp`342- String: `split`, `join`, `replace`, `trim`343- Equality: `equals`, `isNil`344- Debug: `assert`, `invariant`345346---347348## Micro-Patterns (Optional)349350### Boundary handling351352```ts353const pipeline = pipeSideEffectStrict(validate, process);354355export const handler = (data) => {356 const result = pipeline(data);357 if (isSideEffect(result)) return runPipeResult(result);358 return result;359};360```361362### Value-first execution363364```ts365const result = pipe(366 [1, 2, 3, 4, 5],367 filter((n: number) => n % 2 === 0),368 map((n) => n * 10)369); // [20, 40]370```371372### from() for constants / 0-arg pipelines373374```ts375const result = pipe(376 from([1, 2, 3, 4, 5]),377 filter((n: number) => n % 2 === 0),378 map((n) => n * 10)379)(); // [20, 40]380```381382### Stream to array383384```ts385const toIds = pipe(386 filter((u: User) => u.active),387 map((u) => u.id),388 toArray389);390```391392### Object updates393394```ts395const updateAccount = pipe(396 assocPath(['profile', 'role'], 'member'),397 merge({ updatedAt: Date.now() })398);399```400401---402403## Decision Guide404405- Is everything sync and pure? → `pipe`406- Any step async? → `pipeAsync`407- Need early-exit + typed effect unions? → `pipeSideEffectStrict` / `pipeAsyncSideEffectStrict`408- Need early-exit but type precision doesn't matter? → `pipeSideEffect` / `pipeAsyncSideEffect`409- Only one step? → call the function directly (no `pipe`)410- Handling result at boundary? → `isSideEffect` for branching, `runPipeResult` to unwrap411- Large/unbounded/iterable data? → `fp-pack/stream`412413---414415## Import Paths416417- Main: `import { pipe, map, filter } from 'fp-pack'`418- SideEffect: `import { pipeSideEffect, SideEffect } from 'fp-pack'`419- Async: `import { pipeAsync, retry, timeout } from 'fp-pack'`420- Stream: `import { map, filter, toArray } from 'fp-pack/stream'`421422---423424## Quick Signature Lookup (When Unsure)425426If TypeScript inference is stuck or you need to verify a function signature:427428**In fp-pack project:**429- Main types: `dist/index.d.ts`430- Stream types: `dist/stream/index.d.ts`431432**In consumer project:**433- Main types: `node_modules/fp-pack/dist/index.d.ts`434- Stream types: `node_modules/fp-pack/dist/stream/index.d.ts`435436---437438## Summary439440Default to value-first `pipe` / `pipeAsync` for inference, keep helpers data-last and unary, switch to `stream/*` when laziness matters, and reserve SideEffect-aware pipelines for true early-exit flows. Use functions-first only for reusable pipelines. Use `isSideEffect` for precise narrowing and call `runPipeResult` only at the boundary.441442---443> Source: [superlucky84/fp-pack](https://github.com/superlucky84/fp-pack) — distributed by [TomeVault](https://tomevault.io).444<!-- tomevault:4.0:skill_md:2026-06-20 -->