npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@roxdavirox/fp-core

v0.1.0

Published

Functional programming primitives for TypeScript — every type inferred, zero dependencies.

Downloads

145

Readme

fp-core

Functional programming primitives for TypeScript — every type inferred, zero dependencies.

CI npm Bundle Size License: MIT


fp-core is a TypeScript-native library for writing predictable, composable code. It gives you a small set of well-defined primitives — pipe, Result, Option, and a full suite of curried array, object, string, and async utilities — all with precise types at every step and zero runtime dependencies.


Why fp-core?

  • vs fp-ts — same Result/Option/pipe primitives without HKT encoding or category-theory prerequisites. No Functor, Monad, or Applicative in your mental model — just named functions that compose.
  • vs Ramda — value-first pipe(value, f, g) instead of pipe(f, g)(value). TypeScript infers every intermediate type without losing precision across steps.
  • vs neverthrowResult + Option + pipe + async utilities + array/object/string helpers in one tree-shakeable package with no dependencies.
  • vs lodash/fp — TypeScript-native from the ground up (no any leakage from overloads), with Result and Option built in to replace try/catch and null-check patterns structurally.

Install

npm install @roxdavirox/fp-core
# or
pnpm add @roxdavirox/fp-core

Quick Start

import { pipe, Ok, Err, Some, None, mapResult, flatMap, match } from '@roxdavirox/fp-core';

// pipe — fully typed at every step
const result = pipe(
  '  Hello World  ',
  s => s.trim(),           // string → string
  s => s.toLowerCase(),    // string → string
  s => s.split(' '),       // string → string[]
  arr => arr.length,       // string[] → number
);
// result: 2  (TypeScript knows it's a number)

// Result — errors as values, no try/catch
const divide = (a: number, b: number) =>
  b === 0 ? Err('division by zero' as const) : Ok(a / b);

pipe(
  divide(10, 2),
  mapResult(n => n * 100),
  match(
    value => console.log('Result:', value), // 500
    error => console.error('Error:', error),
  ),
);

// Option — nullable without null checks
import { fromNullable, mapOption, unwrapOptionOr } from '@roxdavirox/fp-core';

const getUser = (id: number) => fromNullable(users.find(u => u.id === id));

pipe(
  getUser(42),
  mapOption(u => u.name),
  unwrapOptionOr('Anonymous'),
); // 'Alice' or 'Anonymous'

What's included

  • compositionpipe, compose, flow, curry, memoize, tap, identity, constant
  • resultOk, Err, mapResult, flatMap, match, tryCatch, fromPromise, collectErrors
  • optionSome, None, fromNullable, mapOption, flatMapOption, matchOption, unwrapOptionOr
  • asyncpipeAsync, retry, timeout, debounce, mapConcurrent, mapConcurrentResult, tryCatchAsync
  • arraymap, filter, reduce, groupBy, partition, chunk, sortBy, flatten, unique
  • objectpick, omit, merge, deepMerge, mapValues, setPath, getPath, defaults
  • stringcamelCase, kebabCase, snakeCase, truncate, template, capitalize
  • predicatesand, or, not, isString, isNumber, isNil, between

Quick Patterns

Safe API call — errors as values, no try/catch

import { fromPromise, flatMapAsync, match } from '@roxdavirox/fp-core';

const getUser = async (id: string) => {
  const result = await fromPromise(fetch(`/api/users/${id}`).then(r => r.json()))
    .then(flatMapAsync(user => fromPromise(enrichUser(user))));

  return match(
    user  => ({ status: 'ok'    as const, user }),
    error => ({ status: 'error' as const, message: error.message }),
  )(result);
};

Option null-safe navigation — no optional chaining noise

import { pipe, fromNullable, flatMapOption, mapOption, unwrapOptionOr } from '@roxdavirox/fp-core';

const getEventCoords = (user: User): string =>
  pipe(
    fromNullable(user.upcomingEvent),
    flatMapOption(e => fromNullable(e.location)),
    flatMapOption(l => fromNullable(l.coordinates)),
    mapOption(c => `${c.lat}, ${c.lng}`),
    unwrapOptionOr('Location unavailable'),
  );

Form validation — accumulate all errors

import { Ok, Err, collectErrors, match } from '@roxdavirox/fp-core';

const validate = (form: SignupForm) =>
  match(
    ()       => ({ ok: true  as const, form }),
    (errors) => ({ ok: false as const, errors }),
  )(collectErrors([
    form.email.includes('@') ? Ok(form) : Err('Email is invalid'),
    form.password.length >= 8 ? Ok(form) : Err('Password too short'),
    form.age >= 18 ? Ok(form) : Err('Must be 18 or older'),
  ]));

See all 10 recipes in docs/RECIPES.md


Design Principles

  1. Value-first, not point-free. pipe(value, fn1, fn2) instead of pipe(fn1, fn2)(value). TypeScript loves this.
  2. Data-last currying. All array/object utilities take data as the last argument so they compose naturally.
  3. Result over exceptions. Functions that can fail return Result<T, E>. No hidden control flow.
  4. Option over null. Functions that can return nothing return Option<T>. No accidental undefined.
  5. Zero magic. No runtime reflection, no proxy traps, no hidden global state.
  6. Tree-shakeable. Import only what you use — bundlers will shake the rest.

Subpath Imports

fp-core is fully tree-shakeable. Import the root '@roxdavirox/fp-core' for most use cases, or use subpath imports for explicit dependency tracking:

import { Ok, Err, flatMap, mapResult } from '@roxdavirox/fp-core/result';
import { Some, None, fromNullable } from '@roxdavirox/fp-core/option';
import { pipe, compose, curry } from '@roxdavirox/fp-core/composition';
import { pipeAsync, retry, timeout } from '@roxdavirox/fp-core/async';
import { map, filter, groupBy } from '@roxdavirox/fp-core/array';
import { pick, omit, setPath } from '@roxdavirox/fp-core/object';
import { camelCase, truncate, template } from '@roxdavirox/fp-core/string';
import { and, or, not, isString } from '@roxdavirox/fp-core/predicates';

Docs


Versioning

This project follows Semantic Versioning:

| Change type | Version bump | |-------------|-------------| | Bugfix, doc update, internal refactor | Patch (0.x.Y) | | New exported function, new subpath, new option | Minor (0.X.0) | | Renamed/removed export, changed function signature, changed behavior | Major (X.0.0) |

Until 1.0.0, minor releases may include breaking changes if they are clearly documented in the CHANGELOG.


TypeScript Requirements

  • TypeScript ≥ 5.0
  • "moduleResolution": "bundler" or "node16" / "nodenext"
  • "strict": true recommended

License

MIT © roxdavirox