@statedelta-libs/operators
v0.0.1
Published
Functional operators library optimized for JSON DSL - Ramda-inspired, TypeScript-first
Downloads
96
Maintainers
Readme
@statedelta-libs/operators
Functional operators library optimized for JSON DSL. Ramda-inspired, TypeScript-first.
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/operatorsQuick 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 = 16List
| 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
