fp-pack 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +664 -0
- package/dist/fp-pack-stream.umd.js +2 -0
- package/dist/fp-pack-stream.umd.js.map +1 -0
- package/dist/fp-pack.umd.js +2 -0
- package/dist/fp-pack.umd.js.map +1 -0
- package/dist/implement/array/append.d.ts +7 -0
- package/dist/implement/array/append.d.ts.map +1 -0
- package/dist/implement/array/append.mjs +9 -0
- package/dist/implement/array/append.mjs.map +1 -0
- package/dist/implement/array/chunk.d.ts +7 -0
- package/dist/implement/array/chunk.d.ts.map +1 -0
- package/dist/implement/array/chunk.mjs +15 -0
- package/dist/implement/array/chunk.mjs.map +1 -0
- package/dist/implement/array/concat.d.ts +7 -0
- package/dist/implement/array/concat.d.ts.map +1 -0
- package/dist/implement/array/concat.mjs +9 -0
- package/dist/implement/array/concat.mjs.map +1 -0
- package/dist/implement/array/drop.d.ts +7 -0
- package/dist/implement/array/drop.d.ts.map +1 -0
- package/dist/implement/array/drop.mjs +10 -0
- package/dist/implement/array/drop.mjs.map +1 -0
- package/dist/implement/array/dropWhile.d.ts +7 -0
- package/dist/implement/array/dropWhile.d.ts.map +1 -0
- package/dist/implement/array/dropWhile.mjs +13 -0
- package/dist/implement/array/dropWhile.mjs.map +1 -0
- package/dist/implement/array/every.d.ts +7 -0
- package/dist/implement/array/every.d.ts.map +1 -0
- package/dist/implement/array/every.mjs +9 -0
- package/dist/implement/array/every.mjs.map +1 -0
- package/dist/implement/array/filter.d.ts +7 -0
- package/dist/implement/array/filter.d.ts.map +1 -0
- package/dist/implement/array/filter.mjs +9 -0
- package/dist/implement/array/filter.mjs.map +1 -0
- package/dist/implement/array/find.d.ts +7 -0
- package/dist/implement/array/find.d.ts.map +1 -0
- package/dist/implement/array/find.mjs +9 -0
- package/dist/implement/array/find.mjs.map +1 -0
- package/dist/implement/array/flatMap.d.ts +7 -0
- package/dist/implement/array/flatMap.d.ts.map +1 -0
- package/dist/implement/array/flatMap.mjs +9 -0
- package/dist/implement/array/flatMap.mjs.map +1 -0
- package/dist/implement/array/flatten.d.ts +6 -0
- package/dist/implement/array/flatten.d.ts.map +1 -0
- package/dist/implement/array/flatten.mjs +7 -0
- package/dist/implement/array/flatten.mjs.map +1 -0
- package/dist/implement/array/flattenDeep.d.ts +6 -0
- package/dist/implement/array/flattenDeep.d.ts.map +1 -0
- package/dist/implement/array/flattenDeep.mjs +11 -0
- package/dist/implement/array/flattenDeep.mjs.map +1 -0
- package/dist/implement/array/groupBy.d.ts +7 -0
- package/dist/implement/array/groupBy.d.ts.map +1 -0
- package/dist/implement/array/groupBy.mjs +12 -0
- package/dist/implement/array/groupBy.mjs.map +1 -0
- package/dist/implement/array/head.d.ts +6 -0
- package/dist/implement/array/head.d.ts.map +1 -0
- package/dist/implement/array/head.mjs +7 -0
- package/dist/implement/array/head.mjs.map +1 -0
- package/dist/implement/array/index.d.ts +34 -0
- package/dist/implement/array/index.d.ts.map +1 -0
- package/dist/implement/array/init.d.ts +6 -0
- package/dist/implement/array/init.d.ts.map +1 -0
- package/dist/implement/array/init.mjs +7 -0
- package/dist/implement/array/init.mjs.map +1 -0
- package/dist/implement/array/last.d.ts +6 -0
- package/dist/implement/array/last.d.ts.map +1 -0
- package/dist/implement/array/last.mjs +8 -0
- package/dist/implement/array/last.mjs.map +1 -0
- package/dist/implement/array/map.d.ts +7 -0
- package/dist/implement/array/map.d.ts.map +1 -0
- package/dist/implement/array/map.mjs +9 -0
- package/dist/implement/array/map.mjs.map +1 -0
- package/dist/implement/array/partition.d.ts +7 -0
- package/dist/implement/array/partition.d.ts.map +1 -0
- package/dist/implement/array/partition.mjs +12 -0
- package/dist/implement/array/partition.mjs.map +1 -0
- package/dist/implement/array/prepend.d.ts +7 -0
- package/dist/implement/array/prepend.d.ts.map +1 -0
- package/dist/implement/array/prepend.mjs +9 -0
- package/dist/implement/array/prepend.mjs.map +1 -0
- package/dist/implement/array/range.d.ts +6 -0
- package/dist/implement/array/range.d.ts.map +1 -0
- package/dist/implement/array/range.mjs +12 -0
- package/dist/implement/array/range.mjs.map +1 -0
- package/dist/implement/array/reduce.d.ts +8 -0
- package/dist/implement/array/reduce.d.ts.map +1 -0
- package/dist/implement/array/reduce.mjs +9 -0
- package/dist/implement/array/reduce.mjs.map +1 -0
- package/dist/implement/array/scan.d.ts +8 -0
- package/dist/implement/array/scan.d.ts.map +1 -0
- package/dist/implement/array/scan.mjs +13 -0
- package/dist/implement/array/scan.mjs.map +1 -0
- package/dist/implement/array/some.d.ts +7 -0
- package/dist/implement/array/some.d.ts.map +1 -0
- package/dist/implement/array/some.mjs +11 -0
- package/dist/implement/array/some.mjs.map +1 -0
- package/dist/implement/array/sort.d.ts +7 -0
- package/dist/implement/array/sort.d.ts.map +1 -0
- package/dist/implement/array/sort.mjs +9 -0
- package/dist/implement/array/sort.mjs.map +1 -0
- package/dist/implement/array/sortBy.d.ts +7 -0
- package/dist/implement/array/sortBy.d.ts.map +1 -0
- package/dist/implement/array/sortBy.mjs +12 -0
- package/dist/implement/array/sortBy.mjs.map +1 -0
- package/dist/implement/array/tail.d.ts +6 -0
- package/dist/implement/array/tail.d.ts.map +1 -0
- package/dist/implement/array/tail.mjs +7 -0
- package/dist/implement/array/tail.mjs.map +1 -0
- package/dist/implement/array/take.d.ts +7 -0
- package/dist/implement/array/take.d.ts.map +1 -0
- package/dist/implement/array/take.mjs +9 -0
- package/dist/implement/array/take.mjs.map +1 -0
- package/dist/implement/array/takeWhile.d.ts +7 -0
- package/dist/implement/array/takeWhile.d.ts.map +1 -0
- package/dist/implement/array/takeWhile.mjs +15 -0
- package/dist/implement/array/takeWhile.mjs.map +1 -0
- package/dist/implement/array/uniq.d.ts +6 -0
- package/dist/implement/array/uniq.d.ts.map +1 -0
- package/dist/implement/array/uniq.mjs +10 -0
- package/dist/implement/array/uniq.mjs.map +1 -0
- package/dist/implement/array/uniqBy.d.ts +7 -0
- package/dist/implement/array/uniqBy.d.ts.map +1 -0
- package/dist/implement/array/uniqBy.mjs +14 -0
- package/dist/implement/array/uniqBy.mjs.map +1 -0
- package/dist/implement/array/unzip.d.ts +6 -0
- package/dist/implement/array/unzip.d.ts.map +1 -0
- package/dist/implement/array/unzip.mjs +10 -0
- package/dist/implement/array/unzip.mjs.map +1 -0
- package/dist/implement/array/zip.d.ts +7 -0
- package/dist/implement/array/zip.d.ts.map +1 -0
- package/dist/implement/array/zip.mjs +12 -0
- package/dist/implement/array/zip.mjs.map +1 -0
- package/dist/implement/array/zipIndex.d.ts +6 -0
- package/dist/implement/array/zipIndex.d.ts.map +1 -0
- package/dist/implement/array/zipIndex.mjs +7 -0
- package/dist/implement/array/zipIndex.mjs.map +1 -0
- package/dist/implement/array/zipWith.d.ts +8 -0
- package/dist/implement/array/zipWith.d.ts.map +1 -0
- package/dist/implement/array/zipWith.mjs +12 -0
- package/dist/implement/array/zipWith.mjs.map +1 -0
- package/dist/implement/async/debounce.d.ts +7 -0
- package/dist/implement/async/debounce.d.ts.map +1 -0
- package/dist/implement/async/debounce.mjs +16 -0
- package/dist/implement/async/debounce.mjs.map +1 -0
- package/dist/implement/async/debounceLeading.d.ts +7 -0
- package/dist/implement/async/debounceLeading.d.ts.map +1 -0
- package/dist/implement/async/debounceLeading.mjs +14 -0
- package/dist/implement/async/debounceLeading.mjs.map +1 -0
- package/dist/implement/async/debounceLeadingTrailing.d.ts +7 -0
- package/dist/implement/async/debounceLeadingTrailing.d.ts.map +1 -0
- package/dist/implement/async/debounceLeadingTrailing.mjs +19 -0
- package/dist/implement/async/debounceLeadingTrailing.mjs.map +1 -0
- package/dist/implement/async/delay.d.ts +4 -0
- package/dist/implement/async/delay.d.ts.map +1 -0
- package/dist/implement/async/delay.mjs +9 -0
- package/dist/implement/async/delay.mjs.map +1 -0
- package/dist/implement/async/index.d.ts +10 -0
- package/dist/implement/async/index.d.ts.map +1 -0
- package/dist/implement/async/pipeAsync.d.ts +14 -0
- package/dist/implement/async/pipeAsync.d.ts.map +1 -0
- package/dist/implement/async/pipeAsync.mjs +12 -0
- package/dist/implement/async/pipeAsync.mjs.map +1 -0
- package/dist/implement/async/pipeAsyncSideEffect.d.ts +21 -0
- package/dist/implement/async/pipeAsyncSideEffect.d.ts.map +1 -0
- package/dist/implement/async/pipeAsyncSideEffect.mjs +16 -0
- package/dist/implement/async/pipeAsyncSideEffect.mjs.map +1 -0
- package/dist/implement/async/retry.d.ts +7 -0
- package/dist/implement/async/retry.d.ts.map +1 -0
- package/dist/implement/async/retry.mjs +19 -0
- package/dist/implement/async/retry.mjs.map +1 -0
- package/dist/implement/async/throttle.d.ts +7 -0
- package/dist/implement/async/throttle.d.ts.map +1 -0
- package/dist/implement/async/throttle.mjs +18 -0
- package/dist/implement/async/throttle.mjs.map +1 -0
- package/dist/implement/async/timeout.d.ts +7 -0
- package/dist/implement/async/timeout.d.ts.map +1 -0
- package/dist/implement/async/timeout.mjs +16 -0
- package/dist/implement/async/timeout.mjs.map +1 -0
- package/dist/implement/composition/complement.d.ts +7 -0
- package/dist/implement/composition/complement.d.ts.map +1 -0
- package/dist/implement/composition/complement.mjs +7 -0
- package/dist/implement/composition/complement.mjs.map +1 -0
- package/dist/implement/composition/compose.d.ts +16 -0
- package/dist/implement/composition/compose.d.ts.map +1 -0
- package/dist/implement/composition/compose.mjs +7 -0
- package/dist/implement/composition/compose.mjs.map +1 -0
- package/dist/implement/composition/constant.d.ts +6 -0
- package/dist/implement/composition/constant.d.ts.map +1 -0
- package/dist/implement/composition/constant.mjs +7 -0
- package/dist/implement/composition/constant.mjs.map +1 -0
- package/dist/implement/composition/curry.d.ts +32 -0
- package/dist/implement/composition/curry.d.ts.map +1 -0
- package/dist/implement/composition/curry.mjs +8 -0
- package/dist/implement/composition/curry.mjs.map +1 -0
- package/dist/implement/composition/flip.d.ts +10 -0
- package/dist/implement/composition/flip.d.ts.map +1 -0
- package/dist/implement/composition/flip.mjs +10 -0
- package/dist/implement/composition/flip.mjs.map +1 -0
- package/dist/implement/composition/identity.d.ts +6 -0
- package/dist/implement/composition/identity.d.ts.map +1 -0
- package/dist/implement/composition/identity.mjs +7 -0
- package/dist/implement/composition/identity.mjs.map +1 -0
- package/dist/implement/composition/index.d.ts +16 -0
- package/dist/implement/composition/index.d.ts.map +1 -0
- package/dist/implement/composition/memoize.d.ts +7 -0
- package/dist/implement/composition/memoize.d.ts.map +1 -0
- package/dist/implement/composition/memoize.mjs +16 -0
- package/dist/implement/composition/memoize.mjs.map +1 -0
- package/dist/implement/composition/once.d.ts +7 -0
- package/dist/implement/composition/once.d.ts.map +1 -0
- package/dist/implement/composition/once.mjs +10 -0
- package/dist/implement/composition/once.mjs.map +1 -0
- package/dist/implement/composition/partial.d.ts +6 -0
- package/dist/implement/composition/partial.d.ts.map +1 -0
- package/dist/implement/composition/partial.mjs +10 -0
- package/dist/implement/composition/partial.mjs.map +1 -0
- package/dist/implement/composition/pipe.d.ts +13 -0
- package/dist/implement/composition/pipe.d.ts.map +1 -0
- package/dist/implement/composition/pipe.mjs +7 -0
- package/dist/implement/composition/pipe.mjs.map +1 -0
- package/dist/implement/composition/pipe.type-test.d.ts +20 -0
- package/dist/implement/composition/pipe.type-test.d.ts.map +1 -0
- package/dist/implement/composition/pipeSideEffect.d.ts +17 -0
- package/dist/implement/composition/pipeSideEffect.d.ts.map +1 -0
- package/dist/implement/composition/pipeSideEffect.mjs +16 -0
- package/dist/implement/composition/pipeSideEffect.mjs.map +1 -0
- package/dist/implement/composition/sideEffect.d.ts +18 -0
- package/dist/implement/composition/sideEffect.d.ts.map +1 -0
- package/dist/implement/composition/sideEffect.mjs +27 -0
- package/dist/implement/composition/sideEffect.mjs.map +1 -0
- package/dist/implement/composition/tap.d.ts +6 -0
- package/dist/implement/composition/tap.d.ts.map +1 -0
- package/dist/implement/composition/tap.mjs +7 -0
- package/dist/implement/composition/tap.mjs.map +1 -0
- package/dist/implement/control/cond.d.ts +6 -0
- package/dist/implement/control/cond.d.ts.map +1 -0
- package/dist/implement/control/cond.mjs +11 -0
- package/dist/implement/control/cond.mjs.map +1 -0
- package/dist/implement/control/guard.d.ts +8 -0
- package/dist/implement/control/guard.d.ts.map +1 -0
- package/dist/implement/control/guard.mjs +9 -0
- package/dist/implement/control/guard.mjs.map +1 -0
- package/dist/implement/control/ifElse.d.ts +18 -0
- package/dist/implement/control/ifElse.d.ts.map +1 -0
- package/dist/implement/control/ifElse.mjs +9 -0
- package/dist/implement/control/ifElse.mjs.map +1 -0
- package/dist/implement/control/index.d.ts +7 -0
- package/dist/implement/control/index.d.ts.map +1 -0
- package/dist/implement/control/tryCatch.d.ts +12 -0
- package/dist/implement/control/tryCatch.d.ts.map +1 -0
- package/dist/implement/control/tryCatch.mjs +14 -0
- package/dist/implement/control/tryCatch.mjs.map +1 -0
- package/dist/implement/control/unless.d.ts +9 -0
- package/dist/implement/control/unless.d.ts.map +1 -0
- package/dist/implement/control/unless.mjs +9 -0
- package/dist/implement/control/unless.mjs.map +1 -0
- package/dist/implement/control/when.d.ts +9 -0
- package/dist/implement/control/when.d.ts.map +1 -0
- package/dist/implement/control/when.mjs +9 -0
- package/dist/implement/control/when.mjs.map +1 -0
- package/dist/implement/debug/assert.d.ts +6 -0
- package/dist/implement/debug/assert.d.ts.map +1 -0
- package/dist/implement/debug/assert.mjs +10 -0
- package/dist/implement/debug/assert.mjs.map +1 -0
- package/dist/implement/debug/index.d.ts +4 -0
- package/dist/implement/debug/index.d.ts.map +1 -0
- package/dist/implement/debug/invariant.d.ts +6 -0
- package/dist/implement/debug/invariant.d.ts.map +1 -0
- package/dist/implement/debug/invariant.mjs +10 -0
- package/dist/implement/debug/invariant.mjs.map +1 -0
- package/dist/implement/debug/log.d.ts +4 -0
- package/dist/implement/debug/log.d.ts.map +1 -0
- package/dist/implement/debug/log.mjs +7 -0
- package/dist/implement/debug/log.mjs.map +1 -0
- package/dist/implement/equality/clamp.d.ts +7 -0
- package/dist/implement/equality/clamp.d.ts.map +1 -0
- package/dist/implement/equality/clamp.mjs +9 -0
- package/dist/implement/equality/clamp.mjs.map +1 -0
- package/dist/implement/equality/equals.d.ts +6 -0
- package/dist/implement/equality/equals.d.ts.map +1 -0
- package/dist/implement/equality/equals.mjs +59 -0
- package/dist/implement/equality/equals.mjs.map +1 -0
- package/dist/implement/equality/gt.d.ts +4 -0
- package/dist/implement/equality/gt.d.ts.map +1 -0
- package/dist/implement/equality/gt.mjs +7 -0
- package/dist/implement/equality/gt.mjs.map +1 -0
- package/dist/implement/equality/gte.d.ts +4 -0
- package/dist/implement/equality/gte.d.ts.map +1 -0
- package/dist/implement/equality/gte.mjs +7 -0
- package/dist/implement/equality/gte.mjs.map +1 -0
- package/dist/implement/equality/includes.d.ts +4 -0
- package/dist/implement/equality/includes.d.ts.map +1 -0
- package/dist/implement/equality/includes.mjs +15 -0
- package/dist/implement/equality/includes.mjs.map +1 -0
- package/dist/implement/equality/index.d.ts +11 -0
- package/dist/implement/equality/index.d.ts.map +1 -0
- package/dist/implement/equality/isEmpty.d.ts +4 -0
- package/dist/implement/equality/isEmpty.d.ts.map +1 -0
- package/dist/implement/equality/isEmpty.mjs +7 -0
- package/dist/implement/equality/isEmpty.mjs.map +1 -0
- package/dist/implement/equality/isNil.d.ts +4 -0
- package/dist/implement/equality/isNil.d.ts.map +1 -0
- package/dist/implement/equality/isNil.mjs +7 -0
- package/dist/implement/equality/isNil.mjs.map +1 -0
- package/dist/implement/equality/isType.d.ts +4 -0
- package/dist/implement/equality/isType.d.ts.map +1 -0
- package/dist/implement/equality/isType.mjs +13 -0
- package/dist/implement/equality/isType.mjs.map +1 -0
- package/dist/implement/equality/lt.d.ts +4 -0
- package/dist/implement/equality/lt.d.ts.map +1 -0
- package/dist/implement/equality/lt.mjs +7 -0
- package/dist/implement/equality/lt.mjs.map +1 -0
- package/dist/implement/equality/lte.d.ts +4 -0
- package/dist/implement/equality/lte.d.ts.map +1 -0
- package/dist/implement/equality/lte.mjs +7 -0
- package/dist/implement/equality/lte.mjs.map +1 -0
- package/dist/implement/math/add.d.ts +6 -0
- package/dist/implement/math/add.d.ts.map +1 -0
- package/dist/implement/math/add.mjs +9 -0
- package/dist/implement/math/add.mjs.map +1 -0
- package/dist/implement/math/ceil.d.ts +4 -0
- package/dist/implement/math/ceil.d.ts.map +1 -0
- package/dist/implement/math/ceil.mjs +7 -0
- package/dist/implement/math/ceil.mjs.map +1 -0
- package/dist/implement/math/div.d.ts +6 -0
- package/dist/implement/math/div.d.ts.map +1 -0
- package/dist/implement/math/div.mjs +9 -0
- package/dist/implement/math/div.mjs.map +1 -0
- package/dist/implement/math/floor.d.ts +4 -0
- package/dist/implement/math/floor.d.ts.map +1 -0
- package/dist/implement/math/floor.mjs +7 -0
- package/dist/implement/math/floor.mjs.map +1 -0
- package/dist/implement/math/index.d.ts +13 -0
- package/dist/implement/math/index.d.ts.map +1 -0
- package/dist/implement/math/max.d.ts +4 -0
- package/dist/implement/math/max.d.ts.map +1 -0
- package/dist/implement/math/max.mjs +7 -0
- package/dist/implement/math/max.mjs.map +1 -0
- package/dist/implement/math/mean.d.ts +4 -0
- package/dist/implement/math/mean.d.ts.map +1 -0
- package/dist/implement/math/mean.mjs +7 -0
- package/dist/implement/math/mean.mjs.map +1 -0
- package/dist/implement/math/min.d.ts +4 -0
- package/dist/implement/math/min.d.ts.map +1 -0
- package/dist/implement/math/min.mjs +7 -0
- package/dist/implement/math/min.mjs.map +1 -0
- package/dist/implement/math/mul.d.ts +6 -0
- package/dist/implement/math/mul.d.ts.map +1 -0
- package/dist/implement/math/mul.mjs +9 -0
- package/dist/implement/math/mul.mjs.map +1 -0
- package/dist/implement/math/randomInt.d.ts +6 -0
- package/dist/implement/math/randomInt.d.ts.map +1 -0
- package/dist/implement/math/randomInt.mjs +10 -0
- package/dist/implement/math/randomInt.mjs.map +1 -0
- package/dist/implement/math/round.d.ts +4 -0
- package/dist/implement/math/round.d.ts.map +1 -0
- package/dist/implement/math/round.mjs +7 -0
- package/dist/implement/math/round.mjs.map +1 -0
- package/dist/implement/math/sub.d.ts +6 -0
- package/dist/implement/math/sub.d.ts.map +1 -0
- package/dist/implement/math/sub.mjs +9 -0
- package/dist/implement/math/sub.mjs.map +1 -0
- package/dist/implement/math/sum.d.ts +4 -0
- package/dist/implement/math/sum.d.ts.map +1 -0
- package/dist/implement/math/sum.mjs +7 -0
- package/dist/implement/math/sum.mjs.map +1 -0
- package/dist/implement/nullable/fold.d.ts +8 -0
- package/dist/implement/nullable/fold.d.ts.map +1 -0
- package/dist/implement/nullable/fold.mjs +9 -0
- package/dist/implement/nullable/fold.mjs.map +1 -0
- package/dist/implement/nullable/getOrElse.d.ts +4 -0
- package/dist/implement/nullable/getOrElse.d.ts.map +1 -0
- package/dist/implement/nullable/getOrElse.mjs +7 -0
- package/dist/implement/nullable/getOrElse.mjs.map +1 -0
- package/dist/implement/nullable/index.d.ts +6 -0
- package/dist/implement/nullable/index.d.ts.map +1 -0
- package/dist/implement/nullable/mapMaybe.d.ts +4 -0
- package/dist/implement/nullable/mapMaybe.d.ts.map +1 -0
- package/dist/implement/nullable/mapMaybe.mjs +14 -0
- package/dist/implement/nullable/mapMaybe.mjs.map +1 -0
- package/dist/implement/nullable/maybe.d.ts +4 -0
- package/dist/implement/nullable/maybe.d.ts.map +1 -0
- package/dist/implement/nullable/maybe.mjs +7 -0
- package/dist/implement/nullable/maybe.mjs.map +1 -0
- package/dist/implement/nullable/result.d.ts +10 -0
- package/dist/implement/nullable/result.d.ts.map +1 -0
- package/dist/implement/nullable/result.mjs +11 -0
- package/dist/implement/nullable/result.mjs.map +1 -0
- package/dist/implement/object/assoc.d.ts +11 -0
- package/dist/implement/object/assoc.d.ts.map +1 -0
- package/dist/implement/object/assoc.mjs +16 -0
- package/dist/implement/object/assoc.mjs.map +1 -0
- package/dist/implement/object/assoc.type-test.d.ts +30 -0
- package/dist/implement/object/assoc.type-test.d.ts.map +1 -0
- package/dist/implement/object/assocPath.d.ts +9 -0
- package/dist/implement/object/assocPath.d.ts.map +1 -0
- package/dist/implement/object/assocPath.mjs +21 -0
- package/dist/implement/object/assocPath.mjs.map +1 -0
- package/dist/implement/object/dissoc.d.ts +7 -0
- package/dist/implement/object/dissoc.d.ts.map +1 -0
- package/dist/implement/object/dissoc.mjs +15 -0
- package/dist/implement/object/dissoc.mjs.map +1 -0
- package/dist/implement/object/dissocPath.d.ts +8 -0
- package/dist/implement/object/dissocPath.d.ts.map +1 -0
- package/dist/implement/object/dissocPath.mjs +33 -0
- package/dist/implement/object/dissocPath.mjs.map +1 -0
- package/dist/implement/object/entries.d.ts +6 -0
- package/dist/implement/object/entries.d.ts.map +1 -0
- package/dist/implement/object/entries.mjs +7 -0
- package/dist/implement/object/entries.mjs.map +1 -0
- package/dist/implement/object/evolve.d.ts +9 -0
- package/dist/implement/object/evolve.d.ts.map +1 -0
- package/dist/implement/object/evolve.mjs +14 -0
- package/dist/implement/object/evolve.mjs.map +1 -0
- package/dist/implement/object/has.d.ts +7 -0
- package/dist/implement/object/has.d.ts.map +1 -0
- package/dist/implement/object/has.mjs +9 -0
- package/dist/implement/object/has.mjs.map +1 -0
- package/dist/implement/object/hasPath.d.ts +6 -0
- package/dist/implement/object/hasPath.d.ts.map +1 -0
- package/dist/implement/object/hasPath.mjs +15 -0
- package/dist/implement/object/hasPath.mjs.map +1 -0
- package/dist/implement/object/index.d.ts +21 -0
- package/dist/implement/object/index.d.ts.map +1 -0
- package/dist/implement/object/keys.d.ts +6 -0
- package/dist/implement/object/keys.d.ts.map +1 -0
- package/dist/implement/object/keys.mjs +7 -0
- package/dist/implement/object/keys.mjs.map +1 -0
- package/dist/implement/object/mapValues.d.ts +6 -0
- package/dist/implement/object/mapValues.d.ts.map +1 -0
- package/dist/implement/object/mapValues.mjs +12 -0
- package/dist/implement/object/mapValues.mjs.map +1 -0
- package/dist/implement/object/merge.d.ts +7 -0
- package/dist/implement/object/merge.d.ts.map +1 -0
- package/dist/implement/object/merge.mjs +12 -0
- package/dist/implement/object/merge.mjs.map +1 -0
- package/dist/implement/object/mergeAll.d.ts +6 -0
- package/dist/implement/object/mergeAll.d.ts.map +1 -0
- package/dist/implement/object/mergeAll.mjs +10 -0
- package/dist/implement/object/mergeAll.mjs.map +1 -0
- package/dist/implement/object/mergeDeep.d.ts +7 -0
- package/dist/implement/object/mergeDeep.d.ts.map +1 -0
- package/dist/implement/object/mergeDeep.mjs +20 -0
- package/dist/implement/object/mergeDeep.mjs.map +1 -0
- package/dist/implement/object/omit.d.ts +7 -0
- package/dist/implement/object/omit.d.ts.map +1 -0
- package/dist/implement/object/omit.mjs +12 -0
- package/dist/implement/object/omit.mjs.map +1 -0
- package/dist/implement/object/path.d.ts +7 -0
- package/dist/implement/object/path.d.ts.map +1 -0
- package/dist/implement/object/path.mjs +9 -0
- package/dist/implement/object/path.mjs.map +1 -0
- package/dist/implement/object/pathOr.d.ts +8 -0
- package/dist/implement/object/pathOr.d.ts.map +1 -0
- package/dist/implement/object/pathOr.mjs +10 -0
- package/dist/implement/object/pathOr.mjs.map +1 -0
- package/dist/implement/object/pick.d.ts +7 -0
- package/dist/implement/object/pick.d.ts.map +1 -0
- package/dist/implement/object/pick.mjs +12 -0
- package/dist/implement/object/pick.mjs.map +1 -0
- package/dist/implement/object/prop.d.ts +7 -0
- package/dist/implement/object/prop.d.ts.map +1 -0
- package/dist/implement/object/prop.mjs +9 -0
- package/dist/implement/object/prop.mjs.map +1 -0
- package/dist/implement/object/propOr.d.ts +8 -0
- package/dist/implement/object/propOr.d.ts.map +1 -0
- package/dist/implement/object/propOr.mjs +10 -0
- package/dist/implement/object/propOr.mjs.map +1 -0
- package/dist/implement/object/values.d.ts +6 -0
- package/dist/implement/object/values.d.ts.map +1 -0
- package/dist/implement/object/values.mjs +7 -0
- package/dist/implement/object/values.mjs.map +1 -0
- package/dist/implement/string/endsWith.d.ts +5 -0
- package/dist/implement/string/endsWith.d.ts.map +1 -0
- package/dist/implement/string/endsWith.mjs +18 -0
- package/dist/implement/string/endsWith.mjs.map +1 -0
- package/dist/implement/string/index.d.ts +10 -0
- package/dist/implement/string/index.d.ts.map +1 -0
- package/dist/implement/string/join.d.ts +6 -0
- package/dist/implement/string/join.d.ts.map +1 -0
- package/dist/implement/string/join.mjs +9 -0
- package/dist/implement/string/join.mjs.map +1 -0
- package/dist/implement/string/match.d.ts +6 -0
- package/dist/implement/string/match.d.ts.map +1 -0
- package/dist/implement/string/match.mjs +9 -0
- package/dist/implement/string/match.mjs.map +1 -0
- package/dist/implement/string/replace.d.ts +7 -0
- package/dist/implement/string/replace.d.ts.map +1 -0
- package/dist/implement/string/replace.mjs +9 -0
- package/dist/implement/string/replace.mjs.map +1 -0
- package/dist/implement/string/split.d.ts +6 -0
- package/dist/implement/string/split.d.ts.map +1 -0
- package/dist/implement/string/split.mjs +9 -0
- package/dist/implement/string/split.mjs.map +1 -0
- package/dist/implement/string/startsWith.d.ts +5 -0
- package/dist/implement/string/startsWith.d.ts.map +1 -0
- package/dist/implement/string/startsWith.mjs +16 -0
- package/dist/implement/string/startsWith.mjs.map +1 -0
- package/dist/implement/string/toLower.d.ts +4 -0
- package/dist/implement/string/toLower.d.ts.map +1 -0
- package/dist/implement/string/toLower.mjs +7 -0
- package/dist/implement/string/toLower.mjs.map +1 -0
- package/dist/implement/string/toUpper.d.ts +4 -0
- package/dist/implement/string/toUpper.d.ts.map +1 -0
- package/dist/implement/string/toUpper.mjs +7 -0
- package/dist/implement/string/toUpper.mjs.map +1 -0
- package/dist/implement/string/trim.d.ts +4 -0
- package/dist/implement/string/trim.d.ts.map +1 -0
- package/dist/implement/string/trim.mjs +7 -0
- package/dist/implement/string/trim.mjs.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +247 -0
- package/dist/index.mjs.map +1 -0
- package/dist/skills/fp-pack.md +1644 -0
- package/dist/stream/append.d.ts +8 -0
- package/dist/stream/append.d.ts.map +1 -0
- package/dist/stream/append.mjs +23 -0
- package/dist/stream/append.mjs.map +1 -0
- package/dist/stream/chunk.d.ts +7 -0
- package/dist/stream/chunk.d.ts.map +1 -0
- package/dist/stream/chunk.mjs +37 -0
- package/dist/stream/chunk.mjs.map +1 -0
- package/dist/stream/concat.d.ts +8 -0
- package/dist/stream/concat.d.ts.map +1 -0
- package/dist/stream/concat.mjs +25 -0
- package/dist/stream/concat.mjs.map +1 -0
- package/dist/stream/drop.d.ts +8 -0
- package/dist/stream/drop.d.ts.map +1 -0
- package/dist/stream/drop.mjs +42 -0
- package/dist/stream/drop.mjs.map +1 -0
- package/dist/stream/dropWhile.d.ts +8 -0
- package/dist/stream/dropWhile.d.ts.map +1 -0
- package/dist/stream/dropWhile.mjs +23 -0
- package/dist/stream/dropWhile.mjs.map +1 -0
- package/dist/stream/every.d.ts +8 -0
- package/dist/stream/every.d.ts.map +1 -0
- package/dist/stream/every.mjs +24 -0
- package/dist/stream/every.mjs.map +1 -0
- package/dist/stream/filter.d.ts +8 -0
- package/dist/stream/filter.d.ts.map +1 -0
- package/dist/stream/filter.mjs +21 -0
- package/dist/stream/filter.mjs.map +1 -0
- package/dist/stream/find.d.ts +8 -0
- package/dist/stream/find.d.ts.map +1 -0
- package/dist/stream/find.mjs +23 -0
- package/dist/stream/find.mjs.map +1 -0
- package/dist/stream/flatMap.d.ts +8 -0
- package/dist/stream/flatMap.d.ts.map +1 -0
- package/dist/stream/flatMap.mjs +27 -0
- package/dist/stream/flatMap.mjs.map +1 -0
- package/dist/stream/flatten.d.ts +6 -0
- package/dist/stream/flatten.d.ts.map +1 -0
- package/dist/stream/flatten.mjs +23 -0
- package/dist/stream/flatten.mjs.map +1 -0
- package/dist/stream/flattenDeep.d.ts +6 -0
- package/dist/stream/flattenDeep.d.ts.map +1 -0
- package/dist/stream/flattenDeep.mjs +30 -0
- package/dist/stream/flattenDeep.mjs.map +1 -0
- package/dist/stream/index.d.ts +24 -0
- package/dist/stream/index.d.ts.map +1 -0
- package/dist/stream/index.mjs +49 -0
- package/dist/stream/index.mjs.map +1 -0
- package/dist/stream/map.d.ts +8 -0
- package/dist/stream/map.d.ts.map +1 -0
- package/dist/stream/map.mjs +21 -0
- package/dist/stream/map.mjs.map +1 -0
- package/dist/stream/prepend.d.ts +8 -0
- package/dist/stream/prepend.d.ts.map +1 -0
- package/dist/stream/prepend.mjs +23 -0
- package/dist/stream/prepend.mjs.map +1 -0
- package/dist/stream/range.d.ts +4 -0
- package/dist/stream/range.d.ts.map +1 -0
- package/dist/stream/range.mjs +17 -0
- package/dist/stream/range.mjs.map +1 -0
- package/dist/stream/reduce.d.ts +8 -0
- package/dist/stream/reduce.d.ts.map +1 -0
- package/dist/stream/reduce.mjs +24 -0
- package/dist/stream/reduce.mjs.map +1 -0
- package/dist/stream/scan.d.ts +8 -0
- package/dist/stream/scan.d.ts.map +1 -0
- package/dist/stream/scan.mjs +23 -0
- package/dist/stream/scan.mjs.map +1 -0
- package/dist/stream/some.d.ts +8 -0
- package/dist/stream/some.d.ts.map +1 -0
- package/dist/stream/some.mjs +24 -0
- package/dist/stream/some.mjs.map +1 -0
- package/dist/stream/take.d.ts +8 -0
- package/dist/stream/take.d.ts.map +1 -0
- package/dist/stream/take.mjs +30 -0
- package/dist/stream/take.mjs.map +1 -0
- package/dist/stream/takeWhile.d.ts +8 -0
- package/dist/stream/takeWhile.d.ts.map +1 -0
- package/dist/stream/takeWhile.mjs +27 -0
- package/dist/stream/takeWhile.mjs.map +1 -0
- package/dist/stream/toArray.d.ts +5 -0
- package/dist/stream/toArray.d.ts.map +1 -0
- package/dist/stream/toArray.mjs +19 -0
- package/dist/stream/toArray.mjs.map +1 -0
- package/dist/stream/toAsync.d.ts +6 -0
- package/dist/stream/toAsync.d.ts.map +1 -0
- package/dist/stream/toAsync.mjs +21 -0
- package/dist/stream/toAsync.mjs.map +1 -0
- package/dist/stream/utils.d.ts +12 -0
- package/dist/stream/utils.d.ts.map +1 -0
- package/dist/stream/utils.mjs +10 -0
- package/dist/stream/utils.mjs.map +1 -0
- package/dist/stream/zip.d.ts +8 -0
- package/dist/stream/zip.d.ts.map +1 -0
- package/dist/stream/zip.mjs +31 -0
- package/dist/stream/zip.mjs.map +1 -0
- package/dist/stream/zipWith.d.ts +8 -0
- package/dist/stream/zipWith.d.ts.map +1 -0
- package/dist/stream/zipWith.mjs +31 -0
- package/dist/stream/zipWith.mjs.map +1 -0
- package/package.json +87 -0
- package/src/implement/array/append.test.ts +13 -0
- package/src/implement/array/append.ts +16 -0
- package/src/implement/array/chunk.test.ts +23 -0
- package/src/implement/array/chunk.ts +25 -0
- package/src/implement/array/concat.test.ts +13 -0
- package/src/implement/array/concat.ts +16 -0
- package/src/implement/array/curried.test.ts +91 -0
- package/src/implement/array/drop.test.ts +22 -0
- package/src/implement/array/drop.ts +20 -0
- package/src/implement/array/dropWhile.test.ts +13 -0
- package/src/implement/array/dropWhile.ts +26 -0
- package/src/implement/array/every.test.ts +16 -0
- package/src/implement/array/every.ts +16 -0
- package/src/implement/array/filter.test.ts +16 -0
- package/src/implement/array/filter.ts +16 -0
- package/src/implement/array/find.test.ts +16 -0
- package/src/implement/array/find.ts +16 -0
- package/src/implement/array/flatMap.test.ts +13 -0
- package/src/implement/array/flatMap.ts +16 -0
- package/src/implement/array/flatten.test.ts +8 -0
- package/src/implement/array/flatten.ts +8 -0
- package/src/implement/array/flattenDeep.test.ts +13 -0
- package/src/implement/array/flattenDeep.ts +21 -0
- package/src/implement/array/groupBy.test.ts +13 -0
- package/src/implement/array/groupBy.ts +23 -0
- package/src/implement/array/head.test.ts +12 -0
- package/src/implement/array/head.ts +8 -0
- package/src/implement/array/index.ts +33 -0
- package/src/implement/array/init.test.ts +13 -0
- package/src/implement/array/init.ts +9 -0
- package/src/implement/array/last.test.ts +12 -0
- package/src/implement/array/last.ts +9 -0
- package/src/implement/array/map.test.ts +12 -0
- package/src/implement/array/map.ts +16 -0
- package/src/implement/array/partition.test.ts +14 -0
- package/src/implement/array/partition.ts +27 -0
- package/src/implement/array/prepend.test.ts +13 -0
- package/src/implement/array/prepend.ts +16 -0
- package/src/implement/array/range.test.ts +16 -0
- package/src/implement/array/range.ts +18 -0
- package/src/implement/array/reduce.test.ts +25 -0
- package/src/implement/array/reduce.ts +21 -0
- package/src/implement/array/scan.test.ts +13 -0
- package/src/implement/array/scan.ts +23 -0
- package/src/implement/array/some.test.ts +25 -0
- package/src/implement/array/some.ts +19 -0
- package/src/implement/array/sort.test.ts +25 -0
- package/src/implement/array/sort.ts +16 -0
- package/src/implement/array/sortBy.test.ts +28 -0
- package/src/implement/array/sortBy.ts +22 -0
- package/src/implement/array/tail.test.ts +13 -0
- package/src/implement/array/tail.ts +8 -0
- package/src/implement/array/take.test.ts +20 -0
- package/src/implement/array/take.ts +18 -0
- package/src/implement/array/takeWhile.test.ts +13 -0
- package/src/implement/array/takeWhile.ts +23 -0
- package/src/implement/array/uniq.test.ts +18 -0
- package/src/implement/array/uniq.ts +16 -0
- package/src/implement/array/uniqBy.test.ts +22 -0
- package/src/implement/array/uniqBy.ts +25 -0
- package/src/implement/array/unzip.test.ts +22 -0
- package/src/implement/array/unzip.ts +16 -0
- package/src/implement/array/zip.test.ts +16 -0
- package/src/implement/array/zip.ts +23 -0
- package/src/implement/array/zipIndex.test.ts +16 -0
- package/src/implement/array/zipIndex.ts +8 -0
- package/src/implement/array/zipWith.test.ts +13 -0
- package/src/implement/array/zipWith.ts +22 -0
- package/src/implement/async/curried.test.ts +133 -0
- package/src/implement/async/debounce.test.ts +21 -0
- package/src/implement/async/debounce.ts +26 -0
- package/src/implement/async/debounceLeading.test.ts +24 -0
- package/src/implement/async/debounceLeading.ts +25 -0
- package/src/implement/async/debounceLeadingTrailing.test.ts +37 -0
- package/src/implement/async/debounceLeadingTrailing.ts +41 -0
- package/src/implement/async/delay.test.ts +22 -0
- package/src/implement/async/delay.ts +7 -0
- package/src/implement/async/index.ts +9 -0
- package/src/implement/async/pipeAsync.test.ts +23 -0
- package/src/implement/async/pipeAsync.ts +48 -0
- package/src/implement/async/pipeAsyncSideEffect.test.ts +23 -0
- package/src/implement/async/pipeAsyncSideEffect.ts +68 -0
- package/src/implement/async/retry.test.ts +49 -0
- package/src/implement/async/retry.ts +27 -0
- package/src/implement/async/throttle.test.ts +38 -0
- package/src/implement/async/throttle.ts +47 -0
- package/src/implement/async/timeout.test.ts +20 -0
- package/src/implement/async/timeout.ts +24 -0
- package/src/implement/composition/complement.test.ts +17 -0
- package/src/implement/composition/complement.ts +13 -0
- package/src/implement/composition/compose.test.ts +30 -0
- package/src/implement/composition/compose.ts +48 -0
- package/src/implement/composition/constant.test.ts +16 -0
- package/src/implement/composition/constant.ts +8 -0
- package/src/implement/composition/curry.test.ts +31 -0
- package/src/implement/composition/curry.ts +71 -0
- package/src/implement/composition/flip.test.ts +41 -0
- package/src/implement/composition/flip.ts +16 -0
- package/src/implement/composition/identity.test.ts +14 -0
- package/src/implement/composition/identity.ts +8 -0
- package/src/implement/composition/index.ts +15 -0
- package/src/implement/composition/memoize.test.ts +46 -0
- package/src/implement/composition/memoize.ts +31 -0
- package/src/implement/composition/once.test.ts +30 -0
- package/src/implement/composition/once.ts +21 -0
- package/src/implement/composition/partial.test.ts +25 -0
- package/src/implement/composition/partial.ts +14 -0
- package/src/implement/composition/pipe.test.ts +31 -0
- package/src/implement/composition/pipe.ts +39 -0
- package/src/implement/composition/pipe.type-test.ts +54 -0
- package/src/implement/composition/pipeSideEffect.test.ts +23 -0
- package/src/implement/composition/pipeSideEffect.ts +60 -0
- package/src/implement/composition/sideEffect.test.ts +43 -0
- package/src/implement/composition/sideEffect.ts +44 -0
- package/src/implement/composition/tap.test.ts +17 -0
- package/src/implement/composition/tap.ts +11 -0
- package/src/implement/control/cond.test.ts +33 -0
- package/src/implement/control/cond.ts +17 -0
- package/src/implement/control/curried.test.ts +71 -0
- package/src/implement/control/guard.test.ts +17 -0
- package/src/implement/control/guard.ts +27 -0
- package/src/implement/control/ifElse.test.ts +68 -0
- package/src/implement/control/ifElse.ts +40 -0
- package/src/implement/control/index.ts +6 -0
- package/src/implement/control/tryCatch.test.ts +35 -0
- package/src/implement/control/tryCatch.ts +36 -0
- package/src/implement/control/unless.test.ts +22 -0
- package/src/implement/control/unless.ts +29 -0
- package/src/implement/control/when.test.ts +22 -0
- package/src/implement/control/when.ts +29 -0
- package/src/implement/debug/assert.test.ts +16 -0
- package/src/implement/debug/assert.ts +9 -0
- package/src/implement/debug/curried.test.ts +17 -0
- package/src/implement/debug/index.ts +3 -0
- package/src/implement/debug/invariant.test.ts +16 -0
- package/src/implement/debug/invariant.ts +9 -0
- package/src/implement/debug/log.test.ts +20 -0
- package/src/implement/debug/log.ts +12 -0
- package/src/implement/equality/clamp.test.ts +16 -0
- package/src/implement/equality/clamp.ts +9 -0
- package/src/implement/equality/curried.test.ts +13 -0
- package/src/implement/equality/equals.test.ts +63 -0
- package/src/implement/equality/equals.ts +87 -0
- package/src/implement/equality/gt.test.ts +13 -0
- package/src/implement/equality/gt.ts +5 -0
- package/src/implement/equality/gte.test.ts +13 -0
- package/src/implement/equality/gte.ts +5 -0
- package/src/implement/equality/includes.test.ts +16 -0
- package/src/implement/equality/includes.ts +21 -0
- package/src/implement/equality/index.ts +10 -0
- package/src/implement/equality/isEmpty.test.ts +24 -0
- package/src/implement/equality/isEmpty.ts +19 -0
- package/src/implement/equality/isNil.test.ts +16 -0
- package/src/implement/equality/isNil.ts +5 -0
- package/src/implement/equality/isType.test.ts +28 -0
- package/src/implement/equality/isType.ts +13 -0
- package/src/implement/equality/lt.test.ts +13 -0
- package/src/implement/equality/lt.ts +5 -0
- package/src/implement/equality/lte.test.ts +13 -0
- package/src/implement/equality/lte.ts +5 -0
- package/src/implement/math/add.test.ts +9 -0
- package/src/implement/math/add.ts +7 -0
- package/src/implement/math/ceil.test.ts +10 -0
- package/src/implement/math/ceil.ts +5 -0
- package/src/implement/math/curried.test.ts +30 -0
- package/src/implement/math/div.test.ts +9 -0
- package/src/implement/math/div.ts +7 -0
- package/src/implement/math/floor.test.ts +10 -0
- package/src/implement/math/floor.ts +5 -0
- package/src/implement/math/index.ts +12 -0
- package/src/implement/math/max.test.ts +13 -0
- package/src/implement/math/max.ts +6 -0
- package/src/implement/math/mean.test.ts +12 -0
- package/src/implement/math/mean.ts +7 -0
- package/src/implement/math/min.test.ts +12 -0
- package/src/implement/math/min.ts +6 -0
- package/src/implement/math/mul.test.ts +9 -0
- package/src/implement/math/mul.ts +7 -0
- package/src/implement/math/randomInt.test.ts +22 -0
- package/src/implement/math/randomInt.ts +14 -0
- package/src/implement/math/round.test.ts +10 -0
- package/src/implement/math/round.ts +5 -0
- package/src/implement/math/sub.test.ts +9 -0
- package/src/implement/math/sub.ts +7 -0
- package/src/implement/math/sum.test.ts +10 -0
- package/src/implement/math/sum.ts +5 -0
- package/src/implement/nullable/curried.test.ts +13 -0
- package/src/implement/nullable/fold.test.ts +31 -0
- package/src/implement/nullable/fold.ts +27 -0
- package/src/implement/nullable/getOrElse.test.ts +17 -0
- package/src/implement/nullable/getOrElse.ts +10 -0
- package/src/implement/nullable/index.ts +5 -0
- package/src/implement/nullable/mapMaybe.test.ts +23 -0
- package/src/implement/nullable/mapMaybe.ts +18 -0
- package/src/implement/nullable/maybe.test.ts +15 -0
- package/src/implement/nullable/maybe.ts +10 -0
- package/src/implement/nullable/result.test.ts +17 -0
- package/src/implement/nullable/result.ts +10 -0
- package/src/implement/object/assoc.test.ts +27 -0
- package/src/implement/object/assoc.ts +43 -0
- package/src/implement/object/assoc.type-test.ts +38 -0
- package/src/implement/object/assocPath.test.ts +33 -0
- package/src/implement/object/assocPath.ts +64 -0
- package/src/implement/object/curried.test.ts +49 -0
- package/src/implement/object/dissoc.test.ts +20 -0
- package/src/implement/object/dissoc.ts +26 -0
- package/src/implement/object/dissocPath.test.ts +18 -0
- package/src/implement/object/dissocPath.ts +80 -0
- package/src/implement/object/entries.test.ts +12 -0
- package/src/implement/object/entries.ts +8 -0
- package/src/implement/object/evolve.test.ts +14 -0
- package/src/implement/object/evolve.ts +31 -0
- package/src/implement/object/has.test.ts +10 -0
- package/src/implement/object/has.ts +16 -0
- package/src/implement/object/hasPath.test.ts +13 -0
- package/src/implement/object/hasPath.ts +17 -0
- package/src/implement/object/index.ts +20 -0
- package/src/implement/object/keys.test.ts +9 -0
- package/src/implement/object/keys.ts +8 -0
- package/src/implement/object/mapValues.test.ts +11 -0
- package/src/implement/object/mapValues.ts +16 -0
- package/src/implement/object/merge.test.ts +22 -0
- package/src/implement/object/merge.ts +19 -0
- package/src/implement/object/mergeAll.test.ts +13 -0
- package/src/implement/object/mergeAll.ts +12 -0
- package/src/implement/object/mergeDeep.test.ts +11 -0
- package/src/implement/object/mergeDeep.ts +41 -0
- package/src/implement/object/omit.test.ts +12 -0
- package/src/implement/object/omit.ts +20 -0
- package/src/implement/object/path.test.ts +9 -0
- package/src/implement/object/path.ts +16 -0
- package/src/implement/object/pathOr.test.ts +15 -0
- package/src/implement/object/pathOr.ts +18 -0
- package/src/implement/object/pick.test.ts +12 -0
- package/src/implement/object/pick.ts +22 -0
- package/src/implement/object/prop.test.ts +9 -0
- package/src/implement/object/prop.ts +16 -0
- package/src/implement/object/propOr.test.ts +15 -0
- package/src/implement/object/propOr.ts +22 -0
- package/src/implement/object/values.test.ts +9 -0
- package/src/implement/object/values.ts +8 -0
- package/src/implement/string/curried.test.ts +21 -0
- package/src/implement/string/endsWith.test.ts +23 -0
- package/src/implement/string/endsWith.ts +21 -0
- package/src/implement/string/index.ts +9 -0
- package/src/implement/string/join.test.ts +16 -0
- package/src/implement/string/join.ts +7 -0
- package/src/implement/string/match.test.ts +13 -0
- package/src/implement/string/match.ts +7 -0
- package/src/implement/string/replace.test.ts +12 -0
- package/src/implement/string/replace.ts +7 -0
- package/src/implement/string/split.test.ts +12 -0
- package/src/implement/string/split.ts +7 -0
- package/src/implement/string/startsWith.test.ts +21 -0
- package/src/implement/string/startsWith.ts +20 -0
- package/src/implement/string/toLower.test.ts +13 -0
- package/src/implement/string/toLower.ts +5 -0
- package/src/implement/string/toUpper.test.ts +13 -0
- package/src/implement/string/toUpper.ts +5 -0
- package/src/implement/string/trim.test.ts +12 -0
- package/src/implement/string/trim.ts +5 -0
- package/src/index.ts +32 -0
- package/src/stream/append.test.ts +20 -0
- package/src/stream/append.ts +39 -0
- package/src/stream/chunk.test.ts +62 -0
- package/src/stream/chunk.ts +74 -0
- package/src/stream/concat.test.ts +20 -0
- package/src/stream/concat.ts +43 -0
- package/src/stream/drop.test.ts +19 -0
- package/src/stream/drop.ts +59 -0
- package/src/stream/dropWhile.test.ts +20 -0
- package/src/stream/dropWhile.ts +51 -0
- package/src/stream/every.test.ts +19 -0
- package/src/stream/every.ts +46 -0
- package/src/stream/filter.test.ts +19 -0
- package/src/stream/filter.ts +46 -0
- package/src/stream/find.test.ts +18 -0
- package/src/stream/find.ts +43 -0
- package/src/stream/flatMap.test.ts +20 -0
- package/src/stream/flatMap.ts +48 -0
- package/src/stream/flatten.test.ts +17 -0
- package/src/stream/flatten.ts +33 -0
- package/src/stream/flattenDeep.test.ts +17 -0
- package/src/stream/flattenDeep.ts +41 -0
- package/src/stream/index.ts +23 -0
- package/src/stream/map.test.ts +19 -0
- package/src/stream/map.ts +39 -0
- package/src/stream/pipeAsync.test.ts +27 -0
- package/src/stream/prepend.test.ts +20 -0
- package/src/stream/prepend.ts +39 -0
- package/src/stream/range.test.ts +16 -0
- package/src/stream/range.ts +20 -0
- package/src/stream/reduce.test.ts +18 -0
- package/src/stream/reduce.ts +47 -0
- package/src/stream/scan.test.ts +22 -0
- package/src/stream/scan.ts +49 -0
- package/src/stream/some.test.ts +19 -0
- package/src/stream/some.ts +46 -0
- package/src/stream/take.test.ts +19 -0
- package/src/stream/take.ts +51 -0
- package/src/stream/takeWhile.test.ts +20 -0
- package/src/stream/takeWhile.ts +45 -0
- package/src/stream/toArray.test.ts +19 -0
- package/src/stream/toArray.ts +26 -0
- package/src/stream/toAsync.test.ts +11 -0
- package/src/stream/toAsync.ts +30 -0
- package/src/stream/utils.ts +30 -0
- package/src/stream/zip.test.ts +29 -0
- package/src/stream/zip.ts +56 -0
- package/src/stream/zipWith.test.ts +22 -0
- package/src/stream/zipWith.ts +62 -0
|
@@ -0,0 +1,1644 @@
|
|
|
1
|
+
# fp-pack AI Agent Skills
|
|
2
|
+
|
|
3
|
+
Document Version: 0.1.0
|
|
4
|
+
|
|
5
|
+
This document provides guidelines for AI coding assistants when working in projects that use fp-pack. Follow these instructions to write clean, declarative, functional code using fp-pack's utilities.
|
|
6
|
+
|
|
7
|
+
## Project Philosophy
|
|
8
|
+
|
|
9
|
+
fp-pack is a TypeScript functional programming library focused on:
|
|
10
|
+
|
|
11
|
+
1. **Function Composition**: Use `pipe` and `pipeAsync` as the primary tools for combining operations
|
|
12
|
+
2. **Declarative Code**: Prefer function composition over imperative loops and mutations
|
|
13
|
+
3. **No Monad Pattern**: Traditional FP monads (Option, Either, etc.) are NOT used - they don't compose well with `pipe`
|
|
14
|
+
4. **SideEffect Pattern**: Handle errors and side effects using `SideEffect` with `pipeSideEffect` / `pipeAsyncSideEffect` pipelines
|
|
15
|
+
5. **Lazy Evaluation**: Use `stream/*` functions for efficient iterable processing
|
|
16
|
+
|
|
17
|
+
## Core Composition Functions
|
|
18
|
+
|
|
19
|
+
### pipe - Synchronous Function Composition
|
|
20
|
+
|
|
21
|
+
**Always prefer `pipe` for synchronous operations** instead of manual imperative code.
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { pipe, map, filter, take } from 'fp-pack';
|
|
25
|
+
|
|
26
|
+
// GOOD: Declarative pipe composition
|
|
27
|
+
const processUsers = pipe(
|
|
28
|
+
filter((user: User) => user.age >= 18),
|
|
29
|
+
map(user => user.name.toUpperCase()),
|
|
30
|
+
take(10)
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// BAD: Imperative approach
|
|
34
|
+
const processUsers = (users: User[]) => {
|
|
35
|
+
const result = [];
|
|
36
|
+
for (const user of users) {
|
|
37
|
+
if (user.age >= 18) {
|
|
38
|
+
result.push(user.name.toUpperCase());
|
|
39
|
+
if (result.length >= 10) break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
> For SideEffect-based early exits, use `pipeSideEffect`.
|
|
47
|
+
|
|
48
|
+
### pipeAsync - Asynchronous Function Composition
|
|
49
|
+
|
|
50
|
+
**Use `pipeAsync` for any async operations** including API calls, database queries, or async transformations.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { pipeAsync } from 'fp-pack';
|
|
54
|
+
|
|
55
|
+
// GOOD: Async pipe composition
|
|
56
|
+
const fetchUserData = pipeAsync(
|
|
57
|
+
async (userId: string) => fetch(`/api/users/${userId}`),
|
|
58
|
+
async (response) => response.json(),
|
|
59
|
+
(data) => data.user
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// BAD: Manual async handling
|
|
63
|
+
const fetchUserData = async (userId: string) => {
|
|
64
|
+
const response = await fetch(`/api/users/${userId}`);
|
|
65
|
+
const data = await response.json();
|
|
66
|
+
return data.user;
|
|
67
|
+
};
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
> For SideEffect-aware async pipelines, use `pipeAsyncSideEffect`.
|
|
71
|
+
|
|
72
|
+
## SideEffect Pattern - For Special Cases Only
|
|
73
|
+
|
|
74
|
+
**Most cases: Use `pipe` / `pipeAsync` - they're simpler and sufficient for 99% of use cases.**
|
|
75
|
+
|
|
76
|
+
`pipe` and `pipeAsync` are for **pure** functions and don't handle `SideEffect`. **Only use `pipeSideEffect`/`pipeAsyncSideEffect` when you specifically need**:
|
|
77
|
+
- Early termination based on validation
|
|
78
|
+
- Error handling with side effects (logging, toasts, etc.)
|
|
79
|
+
- Optional chaining patterns
|
|
80
|
+
|
|
81
|
+
For regular error handling, standard try-catch or error propagation is perfectly fine.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// MOST CASES: Just use pipe with regular error handling
|
|
85
|
+
import { pipe, map, filter } from 'fp-pack';
|
|
86
|
+
|
|
87
|
+
const processData = pipe(
|
|
88
|
+
validateInput,
|
|
89
|
+
transformData,
|
|
90
|
+
saveData
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const result = processData(input);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error('Processing failed:', error);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// SPECIAL CASES: Use pipeSideEffect when you need early termination with side effects
|
|
100
|
+
import { pipeSideEffect, SideEffect, runPipeResult } from 'fp-pack';
|
|
101
|
+
|
|
102
|
+
const processDataPipeline = pipeSideEffect(
|
|
103
|
+
validateInput,
|
|
104
|
+
(data) => {
|
|
105
|
+
if (!data.isValid) {
|
|
106
|
+
return SideEffect.of(() => {
|
|
107
|
+
showToast('Invalid data'); // Side effect
|
|
108
|
+
logError('validation_failed'); // Side effect
|
|
109
|
+
return null;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return data;
|
|
113
|
+
},
|
|
114
|
+
transformData
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// runPipeResult must be called OUTSIDE the pipeline
|
|
118
|
+
const finalValue = runPipeResult(processDataPipeline(input));
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Key SideEffect functions:**
|
|
122
|
+
- `SideEffect.of(fn, label?)` - Create a side effect container
|
|
123
|
+
- `isSideEffect(value)` - Type guard for **runtime checking** whether a value is a SideEffect
|
|
124
|
+
- `runPipeResult<T, R>(result)` - Execute SideEffect or return value (call **OUTSIDE** pipelines, provide generics for type safety). **⚠️ CRITICAL:** `runPipeResult<T, R=any>` has default `R=any`, so using it without generics returns `any` type. Always provide generics for type safety
|
|
125
|
+
- `matchSideEffect(result, { value, effect })` - Pattern match on result
|
|
126
|
+
|
|
127
|
+
**Type-safe result handling:**
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { pipeSideEffect, SideEffect, isSideEffect, runPipeResult } from 'fp-pack';
|
|
131
|
+
|
|
132
|
+
const processNumbers = pipeSideEffect(
|
|
133
|
+
(nums: number[]) => nums.filter(n => n % 2 === 1),
|
|
134
|
+
(odds) => odds.length > 0
|
|
135
|
+
? odds
|
|
136
|
+
: SideEffect.of(() => 'No odd numbers'),
|
|
137
|
+
(odds) => odds.map(n => n * 2)
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const result = processNumbers([1, 2, 3, 4, 5]);
|
|
141
|
+
|
|
142
|
+
// ✅ CORRECT: Use isSideEffect for runtime checking + provide generics to runPipeResult
|
|
143
|
+
if (!isSideEffect(result)) {
|
|
144
|
+
// TypeScript knows: result is number[]
|
|
145
|
+
const sum: number = result.reduce((a, b) => a + b, 0);
|
|
146
|
+
} else {
|
|
147
|
+
// TypeScript knows: result is SideEffect<string>
|
|
148
|
+
// But runPipeResult still returns number[] | string (not fully narrowed)
|
|
149
|
+
const error = runPipeResult<number[], string>(result); // error: number[] | string
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ❌ WRONG: runPipeResult without generics
|
|
153
|
+
const value = runPipeResult(result); // result: any (no type information!)
|
|
154
|
+
|
|
155
|
+
// ✅ CORRECT: Provide generics to runPipeResult
|
|
156
|
+
const value = runPipeResult<number[], string>(result); // result: number[] | string (union type - safe but not narrowed)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**⚠️ CRITICAL: runPipeResult Type Safety**
|
|
160
|
+
|
|
161
|
+
`runPipeResult<T, R=any>` has a default type parameter `R=any`. This means:
|
|
162
|
+
|
|
163
|
+
- ❌ **Without generics**: `const result = runPipeResult(pipeline(data));` returns `any` type (unsafe!)
|
|
164
|
+
- ✅ **With generics**: `runPipeResult<SuccessType, ErrorType>(result)` returns union type `SuccessType | ErrorType` (type-safe)
|
|
165
|
+
- ✅ **With isSideEffect**: Use for runtime checking whether a value is SideEffect
|
|
166
|
+
|
|
167
|
+
**Always provide generics to `runPipeResult`** for type safety. Use `isSideEffect` for runtime type checking.
|
|
168
|
+
|
|
169
|
+
## Stream Functions - Lazy Iterable Processing
|
|
170
|
+
|
|
171
|
+
**Use `stream/*` functions for lazy, memory-efficient data processing** instead of array methods.
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { pipe } from 'fp-pack';
|
|
175
|
+
import { map, filter, take, toArray, range } from 'fp-pack/stream';
|
|
176
|
+
|
|
177
|
+
// GOOD: Lazy stream processing
|
|
178
|
+
const processLargeDataset = pipe(
|
|
179
|
+
filter((n: number) => n % 2 === 0),
|
|
180
|
+
map(n => n * n),
|
|
181
|
+
take(100),
|
|
182
|
+
toArray
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// Processes only what's needed - memory efficient
|
|
186
|
+
const result = processLargeDataset(range(1, 1000000));
|
|
187
|
+
|
|
188
|
+
// BAD: Eager array processing
|
|
189
|
+
const result = Array.from({ length: 1000000 }, (_, i) => i + 1)
|
|
190
|
+
.filter(n => n % 2 === 0)
|
|
191
|
+
.map(n => n * n)
|
|
192
|
+
.slice(0, 100); // Processed entire dataset!
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Stream functions support both sync and async iterables:**
|
|
196
|
+
- Sync: `Iterable<T>` → `IterableIterator<R>`
|
|
197
|
+
- Async: `AsyncIterable<T>` → `AsyncIterableIterator<R>`
|
|
198
|
+
|
|
199
|
+
## Available Functions by Category
|
|
200
|
+
|
|
201
|
+
### Composition
|
|
202
|
+
- `pipe` - Left-to-right function composition (sync)
|
|
203
|
+
- `pipeSideEffect` - Left-to-right composition with SideEffect short-circuiting
|
|
204
|
+
- `compose` - Right-to-left function composition
|
|
205
|
+
- `curry` - Curry a function
|
|
206
|
+
- `partial` - Partial application
|
|
207
|
+
- `flip` - Flip function argument order
|
|
208
|
+
- `complement` - Logical negation
|
|
209
|
+
- `identity` - Return input unchanged
|
|
210
|
+
- `constant` - Always return the same value
|
|
211
|
+
- `tap` - Execute side effect and return original value
|
|
212
|
+
- `once` - Execute function only once
|
|
213
|
+
- `memoize` - Cache function results
|
|
214
|
+
- `SideEffect` - Side effect container
|
|
215
|
+
- `isSideEffect` - Type guard for SideEffect
|
|
216
|
+
- `matchSideEffect` - Pattern match on value/SideEffect
|
|
217
|
+
- `runPipeResult` - Execute SideEffect or return value
|
|
218
|
+
|
|
219
|
+
### Async
|
|
220
|
+
- `pipeAsync` - Async function composition
|
|
221
|
+
- `pipeAsyncSideEffect` - Async composition with SideEffect short-circuiting
|
|
222
|
+
- `delay` - Delay execution
|
|
223
|
+
- `timeout` - Add timeout to promise
|
|
224
|
+
- `retry` - Retry failed operations
|
|
225
|
+
- `debounce` - Debounce function calls
|
|
226
|
+
- `debounceLeading` - Debounce with leading edge
|
|
227
|
+
- `debounceLeadingTrailing` - Debounce with both edges
|
|
228
|
+
- `throttle` - Throttle function calls
|
|
229
|
+
|
|
230
|
+
### Array
|
|
231
|
+
- `map`, `filter`, `reduce`, `flatMap`
|
|
232
|
+
- `find`, `some`, `every`
|
|
233
|
+
- `take`, `drop`, `takeWhile`, `dropWhile`
|
|
234
|
+
- `chunk`, `zip`, `zipWith`, `unzip`, `zipIndex`
|
|
235
|
+
- `uniq`, `uniqBy`, `sort`, `sortBy`, `groupBy`
|
|
236
|
+
- `concat`, `append`, `prepend`, `flatten`, `flattenDeep`
|
|
237
|
+
- `head`, `tail`, `last`, `init`
|
|
238
|
+
- `range`, `partition`, `scan`
|
|
239
|
+
|
|
240
|
+
### Object
|
|
241
|
+
- `prop`, `propOr`, `path`, `pathOr`
|
|
242
|
+
- `pick`, `omit`
|
|
243
|
+
- `assoc`, `assocPath`, `dissoc`, `dissocPath`
|
|
244
|
+
- `merge`, `mergeDeep`, `mergeAll`
|
|
245
|
+
- `keys`, `values`, `entries`
|
|
246
|
+
- `mapValues`, `evolve`
|
|
247
|
+
- `has`, `hasPath`
|
|
248
|
+
|
|
249
|
+
### Control Flow
|
|
250
|
+
- `ifElse` - Conditional branching
|
|
251
|
+
- `when`, `unless` - Conditional execution
|
|
252
|
+
- `cond` - Multi-branch conditional
|
|
253
|
+
- `tryCatch` - Safe function execution
|
|
254
|
+
- `guard` - Validation guard
|
|
255
|
+
|
|
256
|
+
### Stream (Lazy Iterables)
|
|
257
|
+
- `append`, `concat`, `prepend`
|
|
258
|
+
- `map`, `filter`, `flatMap`, `flatten`, `flattenDeep`
|
|
259
|
+
- `take`, `takeWhile`, `drop`, `dropWhile`, `chunk`
|
|
260
|
+
- `zip`, `zipWith`, `find`, `some`, `every`
|
|
261
|
+
- `reduce`, `scan`
|
|
262
|
+
- `range`
|
|
263
|
+
- `toArray` - Materialize stream to array
|
|
264
|
+
- `toAsync` - Convert to async iterable
|
|
265
|
+
|
|
266
|
+
### Math
|
|
267
|
+
- `add`, `sub`, `mul`, `div`
|
|
268
|
+
- `sum`, `mean`, `min`, `max`
|
|
269
|
+
- `round`, `floor`, `ceil`, `randomInt`
|
|
270
|
+
|
|
271
|
+
### String
|
|
272
|
+
- `trim`, `split`, `join`, `replace`
|
|
273
|
+
- `toUpper`, `toLower`
|
|
274
|
+
- `startsWith`, `endsWith`, `match`
|
|
275
|
+
|
|
276
|
+
### Equality
|
|
277
|
+
- `equals`, `includes`
|
|
278
|
+
- `isNil`, `isEmpty`, `isType`
|
|
279
|
+
- `gt`, `gte`, `lt`, `lte`
|
|
280
|
+
- `clamp`
|
|
281
|
+
|
|
282
|
+
### Nullable
|
|
283
|
+
- `maybe`, `mapMaybe`, `getOrElse`, `fold`, `result`
|
|
284
|
+
|
|
285
|
+
### Debug
|
|
286
|
+
- `assert`, `invariant`, `log`
|
|
287
|
+
|
|
288
|
+
## Coding Guidelines for AI Agents
|
|
289
|
+
|
|
290
|
+
### 0. Detect Project Language (JS vs TS)
|
|
291
|
+
|
|
292
|
+
Before writing code, check whether the project is JavaScript or TypeScript:
|
|
293
|
+
|
|
294
|
+
- **TypeScript projects**: use explicit types, leverage generics, and keep type-safe signatures in examples.
|
|
295
|
+
- **JavaScript projects**: avoid TypeScript-only syntax and prefer JSDoc only when it adds clarity or is already used.
|
|
296
|
+
|
|
297
|
+
### 0.1 Quick Signature Lookup
|
|
298
|
+
|
|
299
|
+
If a function signature or argument order is unclear, check the local declaration or source files:
|
|
300
|
+
|
|
301
|
+
- Main exports: `dist/index.d.ts`
|
|
302
|
+
- Stream exports: `dist/stream/index.d.ts`
|
|
303
|
+
- Main utilities (fallback): `src/implement/**`
|
|
304
|
+
- Stream utilities (fallback): `src/stream/**`
|
|
305
|
+
- Installed package:
|
|
306
|
+
- `node_modules/fp-pack/dist/index.d.ts`
|
|
307
|
+
- `node_modules/fp-pack/dist/stream/index.d.ts`
|
|
308
|
+
- `node_modules/fp-pack/src/implement/**`
|
|
309
|
+
- `node_modules/fp-pack/src/stream/**`
|
|
310
|
+
|
|
311
|
+
### 1. Always Prefer pipe/pipeAsync
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
// GOOD
|
|
315
|
+
const result = pipe(
|
|
316
|
+
trim,
|
|
317
|
+
split(','),
|
|
318
|
+
map(toNumber),
|
|
319
|
+
filter(isPositive)
|
|
320
|
+
)(input);
|
|
321
|
+
|
|
322
|
+
// BAD
|
|
323
|
+
const trimmed = trim(input);
|
|
324
|
+
const parts = split(',')(trimmed);
|
|
325
|
+
const numbers = map(toNumber)(parts);
|
|
326
|
+
const result = filter(isPositive)(numbers);
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### 2. Use Curried Functions (Where Available)
|
|
330
|
+
|
|
331
|
+
Most multi-arg functions are curried. Many single-arg utilities are not (e.g. `uniq`, `flatten`, `flattenDeep`, `head`, `tail`, `last`, `init`, `range`, `sum`, `mean`, `min`, `max`, `round`, `floor`, `ceil`, `trim`, `toLower`, `toUpper`, `isNil`, `isEmpty`, `isType`). Call those directly (there's no curried variant).
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
import { pipe, map, filter } from 'fp-pack';
|
|
335
|
+
|
|
336
|
+
// GOOD: Curried usage in pipe
|
|
337
|
+
const processUsers = pipe(
|
|
338
|
+
filter(user => user.active),
|
|
339
|
+
map(user => user.name)
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
// GOOD: Partial application
|
|
343
|
+
const filterActive = filter((user: User) => user.active);
|
|
344
|
+
const getNames = map((user: User) => user.name);
|
|
345
|
+
const processUsers = pipe(filterActive, getNames);
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### 2.1 Custom Utility Authoring (Curry Typing)
|
|
349
|
+
|
|
350
|
+
When you add your own helpers for `pipe`, follow these rules:
|
|
351
|
+
|
|
352
|
+
- Keep **data-last** argument order.
|
|
353
|
+
- **Curry multi-arg functions** so they compose well.
|
|
354
|
+
- **Fixed signatures** can use `curry(fn)` directly.
|
|
355
|
+
- **Generic or overloaded signatures** should use an explicit type alias + cast to preserve inference.
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
// Fixed signature: curry is enough
|
|
359
|
+
function split(separator: string, str: string): string[] {
|
|
360
|
+
return str.split(separator);
|
|
361
|
+
}
|
|
362
|
+
export default curry(split);
|
|
363
|
+
|
|
364
|
+
// Generic signature: add a type alias for the curried form
|
|
365
|
+
type Chunk = {
|
|
366
|
+
(size: number): <T>(arr: T[]) => T[][];
|
|
367
|
+
<T>(size: number, arr: T[]): T[][];
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
function chunk<T>(size: number, arr: T[]): T[][] {
|
|
371
|
+
// ...
|
|
372
|
+
return [];
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const curriedChunk = curry(chunk) as Chunk;
|
|
376
|
+
export default curriedChunk;
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### 3. Choose pipe vs pipeSideEffect
|
|
380
|
+
|
|
381
|
+
**Default choice: Start with `pipe` / `pipeAsync`**
|
|
382
|
+
|
|
383
|
+
Most data transformations are pure and don't need SideEffect handling. Use `pipe` for sync operations and `pipeAsync` for async operations. **Only switch to SideEffect-aware pipes when you actually need** early termination or error handling with side effects.
|
|
384
|
+
|
|
385
|
+
- **`pipe`** - Synchronous, **pure** transformations (99% of cases)
|
|
386
|
+
- **`pipeAsync`** - Async, **pure** transformations (99% of cases)
|
|
387
|
+
- **`pipeSideEffect`** - **Only when you need** SideEffect short-circuiting (sync)
|
|
388
|
+
- **`pipeAsyncSideEffect`** - **Only when you need** SideEffect short-circuiting (async)
|
|
389
|
+
|
|
390
|
+
**Important:** `pipe` and `pipeAsync` are for **pure** functions only—they don't handle `SideEffect`. If your pipeline can return `SideEffect`, use `pipeSideEffect` or `pipeAsyncSideEffect` instead.
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
// Sync: use pipe
|
|
394
|
+
const processNumbers = pipe(
|
|
395
|
+
map((n: number) => n * 2),
|
|
396
|
+
filter(n => n > 10)
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
// Async: use pipeAsync
|
|
400
|
+
const processUsers = pipeAsync(
|
|
401
|
+
async (ids: string[]) => db.users.findMany(ids),
|
|
402
|
+
map(user => user.profile),
|
|
403
|
+
filter(profile => profile.verified)
|
|
404
|
+
);
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### 3.1. SideEffect Composition Rule
|
|
408
|
+
|
|
409
|
+
**🔄 Critical Rule: SideEffect Contagion**
|
|
410
|
+
|
|
411
|
+
Once you use `pipeSideEffect` or `pipeAsyncSideEffect`, the result is **always `T | SideEffect`** (or `Promise<T | SideEffect>` for async).
|
|
412
|
+
|
|
413
|
+
If you want to continue composing this result, you **MUST** keep using SideEffect-aware pipes. You **CANNOT** switch back to `pipe` or `pipeAsync` because they don't handle `SideEffect`.
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
import { pipe, pipeSideEffect, SideEffect } from 'fp-pack';
|
|
417
|
+
|
|
418
|
+
const validateUserPipeline = pipeSideEffect(
|
|
419
|
+
findUser,
|
|
420
|
+
validateAge
|
|
421
|
+
);
|
|
422
|
+
// Result type: User | SideEffect
|
|
423
|
+
|
|
424
|
+
// ❌ WRONG - pipe cannot handle SideEffect
|
|
425
|
+
const wrongPipeline = pipe(
|
|
426
|
+
validateUserPipeline, // Returns User | SideEffect
|
|
427
|
+
(user) => user.email // Type error! SideEffect has no 'email' property
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
// ✅ CORRECT - Keep using pipeSideEffect
|
|
431
|
+
const correctPipeline = pipeSideEffect(
|
|
432
|
+
validateUserPipeline, // User | SideEffect - handled correctly
|
|
433
|
+
(user) => user.email, // Automatically skipped if SideEffect
|
|
434
|
+
sendEmail
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
// The same rule applies to async pipes
|
|
438
|
+
const asyncPipeline = pipeAsyncSideEffect(
|
|
439
|
+
fetchUser,
|
|
440
|
+
validateUser
|
|
441
|
+
);
|
|
442
|
+
// Result type: Promise<User | SideEffect>
|
|
443
|
+
|
|
444
|
+
// You must continue with pipeAsyncSideEffect, not pipeAsync
|
|
445
|
+
const extendedAsyncPipeline = pipeAsyncSideEffect(
|
|
446
|
+
asyncPipeline,
|
|
447
|
+
processUser,
|
|
448
|
+
saveToDatabase
|
|
449
|
+
);
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### 4. Use stream/* for Large Datasets
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
import { pipe } from 'fp-pack';
|
|
456
|
+
import { filter, map, take, toArray, range } from 'fp-pack/stream';
|
|
457
|
+
|
|
458
|
+
// GOOD: Lazy processing
|
|
459
|
+
const getFirst100Even = pipe(
|
|
460
|
+
filter((n: number) => n % 2 === 0),
|
|
461
|
+
take(100),
|
|
462
|
+
toArray
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
// Stops after finding 100 items (only processes 100, not 1 million)
|
|
466
|
+
const result = getFirst100Even(range(1, 1000000));
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### 5. Handle Errors with SideEffect
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
import { pipeSideEffect, SideEffect, runPipeResult } from 'fp-pack';
|
|
473
|
+
|
|
474
|
+
const safeDividePipeline = pipeSideEffect(
|
|
475
|
+
(input: { a: number; b: number }) => {
|
|
476
|
+
if (input.b === 0) {
|
|
477
|
+
return SideEffect.of(() => {
|
|
478
|
+
throw new Error('Division by zero');
|
|
479
|
+
}, 'DIVISION_ERROR');
|
|
480
|
+
}
|
|
481
|
+
return input;
|
|
482
|
+
},
|
|
483
|
+
({ a, b }) => a / b
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
// runPipeResult must be called OUTSIDE the pipeline
|
|
487
|
+
const result = runPipeResult(safeDividePipeline({ a: 10, b: 2 })); // 5
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### 6. Use Control Flow Functions
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
import { pipe, ifElse, when, cond } from 'fp-pack';
|
|
494
|
+
|
|
495
|
+
// GOOD: Declarative conditionals
|
|
496
|
+
const processAge = pipe(
|
|
497
|
+
ifElse(
|
|
498
|
+
(age: number) => age >= 18,
|
|
499
|
+
age => ({ age, status: 'adult' }),
|
|
500
|
+
age => ({ age, status: 'minor' })
|
|
501
|
+
)
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
// GOOD: Multi-branch with cond
|
|
505
|
+
const gradeToLetter = cond([
|
|
506
|
+
[(n: number) => n >= 90, () => 'A'],
|
|
507
|
+
[(n: number) => n >= 80, () => 'B'],
|
|
508
|
+
[(n: number) => n >= 70, () => 'C'],
|
|
509
|
+
[() => true, () => 'F']
|
|
510
|
+
]);
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### 7. Object Transformations
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
import { pipe, pick, mapValues, merge } from 'fp-pack';
|
|
517
|
+
|
|
518
|
+
// GOOD: Declarative object operations
|
|
519
|
+
const processUser = pipe(
|
|
520
|
+
pick(['name', 'email', 'age']),
|
|
521
|
+
mapValues((value) => typeof value === 'string' ? value.trim() : value),
|
|
522
|
+
merge({ verified: false })
|
|
523
|
+
);
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
## Anti-Patterns to Avoid
|
|
527
|
+
|
|
528
|
+
### ❌ Don't use imperative loops
|
|
529
|
+
|
|
530
|
+
```typescript
|
|
531
|
+
// BAD
|
|
532
|
+
const result = [];
|
|
533
|
+
for (const item of items) {
|
|
534
|
+
if (item.active) {
|
|
535
|
+
result.push(item.name);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// GOOD
|
|
540
|
+
const result = pipe(
|
|
541
|
+
filter((item: Item) => item.active),
|
|
542
|
+
map(item => item.name)
|
|
543
|
+
)(items);
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### ❌ Don't chain array methods
|
|
547
|
+
|
|
548
|
+
```typescript
|
|
549
|
+
// BAD
|
|
550
|
+
const result = users
|
|
551
|
+
.filter(u => u.active)
|
|
552
|
+
.map(u => u.name)
|
|
553
|
+
.slice(0, 10);
|
|
554
|
+
|
|
555
|
+
// GOOD
|
|
556
|
+
const result = pipe(
|
|
557
|
+
filter((u: User) => u.active),
|
|
558
|
+
map(u => u.name),
|
|
559
|
+
take(10)
|
|
560
|
+
)(users);
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### ❌ Don't use traditional monads (Option, Either, Maybe)
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
// BAD - Don't implement this pattern
|
|
567
|
+
const maybeUser = Option.of(user)
|
|
568
|
+
.map(u => u.profile)
|
|
569
|
+
.flatMap(p => p.email);
|
|
570
|
+
|
|
571
|
+
// GOOD - Use SideEffect with pipeSideEffect
|
|
572
|
+
const getUserEmail = pipeSideEffect(
|
|
573
|
+
(user: User) => {
|
|
574
|
+
if (!user.profile) {
|
|
575
|
+
return SideEffect.of(() => null, 'NO_PROFILE');
|
|
576
|
+
}
|
|
577
|
+
return user.profile;
|
|
578
|
+
},
|
|
579
|
+
(profile) => {
|
|
580
|
+
if (!profile.email) {
|
|
581
|
+
return SideEffect.of(() => null, 'NO_EMAIL');
|
|
582
|
+
}
|
|
583
|
+
return profile.email;
|
|
584
|
+
}
|
|
585
|
+
);
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
### ❌ Don't mutate data
|
|
589
|
+
|
|
590
|
+
```typescript
|
|
591
|
+
// BAD
|
|
592
|
+
const updateUser = (user: User) => {
|
|
593
|
+
user.lastLogin = new Date();
|
|
594
|
+
return user;
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
// GOOD
|
|
598
|
+
const updateUser = (user: User) => ({
|
|
599
|
+
...user,
|
|
600
|
+
lastLogin: new Date()
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
// EVEN BETTER with fp-pack
|
|
604
|
+
import { assoc } from 'fp-pack';
|
|
605
|
+
const updateUser = assoc('lastLogin', new Date());
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
## Quick Reference
|
|
609
|
+
|
|
610
|
+
### Import Paths
|
|
611
|
+
- Main functions: `import { pipe, map, filter } from 'fp-pack'`
|
|
612
|
+
- Async: `import { pipeAsync, delay, retry } from 'fp-pack'`
|
|
613
|
+
- SideEffect: `import { pipeSideEffect, pipeAsyncSideEffect, SideEffect } from 'fp-pack'`
|
|
614
|
+
- Stream: `import { map, filter, toArray } from 'fp-pack/stream'`
|
|
615
|
+
|
|
616
|
+
### When to Use What
|
|
617
|
+
- **Pure sync transformations**: `pipe` + array/object functions
|
|
618
|
+
- **Pure async operations**: `pipeAsync`
|
|
619
|
+
- **Error handling with SideEffect**: `pipeSideEffect` (sync) / `pipeAsyncSideEffect` (async)
|
|
620
|
+
- **Type-safe result handling**: `isSideEffect` for precise type narrowing (⚠️ **ALWAYS prefer this over bare `runPipeResult`** to avoid `any` types)
|
|
621
|
+
- **Execute SideEffect**: `runPipeResult` (call OUTSIDE pipelines). **⚠️ CRITICAL:** Returns `any` type without type narrowing due to default `R=any` parameter
|
|
622
|
+
- **Large datasets**: `stream/*` functions
|
|
623
|
+
- **Conditionals**: `ifElse`, `when`, `unless`, `cond`
|
|
624
|
+
- **Object access**: `prop`, `path`, `pick`, `omit`
|
|
625
|
+
- **Object updates**: `assoc`, `merge`, `evolve`
|
|
626
|
+
|
|
627
|
+
## UI Framework Integration Patterns
|
|
628
|
+
|
|
629
|
+
fp-pack works seamlessly with UI frameworks. Here are common patterns organized by **use case**, not framework.
|
|
630
|
+
|
|
631
|
+
### Pattern 1: Handling User Input
|
|
632
|
+
|
|
633
|
+
**When**: Form inputs, button clicks, drag & drop, any user interaction
|
|
634
|
+
**Where to use**: Event handlers (onChange, @input, on:click, etc.)
|
|
635
|
+
|
|
636
|
+
```typescript
|
|
637
|
+
import { pipe, pipeAsyncSideEffect, trim, prop, assoc, tap, SideEffect, runPipeResult } from 'fp-pack';
|
|
638
|
+
|
|
639
|
+
// GOOD: Process form input declaratively
|
|
640
|
+
const handleNameChange = pipe(
|
|
641
|
+
prop('currentTarget'), // Safer than target in most UI libs
|
|
642
|
+
(el) => (el as HTMLInputElement).value,
|
|
643
|
+
trim,
|
|
644
|
+
tap((value) => {
|
|
645
|
+
// Prefer updater form to avoid stale state in React-like frameworks
|
|
646
|
+
setFormState(prev => assoc('name', value, prev));
|
|
647
|
+
})
|
|
648
|
+
);
|
|
649
|
+
|
|
650
|
+
// Use in any framework:
|
|
651
|
+
// React: <input onChange={handleNameChange} />
|
|
652
|
+
// Vue: <input @input="handleNameChange" />
|
|
653
|
+
// Svelte: <input on:input={handleNameChange} />
|
|
654
|
+
|
|
655
|
+
// GOOD: Complex form validation
|
|
656
|
+
const validateFieldsOrStop = (data: any) => {
|
|
657
|
+
const errors = validateFields(data);
|
|
658
|
+
if (!errors) return data;
|
|
659
|
+
return SideEffect.of(() => {
|
|
660
|
+
setErrors(errors);
|
|
661
|
+
return null;
|
|
662
|
+
}, 'VALIDATION_ERROR');
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
const handleSubmitPipeline = pipeAsyncSideEffect(
|
|
666
|
+
tap((e: Event) => e.preventDefault()),
|
|
667
|
+
prop('currentTarget'),
|
|
668
|
+
(form) => getFormData(form as HTMLFormElement),
|
|
669
|
+
validateFieldsOrStop, // Returns data or SideEffect
|
|
670
|
+
sanitizeInput,
|
|
671
|
+
submitToAPI
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
const handleSubmit = (e: Event) => runPipeResult(handleSubmitPipeline(e));
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### Pattern 2: Computing Derived/Reactive Values
|
|
678
|
+
|
|
679
|
+
**When**: Displaying filtered/sorted/transformed data from state
|
|
680
|
+
**Where to use**: Computed properties, memoized values, derived state
|
|
681
|
+
|
|
682
|
+
```typescript
|
|
683
|
+
import { pipe, filter, sortBy, map, take } from 'fp-pack';
|
|
684
|
+
|
|
685
|
+
// GOOD: Create reusable data transformation
|
|
686
|
+
const processUsers = pipe(
|
|
687
|
+
filter((u: User) => u.status === 'active'),
|
|
688
|
+
sortBy(u => u.lastLogin),
|
|
689
|
+
map(u => ({ ...u, displayName: `${u.firstName} ${u.lastName}` })),
|
|
690
|
+
take(50)
|
|
691
|
+
);
|
|
692
|
+
|
|
693
|
+
// Use in any framework:
|
|
694
|
+
// React: const processed = useMemo(() => processUsers(users), [users]);
|
|
695
|
+
// Vue: const processed = computed(() => processUsers(users.value));
|
|
696
|
+
// Svelte: $: processed = processUsers($users);
|
|
697
|
+
// Solid: const processed = createMemo(() => processUsers(users()));
|
|
698
|
+
|
|
699
|
+
// GOOD: Search + filter + pagination
|
|
700
|
+
const searchUsers = (query: string, page: number) =>
|
|
701
|
+
pipe(
|
|
702
|
+
filter((u: User) =>
|
|
703
|
+
u.name.toLowerCase().includes(query.toLowerCase())
|
|
704
|
+
),
|
|
705
|
+
sortBy(u => u.name),
|
|
706
|
+
chunk(20), // Paginate
|
|
707
|
+
(pages) => pages[page] || []
|
|
708
|
+
);
|
|
709
|
+
|
|
710
|
+
// React example:
|
|
711
|
+
// const results = useMemo(
|
|
712
|
+
// () => searchUsers(searchQuery, currentPage)(allUsers),
|
|
713
|
+
// [searchQuery, currentPage, allUsers]
|
|
714
|
+
// );
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
### Pattern 3: Async Data Fetching and Processing
|
|
718
|
+
|
|
719
|
+
**When**: API calls, database queries, file operations
|
|
720
|
+
**Where to use**: Lifecycle hooks, effects, async event handlers
|
|
721
|
+
|
|
722
|
+
```typescript
|
|
723
|
+
import { pipeAsync, pipeAsyncSideEffect, tap, SideEffect, runPipeResult } from 'fp-pack';
|
|
724
|
+
import { filter, map } from 'fp-pack';
|
|
725
|
+
|
|
726
|
+
// GOOD: Fetch + transform + update state
|
|
727
|
+
const fetchAndProcessUsers = pipeAsync(
|
|
728
|
+
async (userId: string) => fetch(`/api/users/${userId}/friends`),
|
|
729
|
+
async (res) => res.json(),
|
|
730
|
+
filter((u: User) => u.isActive),
|
|
731
|
+
map(u => ({ id: u.id, name: u.name, avatar: u.avatar })),
|
|
732
|
+
(processed) => {
|
|
733
|
+
setUsers(processed); // Framework-specific state update
|
|
734
|
+
return processed;
|
|
735
|
+
}
|
|
736
|
+
);
|
|
737
|
+
|
|
738
|
+
// Use in any framework:
|
|
739
|
+
// React: useEffect(() => { fetchAndProcessUsers(id); }, [id]);
|
|
740
|
+
// Vue: watchEffect(() => fetchAndProcessUsers(userId.value));
|
|
741
|
+
// Svelte: $: fetchAndProcessUsers($userId);
|
|
742
|
+
|
|
743
|
+
// GOOD: Error handling with SideEffect
|
|
744
|
+
const validateResponseOrStop = (users: unknown) => {
|
|
745
|
+
if (!Array.isArray(users)) {
|
|
746
|
+
return SideEffect.of(() => {
|
|
747
|
+
setError('Invalid response');
|
|
748
|
+
return [];
|
|
749
|
+
}, 'INVALID_RESPONSE');
|
|
750
|
+
}
|
|
751
|
+
return users as User[];
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
const safeFetchUsersPipeline = pipeAsyncSideEffect(
|
|
755
|
+
fetchUsers,
|
|
756
|
+
validateResponseOrStop,
|
|
757
|
+
filter((u: User) => u.verified),
|
|
758
|
+
tap((users) => setUsers(users))
|
|
759
|
+
);
|
|
760
|
+
|
|
761
|
+
const safeFetchUsers = () => runPipeResult(safeFetchUsersPipeline());
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
### Pattern 4: List/Table Data Processing
|
|
765
|
+
|
|
766
|
+
**When**: Displaying lists, tables, grids with search/filter/sort
|
|
767
|
+
**Where to use**: Component render logic, computed values
|
|
768
|
+
|
|
769
|
+
```typescript
|
|
770
|
+
import { pipe, filter, sortBy, groupBy, map } from 'fp-pack';
|
|
771
|
+
|
|
772
|
+
// GOOD: Complete table data pipeline
|
|
773
|
+
const processTableData = (
|
|
774
|
+
data: Product[],
|
|
775
|
+
filters: Filters,
|
|
776
|
+
sortConfig: SortConfig
|
|
777
|
+
) => pipe(
|
|
778
|
+
// Apply filters
|
|
779
|
+
filter((p: Product) => {
|
|
780
|
+
if (filters.category && p.category !== filters.category) return false;
|
|
781
|
+
if (filters.minPrice && p.price < filters.minPrice) return false;
|
|
782
|
+
if (filters.maxPrice && p.price > filters.maxPrice) return false;
|
|
783
|
+
return true;
|
|
784
|
+
}),
|
|
785
|
+
// Apply sorting
|
|
786
|
+
sortBy(sortConfig.direction === 'asc'
|
|
787
|
+
? (p) => p[sortConfig.key]
|
|
788
|
+
: (p) => -p[sortConfig.key]
|
|
789
|
+
),
|
|
790
|
+
// Add row metadata
|
|
791
|
+
map((product, index) => ({
|
|
792
|
+
...product,
|
|
793
|
+
rowId: `row-${index}`,
|
|
794
|
+
isEven: index % 2 === 0
|
|
795
|
+
}))
|
|
796
|
+
)(data);
|
|
797
|
+
|
|
798
|
+
// GOOD: Group for categorized display
|
|
799
|
+
const groupProductsByCategory = pipe(
|
|
800
|
+
groupBy((p: Product) => p.category),
|
|
801
|
+
(grouped) => Object.entries(grouped).map(([category, products]) => ({
|
|
802
|
+
category,
|
|
803
|
+
products,
|
|
804
|
+
count: products.length,
|
|
805
|
+
totalValue: products.reduce((sum, p) => sum + p.price, 0)
|
|
806
|
+
}))
|
|
807
|
+
);
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
### Pattern 5: Form State Management
|
|
811
|
+
|
|
812
|
+
**When**: Complex forms with validation and state
|
|
813
|
+
**Where to use**: Form submission, field updates, validation
|
|
814
|
+
|
|
815
|
+
```typescript
|
|
816
|
+
import { pipe, pipeSideEffect, assoc, pick, mapValues, SideEffect, runPipeResult } from 'fp-pack';
|
|
817
|
+
|
|
818
|
+
// GOOD: Update nested form state immutably
|
|
819
|
+
const updateField = (fieldName: string, value: any) =>
|
|
820
|
+
pipe(
|
|
821
|
+
assoc(fieldName, value),
|
|
822
|
+
(state) => assoc('touched', { ...state.touched, [fieldName]: true }, state)
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
// GOOD: Form submission with validation
|
|
826
|
+
const validateFormOrStop = (data: any) => {
|
|
827
|
+
const errors = validateFormData(data);
|
|
828
|
+
return Object.keys(errors).length > 0
|
|
829
|
+
? SideEffect.of(() => {
|
|
830
|
+
setFormErrors(errors);
|
|
831
|
+
return null;
|
|
832
|
+
}, 'VALIDATION_ERROR')
|
|
833
|
+
: data;
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
const submitFormPipeline = pipeSideEffect(
|
|
837
|
+
pick(['email', 'password', 'name']), // Only include relevant fields
|
|
838
|
+
mapValues((v) => typeof v === 'string' ? v.trim() : v), // Sanitize
|
|
839
|
+
validateFormOrStop,
|
|
840
|
+
submitToAPI
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
const submitForm = (data: any) => runPipeResult(submitFormPipeline(data));
|
|
844
|
+
|
|
845
|
+
// GOOD: Multi-step form state
|
|
846
|
+
const validateCurrentStepOrStop = (state: any) => {
|
|
847
|
+
const errors = validateCurrentStep(state);
|
|
848
|
+
if (!errors) return state;
|
|
849
|
+
return SideEffect.of(() => {
|
|
850
|
+
setStepErrors(errors);
|
|
851
|
+
return state;
|
|
852
|
+
}, 'STEP_VALIDATION_ERROR');
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
const goToNextStepPipeline = pipeSideEffect(
|
|
856
|
+
validateCurrentStepOrStop,
|
|
857
|
+
(state) => assoc('currentStep', state.currentStep + 1, state)
|
|
858
|
+
);
|
|
859
|
+
|
|
860
|
+
const goToNextStep = (state: any) => runPipeResult(goToNextStepPipeline(state));
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
### Pattern 6: Real-time Data Streams
|
|
864
|
+
|
|
865
|
+
**When**: WebSocket updates, SSE, real-time data
|
|
866
|
+
**Where to use**: WebSocket handlers, event listeners
|
|
867
|
+
|
|
868
|
+
```typescript
|
|
869
|
+
import { pipe, filter, map, take } from 'fp-pack';
|
|
870
|
+
|
|
871
|
+
// GOOD: Process incoming WebSocket messages
|
|
872
|
+
const handleWebSocketMessage = pipe(
|
|
873
|
+
(event: MessageEvent) => JSON.parse(event.data),
|
|
874
|
+
filter((msg: Message) => msg.type === 'USER_UPDATE'),
|
|
875
|
+
map(msg => msg.payload),
|
|
876
|
+
(update) => {
|
|
877
|
+
// Update state with new data
|
|
878
|
+
setUsers(prevUsers =>
|
|
879
|
+
prevUsers.map(u => u.id === update.id ? { ...u, ...update } : u)
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
);
|
|
883
|
+
|
|
884
|
+
// websocket.onmessage = handleWebSocketMessage;
|
|
885
|
+
|
|
886
|
+
// GOOD: Batch updates with stream
|
|
887
|
+
import { pipe as streamPipe, filter as streamFilter, take as streamTake, toArray } from 'fp-pack/stream';
|
|
888
|
+
import { pipeAsync, runPipeResult } from 'fp-pack';
|
|
889
|
+
|
|
890
|
+
const processBatchUpdates = async (updates: AsyncIterable<Update>) => {
|
|
891
|
+
const processed = await streamPipe(
|
|
892
|
+
streamFilter((u: Update) => u.priority === 'high'),
|
|
893
|
+
streamTake(100),
|
|
894
|
+
toArray
|
|
895
|
+
)(updates);
|
|
896
|
+
|
|
897
|
+
batchUpdateUI(processed);
|
|
898
|
+
};
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
### Pattern 7: Component Props Transformation
|
|
902
|
+
|
|
903
|
+
**When**: Passing data to child components
|
|
904
|
+
**Where to use**: Component composition, prop drilling
|
|
905
|
+
|
|
906
|
+
```typescript
|
|
907
|
+
import { pipe, pick, map, merge } from 'fp-pack';
|
|
908
|
+
|
|
909
|
+
// GOOD: Transform data for child component
|
|
910
|
+
const prepareUserCardProps = pipe(
|
|
911
|
+
pick(['id', 'name', 'avatar', 'status']),
|
|
912
|
+
merge({
|
|
913
|
+
onClick: handleUserClick,
|
|
914
|
+
className: 'user-card'
|
|
915
|
+
})
|
|
916
|
+
);
|
|
917
|
+
|
|
918
|
+
// Usage:
|
|
919
|
+
// const userProps = prepareUserCardProps(user);
|
|
920
|
+
// <UserCard {...userProps} />
|
|
921
|
+
|
|
922
|
+
// GOOD: Prepare list of component props
|
|
923
|
+
const prepareListItems = pipe(
|
|
924
|
+
filter((item: Item) => item.visible),
|
|
925
|
+
map(item => ({
|
|
926
|
+
key: item.id,
|
|
927
|
+
...pick(['title', 'description', 'icon'], item),
|
|
928
|
+
onClick: () => handleClick(item.id),
|
|
929
|
+
isActive: item.id === activeId
|
|
930
|
+
}))
|
|
931
|
+
);
|
|
932
|
+
|
|
933
|
+
// Usage:
|
|
934
|
+
// {prepareListItems(items).map(props => <ListItem {...props} />)}
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
### Pattern 8: State Update Reducers
|
|
938
|
+
|
|
939
|
+
**When**: Complex state updates, global state management
|
|
940
|
+
**Where to use**: Redux/Zustand/Pinia reducers, state update functions
|
|
941
|
+
|
|
942
|
+
```typescript
|
|
943
|
+
import { pipe, assoc, dissoc, merge, evolve } from 'fp-pack';
|
|
944
|
+
|
|
945
|
+
// GOOD: Redux-style reducer with fp-pack
|
|
946
|
+
const userReducer = (state: State, action: Action) => {
|
|
947
|
+
switch (action.type) {
|
|
948
|
+
case 'ADD_USER':
|
|
949
|
+
return pipe(
|
|
950
|
+
prop('users'),
|
|
951
|
+
append(action.payload),
|
|
952
|
+
(users) => assoc('users', users, state)
|
|
953
|
+
)(state);
|
|
954
|
+
|
|
955
|
+
case 'UPDATE_USER':
|
|
956
|
+
return pipe(
|
|
957
|
+
prop('users'),
|
|
958
|
+
map((u: User) => u.id === action.payload.id
|
|
959
|
+
? merge(u, action.payload.updates)
|
|
960
|
+
: u
|
|
961
|
+
),
|
|
962
|
+
(users) => assoc('users', users, state)
|
|
963
|
+
)(state);
|
|
964
|
+
|
|
965
|
+
case 'DELETE_USER':
|
|
966
|
+
return pipe(
|
|
967
|
+
prop('users'),
|
|
968
|
+
filter((u: User) => u.id !== action.payload),
|
|
969
|
+
(users) => assoc('users', users, state)
|
|
970
|
+
)(state);
|
|
971
|
+
|
|
972
|
+
default:
|
|
973
|
+
return state;
|
|
974
|
+
}
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
// GOOD: Using evolve for nested updates
|
|
978
|
+
const updateNestedState = evolve({
|
|
979
|
+
user: evolve({
|
|
980
|
+
profile: merge({ verified: true }),
|
|
981
|
+
settings: assoc('notifications', false)
|
|
982
|
+
}),
|
|
983
|
+
lastUpdated: () => new Date()
|
|
984
|
+
});
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
### Pattern 9: Optimistic Updates
|
|
988
|
+
|
|
989
|
+
**When**: UI updates before server confirmation
|
|
990
|
+
**Where to use**: Create/update/delete operations
|
|
991
|
+
|
|
992
|
+
```typescript
|
|
993
|
+
import { pipe, pipeAsync, append, filter } from 'fp-pack';
|
|
994
|
+
|
|
995
|
+
// GOOD: Optimistic create with rollback
|
|
996
|
+
const createItemOptimistically = (newItem: Item) => {
|
|
997
|
+
const tempId = `temp-${Date.now()}`;
|
|
998
|
+
const optimisticItem = { ...newItem, id: tempId, pending: true };
|
|
999
|
+
|
|
1000
|
+
// Immediately update UI
|
|
1001
|
+
setItems(pipe(append(optimisticItem)));
|
|
1002
|
+
|
|
1003
|
+
// Then persist
|
|
1004
|
+
return pipeAsync(
|
|
1005
|
+
async () => api.createItem(newItem),
|
|
1006
|
+
(savedItem) => {
|
|
1007
|
+
// Replace temp with real item
|
|
1008
|
+
setItems(
|
|
1009
|
+
pipe(
|
|
1010
|
+
filter((item: Item) => item.id !== tempId),
|
|
1011
|
+
append(savedItem)
|
|
1012
|
+
)
|
|
1013
|
+
);
|
|
1014
|
+
return savedItem;
|
|
1015
|
+
}
|
|
1016
|
+
)().catch((error) => {
|
|
1017
|
+
// Rollback on error
|
|
1018
|
+
setItems(pipe(filter((item: Item) => item.id !== tempId)));
|
|
1019
|
+
throw error;
|
|
1020
|
+
});
|
|
1021
|
+
};
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
### Pattern 10: URL/Query Parameter Handling
|
|
1025
|
+
|
|
1026
|
+
**When**: Syncing UI state with URL
|
|
1027
|
+
**Where to use**: Routing, search parameters, filters
|
|
1028
|
+
|
|
1029
|
+
```typescript
|
|
1030
|
+
import { pipe, pick, mapValues, merge } from 'fp-pack';
|
|
1031
|
+
|
|
1032
|
+
// GOOD: Parse query params to state
|
|
1033
|
+
const parseQueryParams = pipe(
|
|
1034
|
+
(search: string) => new URLSearchParams(search),
|
|
1035
|
+
(params) => ({
|
|
1036
|
+
page: Number(params.get('page')) || 1,
|
|
1037
|
+
query: params.get('q') || '',
|
|
1038
|
+
category: params.get('category') || 'all',
|
|
1039
|
+
sort: params.get('sort') || 'date'
|
|
1040
|
+
})
|
|
1041
|
+
);
|
|
1042
|
+
|
|
1043
|
+
// GOOD: Convert state to query params
|
|
1044
|
+
const stateToQueryParams = pipe(
|
|
1045
|
+
pick(['page', 'query', 'category', 'sort']),
|
|
1046
|
+
(state) => {
|
|
1047
|
+
const params = new URLSearchParams();
|
|
1048
|
+
Object.entries(state).forEach(([key, value]) => {
|
|
1049
|
+
if (value) params.set(key, String(value));
|
|
1050
|
+
});
|
|
1051
|
+
return params.toString();
|
|
1052
|
+
}
|
|
1053
|
+
);
|
|
1054
|
+
|
|
1055
|
+
// Usage in framework router:
|
|
1056
|
+
// const filters = parseQueryParams(location.search);
|
|
1057
|
+
// navigate(`/products?${stateToQueryParams(currentState)}`);
|
|
1058
|
+
```
|
|
1059
|
+
|
|
1060
|
+
### Pattern 11: Infinite Scroll / Virtual Lists
|
|
1061
|
+
|
|
1062
|
+
**When**: Large lists with lazy loading, infinite scroll, virtual rendering
|
|
1063
|
+
**Where to use**: Scroll handlers, pagination, large dataset rendering
|
|
1064
|
+
|
|
1065
|
+
```typescript
|
|
1066
|
+
import { pipe, pipeAsyncSideEffect, when, tap, runPipeResult } from 'fp-pack';
|
|
1067
|
+
import { pipe as streamPipe, filter as streamFilter, take as streamTake, toArray } from 'fp-pack/stream';
|
|
1068
|
+
|
|
1069
|
+
// GOOD: Infinite scroll with pipe - all logic inside
|
|
1070
|
+
const handleScroll = pipe(
|
|
1071
|
+
(e: Event) => e.target as HTMLElement,
|
|
1072
|
+
(el) => ({
|
|
1073
|
+
scrollTop: el.scrollTop,
|
|
1074
|
+
scrollHeight: el.scrollHeight,
|
|
1075
|
+
clientHeight: el.clientHeight,
|
|
1076
|
+
hasMore,
|
|
1077
|
+
isLoading
|
|
1078
|
+
}),
|
|
1079
|
+
// Only load if near bottom, has more data, and not currently loading
|
|
1080
|
+
when(
|
|
1081
|
+
({ scrollTop, scrollHeight, clientHeight, hasMore, isLoading }) =>
|
|
1082
|
+
scrollTop + clientHeight >= scrollHeight - 100 && hasMore && !isLoading,
|
|
1083
|
+
tap(() => loadNextPage())
|
|
1084
|
+
)
|
|
1085
|
+
);
|
|
1086
|
+
|
|
1087
|
+
// GOOD: Load next page with stream processing
|
|
1088
|
+
const loadNextPagePipeline = pipeAsyncSideEffect(
|
|
1089
|
+
async () => {
|
|
1090
|
+
setIsLoading(true);
|
|
1091
|
+
return fetchItemsFromAPI(currentPage);
|
|
1092
|
+
},
|
|
1093
|
+
// Use stream for lazy processing
|
|
1094
|
+
(items) => streamPipe(
|
|
1095
|
+
streamFilter((item: Item) => item.visible),
|
|
1096
|
+
streamTake(pageSize),
|
|
1097
|
+
toArray
|
|
1098
|
+
)(items),
|
|
1099
|
+
tap((newItems) => setItems(prev => [...prev, ...newItems])),
|
|
1100
|
+
tap(() => setCurrentPage(prev => prev + 1)),
|
|
1101
|
+
tap(() => setIsLoading(false))
|
|
1102
|
+
);
|
|
1103
|
+
|
|
1104
|
+
const loadNextPage = () => runPipeResult(loadNextPagePipeline());
|
|
1105
|
+
|
|
1106
|
+
// GOOD: Virtual scroll - calculate visible range in pipe
|
|
1107
|
+
const getVisibleItems = pipe(
|
|
1108
|
+
(scrollTop: number) => ({
|
|
1109
|
+
itemHeight: 50,
|
|
1110
|
+
viewportHeight: 600,
|
|
1111
|
+
bufferSize: 5,
|
|
1112
|
+
scrollTop
|
|
1113
|
+
}),
|
|
1114
|
+
({ itemHeight, viewportHeight, bufferSize, scrollTop }) => ({
|
|
1115
|
+
startIndex: Math.floor(scrollTop / itemHeight),
|
|
1116
|
+
endIndex: Math.ceil((scrollTop + viewportHeight) / itemHeight),
|
|
1117
|
+
bufferSize
|
|
1118
|
+
}),
|
|
1119
|
+
({ startIndex, endIndex, bufferSize }) => ({
|
|
1120
|
+
start: Math.max(0, startIndex - bufferSize),
|
|
1121
|
+
end: endIndex + bufferSize
|
|
1122
|
+
}),
|
|
1123
|
+
({ start, end }) => allItems.slice(start, end)
|
|
1124
|
+
);
|
|
1125
|
+
|
|
1126
|
+
// GOOD: Lazy load with intersection observer
|
|
1127
|
+
const handleIntersection = pipe(
|
|
1128
|
+
(entries: IntersectionObserverEntry[]) => entries[0],
|
|
1129
|
+
when(
|
|
1130
|
+
(entry) => entry.isIntersecting && hasMoreItems && !isLoadingMore,
|
|
1131
|
+
tap(() => loadMoreItems())
|
|
1132
|
+
)
|
|
1133
|
+
);
|
|
1134
|
+
```
|
|
1135
|
+
|
|
1136
|
+
### Pattern 12: Conditional State Updates
|
|
1137
|
+
|
|
1138
|
+
**When**: State updates that depend on conditions
|
|
1139
|
+
**Where to use**: Any state update with business logic
|
|
1140
|
+
|
|
1141
|
+
```typescript
|
|
1142
|
+
import { pipe, ifElse, when, cond, tap, assoc, prop, append, map, filter, merge } from 'fp-pack';
|
|
1143
|
+
|
|
1144
|
+
// GOOD: Use ifElse instead of if/else
|
|
1145
|
+
const toggleUserStatus = pipe(
|
|
1146
|
+
ifElse(
|
|
1147
|
+
(user: User) => user.status === 'active',
|
|
1148
|
+
assoc('status', 'inactive'),
|
|
1149
|
+
assoc('status', 'active')
|
|
1150
|
+
),
|
|
1151
|
+
tap((updatedUser) => setUser(updatedUser))
|
|
1152
|
+
);
|
|
1153
|
+
|
|
1154
|
+
// GOOD: Use ifElse for conditional side effects
|
|
1155
|
+
const saveIfValid = pipe(
|
|
1156
|
+
validateForm,
|
|
1157
|
+
ifElse(
|
|
1158
|
+
(result) => result.isValid,
|
|
1159
|
+
pipe(
|
|
1160
|
+
tap((data) => saveToAPI(data)),
|
|
1161
|
+
tap(() => showSuccessMessage())
|
|
1162
|
+
),
|
|
1163
|
+
tap((result) => setErrors(result.errors))
|
|
1164
|
+
)
|
|
1165
|
+
);
|
|
1166
|
+
|
|
1167
|
+
// GOOD: Use cond for multi-branch logic (instead of switch/if-else chain)
|
|
1168
|
+
const processUserAction = pipe(
|
|
1169
|
+
prop('action'),
|
|
1170
|
+
cond([
|
|
1171
|
+
[
|
|
1172
|
+
(action) => action.type === 'CREATE',
|
|
1173
|
+
pipe(
|
|
1174
|
+
prop('payload'),
|
|
1175
|
+
(user) => append(user),
|
|
1176
|
+
tap((users) => setUsers(users))
|
|
1177
|
+
)
|
|
1178
|
+
],
|
|
1179
|
+
[
|
|
1180
|
+
(action) => action.type === 'UPDATE',
|
|
1181
|
+
pipe(
|
|
1182
|
+
prop('payload'),
|
|
1183
|
+
({ id, updates }) => map((u: User) => u.id === id ? merge(u, updates) : u),
|
|
1184
|
+
tap((users) => setUsers(users))
|
|
1185
|
+
)
|
|
1186
|
+
],
|
|
1187
|
+
[
|
|
1188
|
+
(action) => action.type === 'DELETE',
|
|
1189
|
+
pipe(
|
|
1190
|
+
prop('payload'),
|
|
1191
|
+
(id) => filter((u: User) => u.id !== id),
|
|
1192
|
+
tap((users) => setUsers(users))
|
|
1193
|
+
)
|
|
1194
|
+
],
|
|
1195
|
+
[
|
|
1196
|
+
() => true, // default case
|
|
1197
|
+
tap(() => console.warn('Unknown action'))
|
|
1198
|
+
]
|
|
1199
|
+
])
|
|
1200
|
+
);
|
|
1201
|
+
|
|
1202
|
+
// GOOD: Complex state update with all logic in pipe
|
|
1203
|
+
const updateCartItem = (itemId: string, quantity: number) => pipe(
|
|
1204
|
+
// Get current cart
|
|
1205
|
+
(cart) => cart.items,
|
|
1206
|
+
// Find and update item
|
|
1207
|
+
map((item: CartItem) =>
|
|
1208
|
+
ifElse(
|
|
1209
|
+
() => item.id === itemId,
|
|
1210
|
+
pipe(
|
|
1211
|
+
assoc('quantity', quantity),
|
|
1212
|
+
when(
|
|
1213
|
+
(updated) => updated.quantity <= 0,
|
|
1214
|
+
() => null // Mark for removal
|
|
1215
|
+
)
|
|
1216
|
+
),
|
|
1217
|
+
() => item
|
|
1218
|
+
)(item)
|
|
1219
|
+
),
|
|
1220
|
+
// Remove null items (quantity <= 0)
|
|
1221
|
+
filter((item) => item !== null),
|
|
1222
|
+
// Update state
|
|
1223
|
+
tap((items) => setCart({ items })),
|
|
1224
|
+
// Show notification
|
|
1225
|
+
tap((items) => {
|
|
1226
|
+
const item = items.find(i => i.id === itemId);
|
|
1227
|
+
if (item) showNotification(`Updated ${item.name}`);
|
|
1228
|
+
else showNotification('Item removed from cart');
|
|
1229
|
+
})
|
|
1230
|
+
);
|
|
1231
|
+
```
|
|
1232
|
+
|
|
1233
|
+
## Library Integration Quick Reference
|
|
1234
|
+
|
|
1235
|
+
This section shows how to integrate fp-pack with popular UI libraries. All examples keep logic **inside pipe chains** using fp-pack control flow functions.
|
|
1236
|
+
|
|
1237
|
+
### React Ecosystem
|
|
1238
|
+
|
|
1239
|
+
#### React Query / TanStack Query
|
|
1240
|
+
|
|
1241
|
+
```typescript
|
|
1242
|
+
import { useQuery, useMutation } from '@tanstack/react-query';
|
|
1243
|
+
import { pipe, filter, sortBy, map, tap, when } from 'fp-pack';
|
|
1244
|
+
|
|
1245
|
+
// GOOD: Transform data in select using pipe
|
|
1246
|
+
const { data: activeUsers } = useQuery({
|
|
1247
|
+
queryKey: ['users'],
|
|
1248
|
+
queryFn: fetchUsers,
|
|
1249
|
+
select: pipe(
|
|
1250
|
+
filter((u: User) => u.status === 'active'),
|
|
1251
|
+
sortBy((u) => u.name),
|
|
1252
|
+
map((u) => ({ id: u.id, name: u.name, email: u.email }))
|
|
1253
|
+
)
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
// GOOD: Handle mutations with pipe
|
|
1257
|
+
const mutation = useMutation({
|
|
1258
|
+
mutationFn: createUser,
|
|
1259
|
+
onSuccess: pipe(
|
|
1260
|
+
tap((newUser) => queryClient.invalidateQueries(['users'])),
|
|
1261
|
+
when(
|
|
1262
|
+
(user) => user.isPremium,
|
|
1263
|
+
tap(() => showPremiumWelcome())
|
|
1264
|
+
),
|
|
1265
|
+
tap(() => navigate('/dashboard'))
|
|
1266
|
+
)
|
|
1267
|
+
});
|
|
1268
|
+
|
|
1269
|
+
// GOOD: Optimistic updates in pipe
|
|
1270
|
+
const updateMutation = useMutation({
|
|
1271
|
+
mutationFn: updateUser,
|
|
1272
|
+
onMutate: pipe(
|
|
1273
|
+
tap(async (newUser) => {
|
|
1274
|
+
await queryClient.cancelQueries(['users']);
|
|
1275
|
+
const previous = queryClient.getQueryData(['users']);
|
|
1276
|
+
queryClient.setQueryData(['users'], pipe(
|
|
1277
|
+
map((u: User) => u.id === newUser.id ? merge(u, newUser) : u)
|
|
1278
|
+
));
|
|
1279
|
+
return { previous };
|
|
1280
|
+
})
|
|
1281
|
+
),
|
|
1282
|
+
onError: pipe(
|
|
1283
|
+
tap((err, variables, context) => {
|
|
1284
|
+
if (context?.previous) {
|
|
1285
|
+
queryClient.setQueryData(['users'], context.previous);
|
|
1286
|
+
}
|
|
1287
|
+
})
|
|
1288
|
+
)
|
|
1289
|
+
});
|
|
1290
|
+
```
|
|
1291
|
+
|
|
1292
|
+
#### Zustand
|
|
1293
|
+
|
|
1294
|
+
```typescript
|
|
1295
|
+
import create from 'zustand';
|
|
1296
|
+
import { pipe, append, filter, map, merge, ifElse, when, tap, prop, sortBy, assoc } from 'fp-pack';
|
|
1297
|
+
|
|
1298
|
+
// GOOD: All actions use pipe
|
|
1299
|
+
const useStore = create((set, get) => ({
|
|
1300
|
+
users: [],
|
|
1301
|
+
|
|
1302
|
+
addUser: pipe(
|
|
1303
|
+
(user: User) => user,
|
|
1304
|
+
when(
|
|
1305
|
+
(user) => !get().users.some(u => u.id === user.id),
|
|
1306
|
+
tap((user) => set(pipe(
|
|
1307
|
+
prop('users'),
|
|
1308
|
+
append(user),
|
|
1309
|
+
sortBy((u: User) => u.name),
|
|
1310
|
+
(users) => ({ users })
|
|
1311
|
+
)(get())))
|
|
1312
|
+
)
|
|
1313
|
+
),
|
|
1314
|
+
|
|
1315
|
+
updateUser: (id: string, updates: Partial<User>) => set(pipe(
|
|
1316
|
+
prop('users'),
|
|
1317
|
+
map((u: User) =>
|
|
1318
|
+
ifElse(
|
|
1319
|
+
() => u.id === id,
|
|
1320
|
+
merge(u, updates),
|
|
1321
|
+
() => u
|
|
1322
|
+
)(u)
|
|
1323
|
+
),
|
|
1324
|
+
(users) => ({ users })
|
|
1325
|
+
)(get())),
|
|
1326
|
+
|
|
1327
|
+
deleteUser: (id: string) => set(pipe(
|
|
1328
|
+
prop('users'),
|
|
1329
|
+
filter((u: User) => u.id !== id),
|
|
1330
|
+
(users) => ({ users })
|
|
1331
|
+
)(get())),
|
|
1332
|
+
|
|
1333
|
+
toggleUserStatus: (id: string) => set(pipe(
|
|
1334
|
+
prop('users'),
|
|
1335
|
+
map((u: User) => u.id === id
|
|
1336
|
+
? pipe(
|
|
1337
|
+
ifElse(
|
|
1338
|
+
(user) => user.status === 'active',
|
|
1339
|
+
assoc('status', 'inactive'),
|
|
1340
|
+
assoc('status', 'active')
|
|
1341
|
+
)
|
|
1342
|
+
)(u)
|
|
1343
|
+
: u
|
|
1344
|
+
),
|
|
1345
|
+
(users) => ({ users })
|
|
1346
|
+
)(get()))
|
|
1347
|
+
}));
|
|
1348
|
+
```
|
|
1349
|
+
|
|
1350
|
+
#### Redux Toolkit
|
|
1351
|
+
|
|
1352
|
+
```typescript
|
|
1353
|
+
import { createSlice } from '@reduxjs/toolkit';
|
|
1354
|
+
import { pipe, append, filter, map, merge, sortBy, cond, assoc } from 'fp-pack';
|
|
1355
|
+
|
|
1356
|
+
// GOOD: Reducers with pipe - no manual mutations
|
|
1357
|
+
const userSlice = createSlice({
|
|
1358
|
+
name: 'users',
|
|
1359
|
+
initialState: { list: [], loading: false },
|
|
1360
|
+
reducers: {
|
|
1361
|
+
addUser: (state, action) => {
|
|
1362
|
+
state.list = pipe(
|
|
1363
|
+
append(action.payload),
|
|
1364
|
+
sortBy((u: User) => u.createdAt)
|
|
1365
|
+
)(state.list);
|
|
1366
|
+
},
|
|
1367
|
+
|
|
1368
|
+
updateUser: (state, action) => {
|
|
1369
|
+
state.list = pipe(
|
|
1370
|
+
map((u: User) =>
|
|
1371
|
+
u.id === action.payload.id
|
|
1372
|
+
? merge(u, action.payload.updates)
|
|
1373
|
+
: u
|
|
1374
|
+
)
|
|
1375
|
+
)(state.list);
|
|
1376
|
+
},
|
|
1377
|
+
|
|
1378
|
+
deleteUser: (state, action) => {
|
|
1379
|
+
state.list = pipe(
|
|
1380
|
+
filter((u: User) => u.id !== action.payload)
|
|
1381
|
+
)(state.list);
|
|
1382
|
+
},
|
|
1383
|
+
|
|
1384
|
+
// Complex update with cond
|
|
1385
|
+
processAction: (state, action) => {
|
|
1386
|
+
state.list = pipe(
|
|
1387
|
+
cond([
|
|
1388
|
+
[
|
|
1389
|
+
() => action.type === 'BULK_ACTIVATE',
|
|
1390
|
+
map((u: User) => assoc('status', 'active', u))
|
|
1391
|
+
],
|
|
1392
|
+
[
|
|
1393
|
+
() => action.type === 'BULK_DELETE',
|
|
1394
|
+
filter((u: User) => !action.payload.ids.includes(u.id))
|
|
1395
|
+
],
|
|
1396
|
+
[
|
|
1397
|
+
() => true,
|
|
1398
|
+
(users) => users // no change
|
|
1399
|
+
]
|
|
1400
|
+
])
|
|
1401
|
+
)(state.list);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
1405
|
+
```
|
|
1406
|
+
|
|
1407
|
+
#### React Hook Form
|
|
1408
|
+
|
|
1409
|
+
```typescript
|
|
1410
|
+
import { useForm } from 'react-hook-form';
|
|
1411
|
+
import { pipe, pipeSideEffect, pipeAsyncSideEffect, pick, mapValues, trim, when, tap, SideEffect, runPipeResult } from 'fp-pack';
|
|
1412
|
+
|
|
1413
|
+
// GOOD: Validation with pipeSideEffect
|
|
1414
|
+
const validateFormDataPipeline = pipeSideEffect(
|
|
1415
|
+
pick(['email', 'password', 'name']),
|
|
1416
|
+
mapValues((v) => typeof v === 'string' ? trim(v) : v),
|
|
1417
|
+
(data) => {
|
|
1418
|
+
const errors: any = {};
|
|
1419
|
+
if (!data.email?.includes('@')) errors.email = 'Invalid email';
|
|
1420
|
+
if ((data.password?.length || 0) < 8) errors.password = 'Too short';
|
|
1421
|
+
return Object.keys(errors).length > 0
|
|
1422
|
+
? SideEffect.of(() => ({ values: {}, errors }), 'VALIDATION_ERROR')
|
|
1423
|
+
: { values: data, errors: {} };
|
|
1424
|
+
}
|
|
1425
|
+
);
|
|
1426
|
+
|
|
1427
|
+
const validateFormData = (values: any) => runPipeResult(validateFormDataPipeline(values));
|
|
1428
|
+
|
|
1429
|
+
const { register, handleSubmit } = useForm({
|
|
1430
|
+
resolver: (values) => validateFormData(values)
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
// GOOD: Submit handler with pipeAsyncSideEffect
|
|
1434
|
+
const onSubmitPipeline = pipeAsyncSideEffect(
|
|
1435
|
+
validateFormData,
|
|
1436
|
+
when(
|
|
1437
|
+
(result) => Object.keys(result.errors).length === 0,
|
|
1438
|
+
pipe(
|
|
1439
|
+
prop('values'),
|
|
1440
|
+
submitToAPI,
|
|
1441
|
+
tap(() => navigate('/success'))
|
|
1442
|
+
)
|
|
1443
|
+
)
|
|
1444
|
+
);
|
|
1445
|
+
|
|
1446
|
+
const onSubmit = (data: any) => runPipeResult(onSubmitPipeline(data));
|
|
1447
|
+
```
|
|
1448
|
+
|
|
1449
|
+
### Vue Ecosystem
|
|
1450
|
+
|
|
1451
|
+
#### Pinia
|
|
1452
|
+
|
|
1453
|
+
```typescript
|
|
1454
|
+
import { defineStore } from 'pinia';
|
|
1455
|
+
import { pipe, append, filter, map, merge, sortBy, when, tap } from 'fp-pack';
|
|
1456
|
+
|
|
1457
|
+
// GOOD: All actions use pipe
|
|
1458
|
+
export const useUserStore = defineStore('user', {
|
|
1459
|
+
state: () => ({ users: [], loading: false }),
|
|
1460
|
+
|
|
1461
|
+
actions: {
|
|
1462
|
+
addUser(user: User) {
|
|
1463
|
+
this.users = pipe(
|
|
1464
|
+
append(user),
|
|
1465
|
+
sortBy((u: User) => u.name),
|
|
1466
|
+
when(
|
|
1467
|
+
(users) => users.length > 100,
|
|
1468
|
+
tap(() => this.showWarning('Many users'))
|
|
1469
|
+
)
|
|
1470
|
+
)(this.users);
|
|
1471
|
+
},
|
|
1472
|
+
|
|
1473
|
+
updateUser(id: string, updates: Partial<User>) {
|
|
1474
|
+
this.users = pipe(
|
|
1475
|
+
map((u: User) => u.id === id ? merge(u, updates) : u)
|
|
1476
|
+
)(this.users);
|
|
1477
|
+
},
|
|
1478
|
+
|
|
1479
|
+
deleteUser(id: string) {
|
|
1480
|
+
this.users = pipe(
|
|
1481
|
+
filter((u: User) => u.id !== id),
|
|
1482
|
+
tap((users) => {
|
|
1483
|
+
if (users.length === 0) this.showEmptyState = true;
|
|
1484
|
+
})
|
|
1485
|
+
)(this.users);
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
});
|
|
1489
|
+
```
|
|
1490
|
+
|
|
1491
|
+
#### VueUse
|
|
1492
|
+
|
|
1493
|
+
```typescript
|
|
1494
|
+
import { useFetch } from '@vueuse/core';
|
|
1495
|
+
import { pipe, filter, map, sortBy, tap } from 'fp-pack';
|
|
1496
|
+
|
|
1497
|
+
// GOOD: Transform response with pipe
|
|
1498
|
+
const { data } = useFetch('/api/users', {
|
|
1499
|
+
afterFetch: pipe(
|
|
1500
|
+
prop('data'),
|
|
1501
|
+
filter((u: User) => u.verified),
|
|
1502
|
+
sortBy((u) => u.name),
|
|
1503
|
+
map((u) => ({ id: u.id, name: u.name })),
|
|
1504
|
+
tap((users) => console.log(`Loaded ${users.length} users`))
|
|
1505
|
+
)
|
|
1506
|
+
}).json();
|
|
1507
|
+
|
|
1508
|
+
// GOOD: Refetch with condition in pipe
|
|
1509
|
+
const { execute } = useFetch('/api/users');
|
|
1510
|
+
|
|
1511
|
+
const refreshIfNeeded = pipe(
|
|
1512
|
+
(lastUpdate: Date) => Date.now() - lastUpdate.getTime(),
|
|
1513
|
+
when(
|
|
1514
|
+
(diff) => diff > 5 * 60 * 1000, // 5 minutes
|
|
1515
|
+
tap(() => execute())
|
|
1516
|
+
)
|
|
1517
|
+
);
|
|
1518
|
+
```
|
|
1519
|
+
|
|
1520
|
+
### State Management Patterns
|
|
1521
|
+
|
|
1522
|
+
All state management libraries benefit from fp-pack's immutable update patterns:
|
|
1523
|
+
|
|
1524
|
+
```typescript
|
|
1525
|
+
// GOOD: Generic state update pattern (works with any library)
|
|
1526
|
+
const updateState = <T>(
|
|
1527
|
+
state: T,
|
|
1528
|
+
path: string[],
|
|
1529
|
+
updater: (value: any) => any
|
|
1530
|
+
) => pipe(
|
|
1531
|
+
pathOr(null, path),
|
|
1532
|
+
updater,
|
|
1533
|
+
(newValue) => assocPath(path, newValue, state)
|
|
1534
|
+
)(state);
|
|
1535
|
+
|
|
1536
|
+
// Usage in any framework:
|
|
1537
|
+
// Redux: return updateState(state, ['users', 0, 'name'], toUpper);
|
|
1538
|
+
// Zustand: set(updateState(get(), ['users', 0, 'name'], toUpper));
|
|
1539
|
+
// Pinia: this.$state = updateState(this.$state, ['users', 0, 'name'], toUpper);
|
|
1540
|
+
```
|
|
1541
|
+
|
|
1542
|
+
## Framework-Specific Notes
|
|
1543
|
+
|
|
1544
|
+
While the patterns above are framework-agnostic, here's where to apply them:
|
|
1545
|
+
|
|
1546
|
+
### Reactive/Computed Values
|
|
1547
|
+
- **React**: `useMemo(() => pipe(...)(data), [data])`
|
|
1548
|
+
- **Vue**: `computed(() => pipe(...)(data.value))`
|
|
1549
|
+
- **Svelte**: `$: result = pipe(...)(data)`
|
|
1550
|
+
- **Solid**: `createMemo(() => pipe(...)(data()))`
|
|
1551
|
+
|
|
1552
|
+
### Event Handlers
|
|
1553
|
+
- **React**: `<button onClick={pipe(...)}>` or `const handler = pipe(...)`
|
|
1554
|
+
- **Vue**: `<button @click="pipe(...)">` or `const handler = pipe(...)`
|
|
1555
|
+
- **Svelte**: `<button on:click={pipe(...)}>` or `const handler = pipe(...)`
|
|
1556
|
+
|
|
1557
|
+
### Side Effects (API calls, subscriptions)
|
|
1558
|
+
- **React**: `useEffect(() => { pipeAsync(...)() }, [deps])`
|
|
1559
|
+
- **Vue**: `watchEffect(() => pipeAsync(...)())`
|
|
1560
|
+
- **Svelte**: `onMount(() => pipeAsync(...)())`
|
|
1561
|
+
|
|
1562
|
+
### State Updates
|
|
1563
|
+
- **React**: `setState(pipe(...)(currentState))`
|
|
1564
|
+
- **Vue**: `state.value = pipe(...)(state.value)`
|
|
1565
|
+
- **Svelte**: `$state = pipe(...)($state)`
|
|
1566
|
+
|
|
1567
|
+
All patterns use the same fp-pack functions - only the framework's state/reactive wrapper changes.
|
|
1568
|
+
|
|
1569
|
+
## Summary
|
|
1570
|
+
|
|
1571
|
+
As an AI coding assistant working with fp-pack:
|
|
1572
|
+
|
|
1573
|
+
1. **Default to `pipe`** for all data transformations
|
|
1574
|
+
2. **Switch to `pipeAsync`** when async operations are involved (use `pipeAsyncSideEffect` if SideEffect is in the flow)
|
|
1575
|
+
3. **Use `stream/*`** for lazy, memory-efficient processing
|
|
1576
|
+
4. **Handle errors with `SideEffect`** in `pipeSideEffect`/`pipeAsyncSideEffect`, not try-catch
|
|
1577
|
+
5. **Avoid imperative loops** - use fp-pack's declarative functions
|
|
1578
|
+
6. **Never suggest monads** - use SideEffect pattern instead
|
|
1579
|
+
7. **Keep code declarative** - describe what, not how
|
|
1580
|
+
8. **All logic inside pipe** - never break out of pipe chains for conditionals or loops
|
|
1581
|
+
9. **Use control flow functions** - `when`, `unless`, `ifElse`, `cond` instead of if/else/switch
|
|
1582
|
+
10. **Call `runPipeResult` OUTSIDE pipelines** - `runPipeResult` / `matchSideEffect` must be called outside `pipeSideEffect`/`pipeAsyncSideEffect` for proper type safety
|
|
1583
|
+
11. **Use `isSideEffect` for type narrowing** - get precise types in both success and error branches
|
|
1584
|
+
12. **Apply use-case patterns** - recognize scenarios (form handling, list processing, etc.) and apply appropriate fp-pack patterns
|
|
1585
|
+
13. **Framework-agnostic core** - write fp-pack logic independent of UI framework, only wrap at the boundaries
|
|
1586
|
+
14. **Library integration** - use pipe in select/resolver/action functions of popular libraries (React Query, Zustand, Pinia, etc.)
|
|
1587
|
+
|
|
1588
|
+
### Key Principles
|
|
1589
|
+
|
|
1590
|
+
**✅ DO: Keep everything in pipe**
|
|
1591
|
+
```typescript
|
|
1592
|
+
// GOOD: All logic inside pipe
|
|
1593
|
+
const handleSubmitPipeline = pipeAsyncSideEffect(
|
|
1594
|
+
getFormData,
|
|
1595
|
+
validateFields,
|
|
1596
|
+
when(isValid, submitToAPI),
|
|
1597
|
+
unless(isValid, showErrors)
|
|
1598
|
+
);
|
|
1599
|
+
|
|
1600
|
+
const handleSubmit = (form: HTMLFormElement) => runPipeResult(handleSubmitPipeline(form));
|
|
1601
|
+
```
|
|
1602
|
+
|
|
1603
|
+
**❌ DON'T: Break out of pipe for conditionals**
|
|
1604
|
+
```typescript
|
|
1605
|
+
// BAD: Breaking pipe for if/else
|
|
1606
|
+
const handleSubmit = pipe(
|
|
1607
|
+
getFormData,
|
|
1608
|
+
validateFields
|
|
1609
|
+
);
|
|
1610
|
+
const result = handleSubmit(form);
|
|
1611
|
+
if (result.isValid) { // ❌ Outside pipe
|
|
1612
|
+
submitToAPI(result);
|
|
1613
|
+
} else {
|
|
1614
|
+
showErrors(result.errors);
|
|
1615
|
+
}
|
|
1616
|
+
```
|
|
1617
|
+
|
|
1618
|
+
**✅ DO: Use when/cond/ifElse for branching**
|
|
1619
|
+
```typescript
|
|
1620
|
+
// GOOD: Branching inside pipe
|
|
1621
|
+
const processAction = pipe(
|
|
1622
|
+
cond([
|
|
1623
|
+
[(action) => action.type === 'CREATE', handleCreate],
|
|
1624
|
+
[(action) => action.type === 'UPDATE', handleUpdate],
|
|
1625
|
+
[(action) => action.type === 'DELETE', handleDelete],
|
|
1626
|
+
[() => true, handleDefault]
|
|
1627
|
+
])
|
|
1628
|
+
);
|
|
1629
|
+
```
|
|
1630
|
+
|
|
1631
|
+
**❌ DON'T: Use switch/if-else chains**
|
|
1632
|
+
```typescript
|
|
1633
|
+
// BAD: Imperative branching
|
|
1634
|
+
const processAction = (action) => {
|
|
1635
|
+
switch (action.type) { // ❌ Imperative
|
|
1636
|
+
case 'CREATE': return handleCreate(action);
|
|
1637
|
+
case 'UPDATE': return handleUpdate(action);
|
|
1638
|
+
case 'DELETE': return handleDelete(action);
|
|
1639
|
+
default: return handleDefault(action);
|
|
1640
|
+
}
|
|
1641
|
+
};
|
|
1642
|
+
```
|
|
1643
|
+
|
|
1644
|
+
Your goal is to write clean, readable, functional code that leverages fp-pack's full potential in real-world UI applications.
|