@btsheehy/tikka
v2.1.0
Published
Minimal dependency-free functional programming utilities for TypeScript and JavaScript
Maintainers
Readme
tikka
Tikka is a minimal, dependency-free functional utility library for TypeScript and JavaScript. It works out of the box, is fully typed, tested, and bundled for modern ESM tree-shaking so consumers only pay for what they import.
Why use tikka
- Minimal API surface focused on practical helpers
- Zero runtime dependencies
- TypeScript type declarations published in package output
- Curried utilities for composable functional style
- Tested with Vitest
- Bundled with Rollup and validated for tree-shaking
Why teams switch from larger FP utility libraries
A common pain point with utility libraries is bundle bloat when a project imports a broad FP API surface.
Measured with the same browser bundling setup (esbuild, minified, loading each library's full FP API object):
tikka/all: 5.25 KiB minified / 2.29 KiB gziplodash/fp: 82.27 KiB minified / 29.15 KiB gzipramda: 68.04 KiB minified / 20.30 KiB gzip
That means tikka/all is roughly:
- ~15.7x smaller than
lodash/fp(minified) - ~13.0x smaller than
ramda(minified)
If you only import specific named utilities from tikka, tree-shaking can reduce this even further.
Installation
npm install tikka
Usage
Named imports (recommended for tree-shaking):
import { filter, isEven, map, pipe, plus } from 'tikka'
const result = pipe(filter(isEven), map(plus(1)))([1, 2, 3, 4])
// [3, 5]Default aggregate import (all utilities):
import tikka from 'tikka/all'
const out = tikka.map(tikka.multiply(2), [1, 2, 3]) // [2, 4, 6]Data-first wrappers (underscore-prefixed):
import { _get, _map, _sortBy } from 'tikka/data-first'
const user = { name: 'Brandon', score: 7 }
const name = _get(user, 'name')
const doubled = _map([1, 2, 3], (n) => n * 2)
const sorted = _sortBy([{ score: 2 }, { score: 1 }], 'score', 'asc')Tree-shaking
Use named ESM imports from tikka:
import { plus } from 'tikka'This package is published with sideEffects: false and ESM module output designed for dead-code elimination. Verified with a Rollup consumer bundle: importing only plus emits only the plus implementation.
API
Notes:
- Many helpers are curried (especially those implemented with
curryRight). - Argument order for most collection helpers is callback/key first, then data.
- Aliases are documented explicitly below.
and(a, b)
Boolean AND over two values. Example:
and(true, false) // falseany(test, arr)
Returns true if at least one array element passes test.
Example:
any((x) => x > 2, [1, 2, 3]) // true
any(gt(2), [1, 2, 3]) // truecompact(arr)
Removes null and undefined values from an array.
Example:
compact([1, null, 2, undefined]) // [1,2]concat(left, right)
Concatenates arrays or strings. Examples:
concat([1, 2], [3, 4]) // [1,2,3,4]
concat('ab', 'cd') // 'abcd'contains(value, searchTarget)
Checks whether searchTarget.includes(value) is true.
Example:
contains(2, [1, 2, 3]) // truecountBy(iteratee, arr)
Builds a frequency object keyed by iteratee(value).
Example:
countBy((x) => (isOdd(x) ? 'odd' : 'even'), [1, 2, 3, 4]) // { odd: 2, even: 2 }countWhere(test, arr)
Counts array elements matching test.
Example:
countWhere((x) => x % 2 === 0, [1, 2, 3, 4]) // 2
countWhere(isEven, [1, 2, 3, 4]) // 2curry(fn, arity?)
Left-to-right curry helper. Example:
curry((a, b, c) => a + b + c)(1)(2)(3) // 6curryRight(fn, arity?)
Right-to-left curry helper. Example:
curryRight((a, b) => a / b)(2, 8) // 4debug(msg, value)
Logs msg and value, then returns value.
Example:
debug('current', 42) // 42This is most useful for debugging a particularly long pipe command.
deepClone(data)
Deep-clones arrays/objects recursively (supports Date and RegExp cloning). Example:
deepClone({ a: { b: [1] } })deepForEach(func, data)
Recursively visits nested arrays/objects and runs func on leaf values.
Example:
deepForEach(console.log, { a: [1, { b: 2 }] })
// 1
// 2deepMap(func, data)
Recursively maps leaf values in nested arrays/objects. Example:
deepMap((x) => (typeof x === 'number' ? x * 2 : x), { a: [1, 2, 'c'] })
// { a: [2, 4, 'c'] }every(test, arr)
Returns true if all elements pass test.
Example:
every((x) => x > 0, [1, 2, 3]) // true
every(lt(0))([-4, -5, 2]) // falsefilter(test, arr)
Filters an array by predicate. Example:
filter((x) => x > 1, [1, 2, 3]) // [ 2,3 ]
filter(gt(1), [1, 2, 3]) // [2, 3]find(test, arr)
Returns first matching element or undefined.
Example:
find((x) => x > 1, [1, 2, 3]) // 2
find(gt(1), [1, 2, 3]) // 2findIndex(test, arr)
Returns index of first matching element or -1.
Example:
findIndex(gt(1), [1, 2, 3]) // 1first(arr)
Alias of head.
Example:
first([1, 2, 3]) // 1flatten(arr)
Deep-flattens nested arrays. Example:
flatten([1, [2, [3]], 4]) // [1,2,3,4]forEach(func, arr)
Runs func for each element and returns original array.
Example:
forEach(console.log, [1, 2])
// 1
// 2forEachValues(func, obj)
Runs func for each object value and returns original object.
Example:
forEachValues(console.log, { a: 1, b: 2 })
// 1
// 2get(prop, obj)
Gets property by key. Example:
get('a', { a: 1 }) // 1getOr(defaultValue, prop, obj)
Gets property if present, otherwise returns defaultValue.
Example:
getOr(0, 'a', {}) // 0grab(props, data)
Picks listed keys from an object, or from each object in an array. Example:
grab(['a'], { a: 1, b: 2 }) // { a: 1 }
grab(['a'], [{ a: 1, b: 2 }, { a: 5, c: 9 }]) // [{ a: 1 }, { a: 5 }]groupBy(groupingFunction, arr)
Groups array values by iteratee or string key. Example:
groupBy((x) => isOdd(x) ? 'odd' : 'even', [1, 2, 3]) // { odd: [ 1, 3 ], even: [ 2 ] }
groupBy('foo', [{ foo: 1, bar: 300, baz: 'abc'}, { foo: 2, bar: 9, baz: 'def'}, { foo: 2, bar: 1000 }])
// {
// '1': [ { foo: 1, bar: 300, baz: 'abc' } ],
// '2': [ { foo: 2, bar: 9, baz: 'def' }, { foo: 2, bar: 1000 } ]
// }gt(a, b)
Greater-than comparison. Example:
gt(3, 2) // truegte(a, b)
Greater-than-or-equal comparison. Example:
gte(3, 3) // truehas(prop, obj)
Own-property existence check. Example:
has('a', { a: 1 }) // truehead(arr)
Returns first element. Example:
head([1, 2, 3]) // 1highest(nums)
Returns the largest number in an array. Example:
highest([4, 1, 9, 3]) // 9highestBy(fn, arr)
Returns the element with the highest score from fn.
Example:
highestBy((user) => user.score, [{ name: 'Ari', score: 12 }, { name: 'Bea', score: 21 }])
// { name: 'Bea', score: 21 }identity(value)
Alias of self; returns input unchanged.
Example:
identity('x') // 'x'ifElse(onFalse, onTrue, test)
Runs test(), then executes onTrue() or onFalse().
Example:
ifElse(
() => 0,
() => 1,
() => true
) // 1includes(value, searchTarget)
Alias of contains.
Example:
includes('a', 'cat') // trueisEven(num)
Returns true for even numbers. Example:
isEven(4) // trueisOdd(num)
Returns true for odd numbers. Example:
isOdd(3) // truelast(arrOrString)
Returns final element/character. Example:
last([1, 2, 3]) // 3lowest(nums)
Returns the smallest number in an array. Example:
lowest([4, 1, 9, 3]) // 1lowestBy(fn, arr)
Returns the element with the lowest score from fn.
Example:
lowestBy((user) => user.score, [{ name: 'Ari', score: 12 }, { name: 'Bea', score: 21 }])
// { name: 'Ari', score: 12 }lt(a, b)
Less-than comparison. Example:
lt(2, 3) // truelte(a, b)
Less-than-or-equal comparison. Example:
lte(3, 3) // truemap(fn, arr)
Maps array values. Example:
map((x) => x * 2, [1, 2, 3]) // [2,4,6]mapKeys(fn, obj)
Transforms object keys, preserving values. Example:
mapKeys((k) => k.toUpperCase(), { a: 1 }) // { A: 1 }mapValues(fn, obj)
Transforms object values, preserving keys. Example:
mapValues((v) => v * 2, { a: 1 }) // { a: 2 }max(a, b)
Returns the larger of two numbers. Example:
max(4, 9) // 9min(a, b)
Returns the smaller of two numbers. Example:
min(4, 9) // 4minus(b, a)
Subtracts second arg from first in curried-right style. Example:
minus(3, 10) // 7noop()
No-op function. Example:
noop() // undefinedor(a, b)
Boolean OR over two values. Example:
or(false, true) // truepipe(...fns)
Creates a left-to-right function pipeline. This is the core composition primitive in tikka.
Example:
import { pipe } from 'tikka'
const transform = pipe(
(value: number) => value + 1,
(value) => value * 2
)
transform(2)
// 6Common tikka composition pattern:
import { filter, isEven, map, pipe, plus, take } from 'tikka'
const firstThreeIncrementedEvens = pipe(filter(isEven), map(plus(1)), take(3))
firstThreeIncrementedEvens([1, 2, 3, 4, 5, 6, 7, 8])
// [3, 5, 7]Pipeline with object helpers:
import { grab, map, pipe, sortBy } from '@btsheehy/tikka'
const selectLeaderboardFields = pipe(sortBy('score', 'desc'), map(grab(['name', 'score'])))
selectLeaderboardFields([
{ name: 'Ari', score: 12, email: '[email protected]' },
{ name: 'Bea', score: 19, email: '[email protected]' },
])
// [{ name: 'Bea', score: 19 }, { name: 'Ari', score: 12 }]pluck(prop, collection)
Extracts property values from array of objects. Example:
pluck('name', [{ name: 'a' }, { name: 'b' }]) // ['a','b']plus(a, b)
Adds two numbers. Example:
plus(2, 3) // 5remove(pred, arr)
Returns a new array with items removed when pred(item) is true.
Example:
remove((n) => n % 2 === 0, [1, 2, 3, 4]) // [1, 3]select(props, data)
Alias of grab.
Example:
select(['id'], { id: 1, name: 'x' }) // { id:1 }self(value)
Returns input unchanged. Example:
self(42) // 42sort(iteratee, arr)
Sorts an array by a computed key (non-mutating). Example:
sort((x) => x.age, [{ age: 3 }, { age: 1 }]) // [{age:1},{age:3}]sortBy(fieldOrIteratee, direction, arr)
Sorts an array of objects by either a field name or a value-selector function, using 'asc' or 'desc' direction.
Examples:
sortBy('age', 'desc', [{ age: 1 }, { age: 3 }, { age: 2 }]) // [{age:3},{age:2},{age:1}]
sortBy((x) => x.age, 'asc', [{ age: 3 }, { age: 1 }]) // [{age:1},{age:3}]tail(arrOrString)
Returns everything except the first element/character. Examples:
tail([1, 2, 3]) // [2,3]
tail('hello') // 'ello'take(num, arr)
Returns first num elements.
Example:
take(2, [1, 2, 3]) // [1,2]test(regex, str)
Runs regex.test(str).
Example:
test(/ab/, 'zabx') // truetoLower(str)
Lowercases string with locale support. Example:
toLower('AbC') // 'abc'toUpper(str)
Uppercases string with locale support. Example:
toUpper('AbC') // 'ABC'trim(str)
Trims string whitespace. Example:
trim(' x ') // 'x'type(val)
Returns internal type label (for example: Array, Object, Null, Undefined).
Example:
type([]) // 'Array'uniq(arr)
Returns array with duplicate values removed (first occurrence kept). Example:
uniq([1, 2, 1, 3]) // [1,2,3]uniqBy(uniqCond, arr)
Returns unique items by computed key. Example:
uniqBy(get('id'), [{ id: 1 }, { id: 1 }, { id: 2 }])Performance benchmark suites (large datasets)
This repo includes dedicated performance benchmark suites to compare identical function workloads across tikka, lodash/fp, and ramda on very large deterministic datasets.
Benchmarked functions:
mapfilterfindgroupByuniquniqByflatten/flattenDeepcontains/includes- Composed
pipecollection pipeline (filter -> map -> uniq -> find)
Dataset sizes (generated in test/perf/fixtures.ts):
numbers: 60,000 itemsusers: 80,000 objectsnestedNumbers: 5,000 nested groups
Commands:
- Parity validation (same output across all three libs):
vitest run test/perf/parity.test.ts - Performance suite:
npm run perf - Watch performance suite:
npm run perf:watch - Run a specific benchmark group by test name pattern:
npm run perf:test -- map - Run the composed pipeline benchmark group:
npm run perf:test -- "pipe collection pipeline"
Quality gates in this repo
- Lint:
npm run lint - Type-check:
npm run typecheck - Tests:
npm test - Build:
npm run build - Full check:
npm run check
Biome rule policy
This codebase intentionally allows dynamic typing patterns used by curried functional helpers:
suspicious.noExplicitAny: offcomplexity.noBannedTypes: off
Those two rules were disabled to avoid unsafe blanket suppressions in many files while preserving the rest of Biome recommended rules.
