원클릭으로
fp-pack
Use when working in projects that use fp-pack; follow pipe, SideEffect, and curry guidelines.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Use when working in projects that use fp-pack; follow pipe, SideEffect, and curry guidelines.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | fp-pack |
| description | Use when working in projects that use fp-pack; follow pipe, SideEffect, and curry guidelines. |
| metadata | {"short-description":"fp-pack workflow","version":{"[object Object]":null}} |
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.