skills/fp-pack/SKILL.md
Use when working in projects that use fp-pack; follow pipe, SideEffect, and curry guidelines.
npx skillsauth add superlucky84/fp-pack fp-packInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Document Version: {{version}}
Additional materials (optional):
These guidelines apply only when fp-pack is installed in the current project.
Before following this document:
package.json for fp-pack in dependencies/devDependenciesnode_modules/fp-pack existsfp-pack or fp-pack/streamIf fp-pack is not installed, use the project's existing conventions. Do not suggest adding fp-pack unless the user asks.
pipe/pipeAsync for 2+ steps; for a single step, call the function directly.pipeStrict/pipeAsyncStrict when you want stricter mismatch detection; otherwise stick to pipe/pipeAsync.pipe(value, ...) / pipeAsync(value, ...) runs immediately and improves inference (the input anchors types). Use functions-first only when you need a reusable pipeline.from().map/filter are for arrays/iterables, not single values.from() only for constants or 0-arg pipelines (including function values you need to pass as data). Otherwise pass data as the first argument.pipeSideEffect* only when you need early exit; otherwise use pipe/pipeAsync.runPipeResult/matchSideEffect inside pipelines; call at boundaries.isSideEffect for precise narrowing; runPipeResult for unwrapping (use generics if widened).SideEffect is an instance type: use SideEffect<E> (not typeof SideEffect).pipeHint or a tiny wrapper.fp-pack/stream for large/lazy iterables; array/object utils for small/eager data.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.
// ❌ 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 branchesconst 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);
runPipeResult belong at the boundaryconst 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).
map, filter, reduce) on non-arrays?cond ([() => true, () => ...])?SideEffect from a pipe pipeline? (Use pipeSideEffect*.)runPipeResult inside a pipeline? (Move it to the boundary.)pipe instead of pipeAsync?pipeHint or a tiny wrapper.)from() for constants only, not normal input?tap.)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.
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)
);
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()
)
);
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)
);
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
);
Most multi-arg helpers are data-last and curried. Pair them with value-first pipe(value, ...) to anchor types:
map(fn), filter(pred), replace(from, to), assoc('k', v), path(['a','b'])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.
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:
chunk, drop, take, zipassoc, assocPath, dissocPath, evolve, mapValues, merge, mergeDeep, omit, path, pick, proptimeoutchunk, drop, take, zipMost code should use pipe / pipeAsync. Use SideEffect-aware pipes only when you need early termination:
pipeSideEffect / pipeAsyncSideEffect: convenient, but may widen effects to anypipeSideEffectStrict / pipeAsyncSideEffectStrict: preserves strict union effects (recommended)SideEffect.of(effectFn, label?)isSideEffect(value) (type guard)runPipeResult(result) (execute effect or return value; outside pipelines)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
}
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.isSideEffect for precise branch narrowing.fp-pack/stream)Use stream utilities when:
Iterable and AsyncIterableIf 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
);
pipe, pipeStrict, pipeAsync, pipeAsyncStrictpipeSideEffect, pipeSideEffectStrict, pipeAsyncSideEffect, pipeAsyncSideEffectStrictfrom, tap, tap0, once, memoize, identity, constant, curry, composeSideEffect, isSideEffect, matchSideEffect, runPipeResultmap, filter, flatMap, reduce, scanfind, some, everytake, drop, chunksort, sortBy, groupBy, uniqByzip, concat, append, flattenprop, path, propOr, pathOrpick, omitassoc, assocPath, dissocPathmerge, mergeDeepmapValues, evolveifElse, when, unless, cond, guard, tryCatchretry, timeout, delaydebounce*, throttlerangemap, filter, flatMap, flattentake, drop, chunkfind, some, every, reducezip, concattoArray, toAsyncadd, sub, mul, div, clampsplit, join, replace, trimequals, isNilassert, invariantconst pipeline = pipeSideEffectStrict(validate, process);
export const handler = (data) => {
const result = pipeline(data);
if (isSideEffect(result)) return runPipeResult(result);
return result;
};
const result = pipe(
[1, 2, 3, 4, 5],
filter((n: number) => n % 2 === 0),
map((n) => n * 10)
); // [20, 40]
const result = pipe(
from([1, 2, 3, 4, 5]),
filter((n: number) => n % 2 === 0),
map((n) => n * 10)
)(); // [20, 40]
const toIds = pipe(
filter((u: User) => u.active),
map((u) => u.id),
toArray
);
const updateAccount = pipe(
assocPath(['profile', 'role'], 'member'),
merge({ updatedAt: Date.now() })
);
pipepipeAsyncpipeSideEffectStrict / pipeAsyncSideEffectStrictpipeSideEffect / pipeAsyncSideEffectpipe)isSideEffect for branching, runPipeResult to unwrapfp-pack/streamimport { pipe, map, filter } from 'fp-pack'import { pipeSideEffect, SideEffect } from 'fp-pack'import { pipeAsync, retry, timeout } from 'fp-pack'import { map, filter, toArray } from 'fp-pack/stream'If TypeScript inference is stuck or you need to verify a function signature:
In fp-pack project:
dist/index.d.tsdist/stream/index.d.tsIn consumer project:
node_modules/fp-pack/dist/index.d.tsnode_modules/fp-pack/dist/stream/index.d.tsDefault 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.
documentation
Fetch GitHub issues, spawn sub-agents to implement fixes and open PRs, then monitor and address PR review comments. Usage: /gh-issues [owner/repo] [--label bug] [--limit 5] [--milestone v1.0] [--assignee @me] [--fork user/repo] [--watch] [--interval 5] [--reviews-only] [--cron] [--dry-run] [--model glm-5] [--notify-channel -1002381931352]
documentation
Maintain the OpenClaw memory wiki vault with deterministic pages, managed blocks, and source-backed updates.
documentation
Feishu knowledge base navigation. Activate when user mentions knowledge base, wiki, or wiki links.
documentation
Feishu permission management for documents and files. Activate when user mentions sharing, permissions, collaborators.