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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@statedelta-libs/operators

v0.0.1

Published

Functional operators library optimized for JSON DSL - Ramda-inspired, TypeScript-first

Downloads

96

Readme

@statedelta-libs/operators

Functional operators library optimized for JSON DSL. Ramda-inspired, TypeScript-first.

Bundle Size TypeScript

Features

  • Complete: 100+ operators (Core, List, Object, Logic, Math, String, Type, Advanced)
  • Lightweight: ~7kb bundle (vs ~50kb Ramda)
  • TypeScript-first: Strong type inference
  • Async native: pipeAsync, composeAsync, mapAsync
  • JSON DSL ready: Designed for { $fn: "map", args: [...] }
  • Placeholder support: Skip arguments with __
  • Lens support: Immutable nested updates
  • Transducers: Efficient transformations

Installation

pnpm add @statedelta-libs/operators

Quick Start

import { pipe, map, filter, sum, prop } from '@statedelta-libs/operators';

// Pipe - left-to-right composition
const totalActiveItems = pipe(
  items,
  filter(prop('active')),
  map(prop('price')),
  sum
);

// With operators
import { curry, __, propEq, pick, merge } from '@statedelta-libs/operators';

const getVipDiscount = pipe(
  filter(propEq('isVip', true)),
  map(pick(['id', 'discount'])),
);

API Reference

Core

| Function | Description | |----------|-------------| | identity(x) | Returns argument unchanged | | always(x) | Returns function that always returns x | | T() / F() | Always true / always false | | tap(fn, x) | Execute side-effect, return original | | curry(fn) | Curry with placeholder support | | curryN(n, fn) | Curry with explicit arity | | partial(fn, args) | Partial application from left | | partialRight(fn, args) | Partial application from right | | pipe(val, ...fns) | Left-to-right composition | | pipeBuilder(...fns) | Create reusable pipe | | compose(...fns) | Right-to-left composition | | pipeWith(transformer, fns) | Pipe with custom transformer | | pipeAsync(...fns) | Async pipe | | composeAsync(...fns) | Async compose | | pipeK(...fns) | Reader/context-aware pipe | | composeK(...fns) | Reader/context-aware compose | | letIn(bindings, fn) | Let bindings | | __ | Placeholder for skipping args |

// Pipe
pipe(5, x => x + 1, x => x * 2); // 12

// Curry with placeholder
const subtract = curry((a, b) => a - b);
const subtractFrom10 = subtract(__, 10);
subtractFrom10(15); // 5

// Let bindings
letIn(
  { doubled: x => x * 2, incremented: x => x + 1 },
  ({ doubled, incremented }) => doubled + incremented
)(5); // 10 + 6 = 16

List

| Function | Description | |----------|-------------| | map(fn, list) | Transform each element | | filter(pred, list) | Keep elements matching predicate | | reject(pred, list) | Remove elements matching predicate | | reduce(fn, init, list) | Reduce to single value | | reduceRight(fn, init, list) | Reduce from right | | head(list) | First element | | tail(list) | All except first | | last(list) | Last element | | init(list) | All except last | | nth(n, list) | Element at index | | take(n, list) | First n elements | | takeLast(n, list) | Last n elements | | takeWhile(pred, list) | Take while predicate true | | drop(n, list) | Remove first n | | dropLast(n, list) | Remove last n | | dropWhile(pred, list) | Drop while predicate true | | slice(start, end, list) | Slice of list | | find(pred, list) | First match | | findIndex(pred, list) | Index of first match | | findLast(pred, list) | Last match | | indexOf(val, list) | Index of value | | includes(val, list) | Contains value? | | flatten(list) | Flatten one level | | unnest(list) | Alias for flatten | | chain(fn, list) | Map then flatten | | uniq(list) | Remove duplicates | | uniqBy(fn, list) | Remove duplicates by key | | sort(comparator, list) | Sort with comparator | | sortBy(fn, list) | Sort by derived value | | reverse(list) | Reverse list | | groupBy(fn, list) | Group by key | | concat(a, b) | Concatenate lists | | append(val, list) | Add to end | | prepend(val, list) | Add to start | | zip(a, b) | Pair elements | | zipWith(fn, a, b) | Pair with function | | zipObj(keys, values) | Create object from pairs | | some(pred, list) | Any match? | | every(pred, list) | All match? | | none(pred, list) | None match? | | length(list) | Count elements | | sum(list) | Sum numbers | | product(list) | Product of numbers | | mean(list) | Average | | median(list) | Median | | min(list) | Minimum | | max(list) | Maximum | | countBy(fn, list) | Count by key | | range(start, end) | Generate range | | partition(pred, list) | Split by predicate | | splitEvery(n, list) | Split into chunks | | indexBy(fn, list) | Index by key |

// Transform
pipe(
  [1, 2, 3, 4, 5],
  filter(x => x > 2),
  map(x => x * 2),
  sum
); // 24

// Group and count
groupBy(prop('category'), products);
countBy(prop('status'), orders);

// Search
find(propEq('id', 123), users);

Object

| Function | Description | |----------|-------------| | prop(key, obj) | Get property | | path(path, obj) | Get nested property | | propOr(def, key, obj) | Get property with default | | pathOr(def, path, obj) | Get nested with default | | props(keys, obj) | Get multiple properties | | pluck(key, list) | Extract property from each | | assoc(key, val, obj) | Set property (immutable) | | assocPath(path, val, obj) | Set nested (immutable) | | dissoc(key, obj) | Remove property | | dissocPath(path, obj) | Remove nested property | | pick(keys, obj) | Select properties | | omit(keys, obj) | Exclude properties | | merge(a, b) | Shallow merge | | mergeDeep(a, b) | Deep merge | | evolve(transforms, obj) | Transform properties | | applySpec(spec, obj) | Build object from spec | | extend(a, b) | Extend object | | keys(obj) | Get keys | | values(obj) | Get values | | entries(obj) | Get entries | | fromEntries(entries) | Create from entries |

// Access
prop('name', user); // 'John'
path(['address', 'city'], user); // 'NYC'
pathOr('N/A', ['address', 'zip'], user);

// Transform (immutable)
assocPath(['settings', 'theme'], 'dark', user);
evolve({ age: inc, name: toUpper }, user);

// Select
pick(['id', 'name'], user);
omit(['password'], user);

// Merge
merge(defaults, userSettings);
mergeDeep(baseConfig, overrides);

Logic

| Function | Description | |----------|-------------| | equals(a, b) | Deep equality | | identical(a, b) | Reference equality | | gt(a, b) | Greater than | | gte(a, b) | Greater than or equal | | lt(a, b) | Less than | | lte(a, b) | Less than or equal | | not(x) | Boolean not | | and(a, b) | Boolean and | | or(a, b) | Boolean or | | both(f, g) | Both predicates true | | either(f, g) | Either predicate true | | complement(pred) | Negate predicate | | allPass(preds) | All predicates pass | | anyPass(preds) | Any predicate passes | | ifElse(pred, onTrue, onFalse) | Conditional | | when(pred, fn) | Apply if true | | unless(pred, fn) | Apply if false | | cond(pairs) | Switch-like conditional | | tryCatch(fn, handler) | Try/catch wrapper | | propEq(key, val, obj) | Property equals? | | propSatisfies(pred, key, obj) | Property satisfies? | | pathEq(path, val, obj) | Path equals? | | pathSatisfies(pred, path, obj) | Path satisfies? | | where(spec, obj) | Match spec | | whereEq(spec, obj) | Match values | | is(Ctor, val) | Instance check | | isNil(x) | Is null/undefined? | | isEmpty(x) | Is empty? | | isNotNil(x) | Is not null/undefined? | | isNotEmpty(x) | Is not empty? | | defaultTo(def, val) | Default for nil | | coalesce(...vals) | First non-nil |

// Predicates
filter(propEq('status', 'active'), items);
filter(where({ age: gte(__, 18), verified: equals(true) }), users);

// Conditionals
ifElse(
  propEq('role', 'admin'),
  always(fullAccess),
  always(limitedAccess)
)(user);

cond([
  [propEq('type', 'A'), handleA],
  [propEq('type', 'B'), handleB],
  [T, handleDefault]
])(item);

Math

| Function | Description | |----------|-------------| | add(a, b) | Addition | | subtract(a, b) | Subtraction | | multiply(a, b) | Multiplication | | divide(a, b) | Division | | modulo(a, b) | Modulo | | negate(x) | Negate number | | inc(x) | Increment | | dec(x) | Decrement | | clamp(min, max, val) | Clamp to range | | abs(x) | Absolute value | | round(x) | Round | | floor(x) | Floor | | ceil(x) | Ceiling | | pow(base, exp) | Power |

// Math operations
map(multiply(2), [1, 2, 3]); // [2, 4, 6]
clamp(0, 100, value);
pipe(prices, map(multiply(1.1)), map(round));

String

| Function | Description | |----------|-------------| | toUpper(str) | Uppercase | | toLower(str) | Lowercase | | trim(str) | Trim whitespace | | split(sep, str) | Split string | | join(sep, list) | Join list | | replace(pattern, replacement, str) | Replace | | startsWith(prefix, str) | Starts with? | | endsWith(suffix, str) | Ends with? | | test(regex, str) | Test regex | | match(regex, str) | Match regex | | substring(start, end, str) | Substring | | padStart(len, char, str) | Pad start | | padEnd(len, char, str) | Pad end | | concatStr(a, b) | Concatenate strings |

pipe(
  '  Hello World  ',
  trim,
  toLower,
  split(' '),
  map(s => s[0].toUpperCase() + s.slice(1)),
  join(' ')
); // 'Hello World'

Type

| Function | Description | |----------|-------------| | toString(x) | Convert to string | | toNumber(x) | Convert to number | | toBoolean(x) | Convert to boolean | | toArray(x) | Convert to array | | toObject(x) | Convert to object | | type(x) | Get type name |


Advanced

Lens

Immutable nested updates.

| Function | Description | |----------|-------------| | lens(getter, setter) | Create lens | | lensProp(key) | Lens for property | | lensPath(path) | Lens for nested path | | lensIndex(i) | Lens for array index | | view(lens, obj) | Get via lens | | set(lens, val, obj) | Set via lens | | over(lens, fn, obj) | Transform via lens |

const nameLens = lensProp('name');
const addressCityLens = lensPath(['address', 'city']);

view(nameLens, user); // 'John'
set(nameLens, 'Jane', user); // { name: 'Jane', ... }
over(nameLens, toUpper, user); // { name: 'JOHN', ... }

Async

| Function | Description | |----------|-------------| | mapAsync(fn, list) | Parallel async map | | mapSerial(fn, list) | Sequential async map | | filterAsync(pred, list) | Async filter | | reduceAsync(fn, init, list) | Async reduce |

await mapAsync(fetchUser, userIds);
await mapSerial(processInOrder, items);

Transducers

Efficient transformations without intermediate arrays.

| Function | Description | |----------|-------------| | mapT(fn) | Map transducer | | filterT(pred) | Filter transducer | | takeT(n) | Take transducer | | dropT(n) | Drop transducer | | composeT(...xforms) | Compose transducers | | transduce(xform, reducer, init, list) | Apply transducer | | into(to, xform, from) | Transduce into collection |

const xform = composeT(
  filterT(x => x > 2),
  mapT(x => x * 2),
  takeT(3)
);

transduce(xform, (acc, x) => acc + x, 0, [1, 2, 3, 4, 5, 6, 7]);

TypeScript

Full type inference throughout:

import { pipe, map, filter, prop, sum } from '@statedelta-libs/operators';

// Types flow through pipe
const result = pipe(
  [{ price: 10, active: true }, { price: 20, active: false }],
  filter(prop('active')),  // { price: number, active: boolean }[]
  map(prop('price')),      // number[]
  sum                      // number
);

// Curried functions preserve types
const getPrice = prop('price'); // <T>(obj: T) => T['price']

Bundle Size

| Build | Size | |-------|------| | ESM | ~7kb | | CJS | ~7kb | | Types | ~22kb |

Comparison with Ramda

| Aspect | Ramda | @statedelta-libs/operators | |--------|-------|------------------------| | Functions | 280+ | 100+ | | Bundle | ~50kb | ~7kb | | TypeScript | Weak types | Strong inference | | Async | Not native | Native support | | Lens | Yes | Yes | | Transducers | Yes | Yes |


License

MIT