@jpleyjon/all-in-one
v1.1.0
Published
A comprehensive TypeScript utility library with data structures, function, string, array, object, datetime, currency, JSON, number, and validation helpers.
Downloads
479
Maintainers
Keywords
Readme
all-in-one
A comprehensive TypeScript utility library providing data structures, function, string, array, object, date/time, currency, JSON, number, and validation helpers built from scratch with zero runtime dependencies.
🎯 Philosophy
Similar to lodash, but with a twist: all-in-one is built entirely without external libraries, relying solely on Node.js features. This ensures:
- Zero dependencies
- Full control over implementation
- Educational value for understanding data structures and algorithms
- Lightweight and efficient
📦 Installation
npm install @jpleyjon/all-in-one⚡ Quick Start
import { toCamelCase, unique } from '@jpleyjon/all-in-one';
import { formatDate } from '@jpleyjon/all-in-one/datetime';
console.log(toCamelCase('hello world')); // helloWorld
console.log(unique([1, 2, 1, 3])); // [1, 2, 3]
console.log(formatDate(new Date(), 'YYYY-MM-DD'));Use top-level imports for convenience and subpath imports for domain-specific usage.
🚀 Features
Data Structures
Robust, type-safe implementations of fundamental computer science data structures:
- Node Structures - SingleNode and DoubleNode for building linked structures
- Stack - LIFO (Last In, First Out) data structure
- Queue - FIFO (First In, First Out) data structure
- List - Singly linked list with find and remove operations
- Tree - Generic tree structure with multiple children
- Binary Search Tree - Binary tree with ordered insertion and traversal methods
- Graph - Adjacency-list graph with directed/undirected edges and BFS/DFS
- Hash Table - Key/value storage with separate chaining collision handling
Function Utilities
Composable helpers for cleaner control-flow and execution behavior:
- Core helpers -
identity,noop,tap - Composition -
compose,pipe - Execution control -
once,memoize,debounce,throttle
String Utilities
Production-ready functional string helpers:
- Case conversion -
toCamelCase,toPascalCase,toSnakeCase,toKebabCase,toTitleCase - Formatting -
capitalize,capitalizeWords,normalizeWhitespace - Matching -
equalsIgnoreCase,includesIgnoreCase - Transformations -
slugify,stripAccents - Text shaping -
truncate,truncateWords,initials,mask
Array Utilities
Functional, immutable array helpers:
- Selection -
isEmpty,first,last,sample - Structure -
chunk,flatten,flattenDepth,insertAt,removeAt,move,swap - Set-like operations -
unique,uniqueBy,difference,intersection,union - Collection shaping -
groupBy,keyBy,partition,compact - Sorting and analytics -
sortBy,sum,average,minBy,maxBy - Randomization -
shuffle
Object Utilities
Functional, immutable object transformation helpers:
- Selection and projection -
pick,omit,pickBy,omitBy - Entry transformation -
mapValues,mapKeys,mapEntries,entries,fromEntries,toPairs,fromPairs,filterObject,reduceObject,renameKeys,invert,transformKeysDeep,transformValuesDeep - Nested path operations -
get,set,hasPath,unset,update,deepPick,deepOmit - Composition and cloning -
merge,mergeWith,deepMerge,defaults,cloneDeep,safeJsonClone,deepFreeze - Diagnostics and cleanup -
isEmptyObject,diffObjects,cleanObject - Shape conversion -
flattenObject,unflattenObject
Date/Time Utilities
Date parsing, formatting, comparison, and calendar boundary helpers:
- Validation and parsing -
isValidDate,parseDate - Formatting and ISO conversion -
formatDate,toISODate,toISODateTime - Date arithmetic -
addDays,addMonths,addYears,subtractDays,subtractMonths,subtractYears - Calendar boundaries -
startOfDay,endOfDay,startOfWeek,endOfWeek,startOfMonth,endOfMonth,startOfYear,endOfYear - Differences and comparison -
differenceInDays,differenceInHours,differenceInMinutes,differenceInSeconds,isBefore,isAfter,isSameDay,isSameMonth - Relative-day checks -
isToday,isYesterday,isTomorrow - Range and selection -
minDate,maxDate,clampDate - Calendar and timestamp helpers -
getDaysInMonth,getWeekday,fromUnixTimestamp,toUnixTimestamp - Timezone conversion and duration display -
toUTC,toLocalTime,humanizeDuration
Currency Utilities
Exact money conversion and allocation helpers (no exchange-rate features):
- Unit conversion -
dollarsToCents,centsToDollars,centsToDollarsString - Formatting -
formatCents,toAccountingCurrencyString - Allocation -
allocateCents,splitEvenCents,weightedAllocateCents - Math and comparison -
sumCents,subtractCents,multiplyCents,averageCents,applyRateToCents,applyBpsToCents,taxAmountCents,discountAmountCents,totalWithTaxCents,percentageOfTotal,compareCents,absCents,negateCents,clampCents,minCents,maxCents - Flags -
isZeroCents,isPositiveCents,isNegativeCents - Codes and parsing -
isValidCurrencyCode,normalizeCurrencyCode,parseCurrencyStringToCents
Number Utilities
Common number validation, rounding, percentages, ranges, parsing, statistics, and randomization helpers:
- Validation and conversion -
isFiniteNumber,isInteger,isSafeInteger,toFiniteNumber,coerceNumber - Bounds and ranges -
clamp,inRange,between,mapRange,normalize,snap - Rounding and precision -
roundTo,floorTo,ceilTo,truncateTo,roundToStep,almostEqual - Arithmetic and ratios -
safeAdd,safeSubtract,safeMultiply,safeDivide,mod,sign,zScore,percent,percentChange - Collection stats -
sumNumbers,min,max,mean,median,mode,variance,standardDeviation,quantile,lerp - Formatting and parsing -
formatNumber,toThousands,parseNumber - Randomization -
randomInt,randomFloat
JSON Utilities
Safe parsing, formatting, serialization, and redaction helpers:
- Validation and parsing -
isValidJson,parseJson,parseJsonWithReviver,safeParseJson,parseJsonOrDefault - Formatting -
prettifyJson,minifyJson - Serialization -
stringifyJson,stringifyJsonWithReplacer,stableStringifyJson - Diagnostics and safety -
jsonByteSize,redactJson
Validation Utilities
Composed, schema-oriented, and format validation helpers:
- Composed predicates -
isNonEmptyTrimmedString,isPositiveSafeInteger,isValidDateRange - Common formats -
isEmail,isUrl,isUUID,isIPv4,isHexColor,isLuhnNumber - Object and schema checks -
hasRequiredKeys,validateShape - Validator combinators -
optional,nullable,arrayOf,allOf,oneOf
📖 Usage
Stack
import { Stack } from '@jpleyjon/all-in-one';
const stack = new Stack<number>();
stack.push(1);
stack.push(2);
stack.push(3);
console.log(stack.pop()); // 3
console.log(stack.peek()); // 2
console.log(stack.isEmpty()); // falseQueue
import { Queue } from '@jpleyjon/all-in-one';
const queue = new Queue<string>();
queue.push('first');
queue.push('second');
queue.push('third');
console.log(queue.pop()); // 'first'
console.log(queue.isEmpty()); // falseBinary Search Tree
import { BinarySearchTree } from '@jpleyjon/all-in-one';
const tree = new BinarySearchTree<number>(10);
tree.insert(5);
tree.insert(15);
tree.insert(3);
tree.insert(7);
console.log(tree.inOrder()); // [3, 5, 7, 10, 15]
console.log(tree.preOrder()); // [10, 5, 3, 7, 15]
console.log(tree.levelOrder()); // [10, 5, 15, 3, 7]Graph
import { Graph } from '@jpleyjon/all-in-one';
const graph = new Graph<number>();
graph.addEdge(1, 2);
graph.addEdge(1, 3);
graph.addEdge(2, 4);
console.log(graph.breadthFirstSearch(1)); // [1, 2, 3, 4]
console.log(graph.depthFirstSearch(1)); // [1, 2, 4, 3]Hash Table
import { HashTable } from '@jpleyjon/all-in-one';
const table = new HashTable<number>();
table.set('apples', 3);
table.set('oranges', 5);
console.log(table.get('apples')); // 3
console.log(table.has('oranges')); // true
table.remove('apples');
console.log(table.size); // 1List
import { List } from '@jpleyjon/all-in-one';
const list = new List<number>();
list.push(10);
list.push(20);
list.push(30);
console.log(list.find(1)); // 20
list.remove(1);
console.log(list.size); // 2Function Helpers
import { compose, debounce, memoize, once } from '@jpleyjon/all-in-one';
const addTax = once((value: number) => value * 1.08);
console.log(addTax(100)); // 108
console.log(addTax(200)); // 108
const memoized = memoize((value: number) => value * value);
console.log(memoized(6)); // 36
console.log(memoized(6)); // 36 (cached)
const logValue = debounce((value: string) => {
console.log(value);
}, 250);
logValue('first call');
logValue('latest call');String Helpers
import {
toCamelCase,
slugify,
normalizeWhitespace,
truncateWords,
mask,
} from '@jpleyjon/all-in-one';
console.log(toCamelCase('hello-world test')); // helloWorldTest
console.log(slugify('Crème Brûlée Recipe')); // creme-brulee-recipe
console.log(normalizeWhitespace(' too many\nspaces\t')); // too many spaces
console.log(truncateWords('one two three four', 2)); // one two...
console.log(mask('4111111111111111', 4, 4)); // 4111********1111Array Helpers
import { chunk, unique, sortBy, groupBy, move } from '@jpleyjon/all-in-one';
console.log(chunk([1, 2, 3, 4, 5], 2)); // [[1, 2], [3, 4], [5]]
console.log(unique([1, 2, 1, 3])); // [1, 2, 3]
console.log(sortBy([{ n: 2 }, { n: 1 }], (item) => item.n)); // [{ n: 1 }, { n: 2 }]
console.log(groupBy(['one', 'two', 'three'], (word) => word.length)); // { 3: ['one', 'two'], 5: ['three'] }
console.log(move(['a', 'b', 'c'], 0, 2)); // ['b', 'c', 'a']Object Helpers
import {
pick,
omit,
mapValues,
entries,
fromEntries,
get,
set,
deepPick,
deepMerge,
mergeWith,
cleanObject,
diffObjects,
safeJsonClone,
transformKeysDeep,
flattenObject,
unflattenObject,
} from '@jpleyjon/all-in-one';
const user = { id: 1, profile: { name: 'Ada', role: 'admin' } };
console.log(pick(user, ['id'])); // { id: 1 }
console.log(omit(user, ['id'])); // { profile: { name: 'Ada', role: 'admin' } }
console.log(mapValues({ a: 1, b: 2 }, (value) => value * 10)); // { a: 10, b: 20 }
console.log(entries({ a: 1, b: 2 })); // [['a', 1], ['b', 2]]
console.log(
fromEntries([
['a', 1],
['b', 2],
]),
); // { a: 1, b: 2 }
console.log(get(user, 'profile.name')); // Ada
console.log(set(user, 'profile.role', 'editor')); // { id: 1, profile: { name: 'Ada', role: 'editor' } }
console.log(deepPick(user, ['profile.name'])); // { profile: { name: 'Ada' } }
console.log(deepMerge({ a: { b: 1 } }, { a: { c: 2 } })); // { a: { b: 1, c: 2 } }
console.log(mergeWith((current, incoming) => current ?? incoming, { a: 1 }, { a: 2, b: 3 })); // { a: 1, b: 3 }
console.log(cleanObject({ a: 1, b: null, c: '' })); // { a: 1 }
console.log(diffObjects({ a: 1 }, { a: 2, b: 3 })); // { added: { b: 3 }, removed: {}, changed: { a: { before: 1, after: 2 } } }
console.log(safeJsonClone({ a: 1, list: [1, 2] })); // { a: 1, list: [1, 2] }
console.log(transformKeysDeep({ userProfile: { firstName: 'Ada' } }, (key) => key.toUpperCase())); // { USERPROFILE: { FIRSTNAME: 'Ada' } }
console.log(flattenObject({ user: { profile: { name: 'Ada' } } })); // { 'user.profile.name': 'Ada' }
console.log(unflattenObject({ 'user.profile.name': 'Ada' })); // { user: { profile: { name: 'Ada' } } }Currency Helpers
import {
dollarsToCents,
centsToDollars,
centsToDollarsString,
formatCents,
toAccountingCurrencyString,
allocateCents,
splitEvenCents,
weightedAllocateCents,
sumCents,
subtractCents,
multiplyCents,
averageCents,
applyRateToCents,
applyBpsToCents,
totalWithTaxCents,
percentageOfTotal,
parseCurrencyStringToCents,
} from '@jpleyjon/all-in-one';
console.log(dollarsToCents('12.34')); // 1234
console.log(centsToDollars(1234)); // 12.34
console.log(centsToDollarsString(1234)); // 12.34
console.log(formatCents(1234)); // $12.34 (en-US default)
console.log(toAccountingCurrencyString(-1234)); // ($12.34)
console.log(allocateCents(10, 3)); // [4, 3, 3]
console.log(splitEvenCents(10, 3)); // [4, 3, 3]
console.log(weightedAllocateCents(100, [1, 2, 3])); // [17, 33, 50]
console.log(sumCents([100, 250, -50])); // 300
console.log(subtractCents(500, 125)); // 375
console.log(multiplyCents(105, 1.1)); // 116
console.log(averageCents([1, 2])); // 2
console.log(applyRateToCents(1000, 0.0825)); // 1083
console.log(applyBpsToCents(1000, 250)); // 1025
console.log(totalWithTaxCents(1000, 0.0825)); // 1083
console.log(percentageOfTotal(250, 1000)); // 25
console.log(parseCurrencyStringToCents('1,234.56')); // 123456Number Helpers
import {
almostEqual,
between,
ceilTo,
clamp,
coerceNumber,
floorTo,
formatNumber,
inRange,
isEven,
isFiniteNumber,
isInteger,
isOdd,
isSafeInteger,
lerp,
max,
mapRange,
mean,
median,
min,
mod,
mode,
normalize,
parseNumber,
percent,
percentChange,
quantile,
randomFloat,
randomInt,
roundTo,
roundToStep,
safeAdd,
safeDivide,
safeMultiply,
safeSubtract,
sign,
snap,
standardDeviation,
sumNumbers,
toFiniteNumber,
toThousands,
truncateTo,
variance,
zScore,
} from '@jpleyjon/all-in-one';
console.log(isFiniteNumber(10)); // true
console.log(isInteger(10.1)); // false
console.log(isSafeInteger(Number.MAX_SAFE_INTEGER)); // true
console.log(toFiniteNumber('12.34')); // 12.34
console.log(coerceNumber('12.34')); // 12.34
console.log(clamp(120, 0, 100)); // 100
console.log(inRange(5, 1, 10)); // true
console.log(between(5, 10, 1)); // true
console.log(roundTo(1.235, 2)); // 1.24
console.log(floorTo(1.239, 2)); // 1.23
console.log(ceilTo(1.231, 2)); // 1.24
console.log(truncateTo(-1.239, 2)); // -1.23
console.log(roundToStep(5.26, 0.1)); // 5.300000000000001
console.log(almostEqual(1, 1 + Number.EPSILON)); // true
console.log(safeAdd(2, 3)); // 5
console.log(safeSubtract(5, 3)); // 2
console.log(safeMultiply(2, 3)); // 6
console.log(safeDivide(10, 0, 0)); // 0
console.log(mod(-7, 5)); // 3
console.log(sign(-10)); // -1
console.log(percent(25, 100)); // 25
console.log(percentChange(100, 120)); // 20
console.log(mapRange(5, 0, 10, 0, 100)); // 50
console.log(normalize(5, 0, 10)); // 0.5
console.log(lerp(0, 10, 0.5)); // 5
console.log(sumNumbers([1, 2, 3])); // 6
console.log(min([3, 1, 2])); // 1
console.log(max([3, 1, 2])); // 3
console.log(mean([1, 2, 3])); // 2
console.log(median([1, 2, 3, 4])); // 2.5
console.log(mode([1, 2, 2, 3])); // [2]
console.log(variance([1, 2, 3])); // 0.6666666666666666
console.log(standardDeviation([1, 2, 3])); // 0.816496580927726
console.log(quantile([1, 2, 3, 4], 0.25)); // 1.75
console.log(zScore(12, 10, 2)); // 1
console.log(formatNumber(1234.5, { maximumFractionDigits: 1 }, 'en-US')); // 1,234.5
console.log(toThousands(1234567)); // 1,234,567
console.log(parseNumber('1,234.56', 'en-US')); // 1234.56
console.log(isEven(4)); // true
console.log(isOdd(5)); // true
console.log(snap(5.2, [1, 5, 10])); // 5
console.log(randomInt(1, 6)); // integer from 1 to 6
console.log(randomFloat(0, 1)); // float from 0 (inclusive) to 1 (exclusive)JSON Helpers
import {
isValidJson,
jsonByteSize,
minifyJson,
parseJson,
parseJsonWithReviver,
safeParseJson,
parseJsonOrDefault,
prettifyJson,
redactJson,
stringifyJson,
stringifyJsonWithReplacer,
stableStringifyJson,
} from '@jpleyjon/all-in-one';
console.log(isValidJson('{ "a": 1 }')); // true
console.log(minifyJson('{ "a": 1, "b": [1, 2] }')); // {"a":1,"b":[1,2]}
console.log(parseJson<{ a: number }>('{ "a": 1 }')); // { a: 1 }
console.log(
parseJsonWithReviver<{ createdAt: Date }>(
'{ "createdAt": "2026-01-01T00:00:00.000Z" }',
(key, value) => (key === 'createdAt' ? new Date(String(value)) : value),
),
); // { createdAt: 2026-01-01T00:00:00.000Z }
console.log(safeParseJson('{ invalid }')); // { ok: false, error: SyntaxError(...) }
console.log(parseJsonOrDefault('{ invalid }', { a: 0 })); // { a: 0 }
console.log(prettifyJson('{"a":1}')); // {
// "a": 1
// }
console.log(stringifyJson({ a: 1 }, 2)); // {
// "a": 1
// }
console.log(
stringifyJsonWithReplacer({ keep: 1, secret: 'x' }, (key, value) =>
key === 'secret' ? undefined : value,
),
); // {"keep":1}
console.log(stableStringifyJson({ b: 1, a: 2 })); // {"a":2,"b":1}
console.log(jsonByteSize({ a: 'á' })); // 10
console.log(redactJson({ user: { token: 'abc' } }, ['user.token'])); // { user: { token: '[REDACTED]' } }Validation Helpers
import {
allOf,
arrayOf,
hasRequiredKeys,
isEmail,
isHexColor,
isIPv4,
isLuhnNumber,
isNonEmptyTrimmedString,
isPositiveSafeInteger,
isUrl,
isUUID,
isValidDateRange,
nullable,
oneOf,
optional,
validateShape,
} from '@jpleyjon/all-in-one';
console.log(isNonEmptyTrimmedString(' hello ')); // true
console.log(isPositiveSafeInteger(42)); // true
console.log(isValidDateRange('2024-01-01', '2024-12-31')); // true
console.log(isEmail('[email protected]')); // true
console.log(isUrl('https://example.com')); // true
console.log(isUUID('550e8400-e29b-41d4-a716-446655440000', 4)); // true
console.log(isIPv4('192.168.1.1')); // true
console.log(isHexColor('#ff00aa')); // true
console.log(isLuhnNumber('4111111111111111')); // true
console.log(hasRequiredKeys({ id: 1, name: 'A' }, ['id', 'name'])); // true
console.log(
validateShape<{ id: number; name: string }>(
{ id: 1, name: 'A' },
{
id: (value) => typeof value === 'number',
name: (value) => typeof value === 'string',
},
),
); // true
const optionalNumber = optional(
(value: unknown): value is number => typeof value === 'number' && Number.isFinite(value),
);
console.log(optionalNumber(undefined)); // true
const nullableString = nullable((value: unknown): value is string => typeof value === 'string');
console.log(nullableString(null)); // true
const numberArray = arrayOf((value: unknown): value is number => typeof value === 'number');
console.log(numberArray([1, 2, 3])); // true
const strictPositiveInteger = allOf(
(value: unknown): value is number => typeof value === 'number',
(value: unknown): value is number => Number.isInteger(value),
(value: unknown): value is number => value > 0,
);
console.log(strictPositiveInteger(5)); // true
const stringOrNumber = oneOf(
(value: unknown): value is string => typeof value === 'string',
(value: unknown): value is number => typeof value === 'number',
);
console.log(stringOrNumber('abc')); // true🧪 Testing
The library uses the Node.js test runner with TypeScript compilation and c8 coverage, and runs inside Docker:
./bin/test📊 Coverage
Coverage reports are generated as part of ./bin/test (or npm test) via c8.
Coverage reports are available in the coverage/ directory.
🛠️ Development
For contributors, Docker is the only required local dependency.
# Run linter
./bin/lint
# Auto-fix lint issues
./bin/lint-fix
# Check formatting
./bin/format-check
# Run tests
./bin/test
# Build
./bin/buildThe first run will build Dockerfile.dev and install dependencies in Docker volumes.
If needed, you can still run local Node-based commands via npm run <task>:local.
📋 API Documentation
Data Structures
Stack<T>
push(data: T): void- Add element to the toppop(): T- Remove and return the top elementpeek(): T- View the top element without removing itisEmpty(): boolean- Check if stack is emptysize: number- Get the number of elements
Queue<T>
push(data: T): void- Add element to the endpop(): T- Remove and return the first elementisEmpty(): boolean- Check if queue is emptysize: number- Get the number of elements
List<T>
push(data: T): void- Add element to the endfind(index: number): T- Get element at indexremove(index: number): void- Remove element at indexisEmpty(): boolean- Check if list is emptysize: number- Get the number of elements
BinarySearchTree<T>
insert(data: T): void- Insert a new nodeinOrder(): T[]- In-order traversalpreOrder(): T[]- Pre-order traversalpostOrder(): T[]- Post-order traversallevelOrder(): T[]- Level-order traversal
Tree<T>
insert(parent: T, data: T): void- Insert a child into the first matching parentpreOrder(): T[]- Pre-order traversalinOrder(): T[]- In-order traversal (n-ary variant)postOrder(): T[]- Post-order traversallevelOrder(): T[]- Level-order traversal
Graph<T>
addVertex(vertex: T): void- Add a vertexremoveVertex(vertex: T): void- Remove a vertex and connected edgesaddEdge(source: T, destination: T): void- Add an edgeremoveEdge(source: T, destination: T): void- Remove an edgehasVertex(vertex: T): boolean- Check whether a vertex existshasEdge(source: T, destination: T): boolean- Check whether an edge existsgetNeighbors(vertex: T): T[]- Get vertex neighborsbreadthFirstSearch(start: T): T[]- Breadth-first traversaldepthFirstSearch(start: T): T[]- Depth-first traversal
HashTable<T>
set(key: string, value: T): void- Insert or update a valueget(key: string): T | undefined- Get a value by keyhas(key: string): boolean- Check whether a key existsremove(key: string): boolean- Remove a key/value pairclear(): void- Remove all entrieskeys(): string[]- Get all keysvalues(): T[]- Get all valuesentries(): [string, T][]- Get all entriessize: number- Get current entry count
Array Utilities
isEmpty<T>(input: T[]): boolean- Check if an array has no itemsfirst<T>(input: T[]): T | undefined- Get first itemlast<T>(input: T[]): T | undefined- Get last itemcompact<T>(input: T[]): T[]- Remove falsy valueschunk<T>(input: T[], size: number): T[][]- Split into fixed-size chunksflatten<T>(input: (T | T[])[]): T[]- Flatten one levelflattenDepth<T>(input: unknown[], depth = 1): T[]- Flatten up to depthunique<T>(input: T[]): T[]- Keep unique valuesuniqueBy<T, K>(input: T[], selector: (item: T) => K): T[]- Unique by keygroupBy<T, K>(input: T[], selector: (item: T) => K): Record<K, T[]>- Group items by keykeyBy<T, K>(input: T[], selector: (item: T) => K): Record<K, T>- Index items by keypartition<T>(input: T[], predicate: (item: T) => boolean): [T[], T[]]- Split by predicatedifference<T>(left: T[], right: T[]): T[]- Values in left not in rightintersection<T>(left: T[], right: T[]): T[]- Shared unique valuesunion<T>(...inputs: T[][]): T[]- Combined unique valuessortBy<T>(input: T[], selector: (item: T) => string | number | bigint | Date, direction?: 'asc' | 'desc'): T[]- Sort by keysum(input: number[]): number- Sum all valuesaverage(input: number[]): number- Compute arithmetic meanminBy<T>(input: T[], selector: (item: T) => string | number | bigint | Date): T | undefined- Item with lowest keymaxBy<T>(input: T[], selector: (item: T) => string | number | bigint | Date): T | undefined- Item with highest keyshuffle<T>(input: T[]): T[]- Return shuffled copysample<T>(input: T[]): T | undefined- Pick random iteminsertAt<T>(input: T[], index: number, item: T): T[]- Insert item at indexremoveAt<T>(input: T[], index: number): T[]- Remove item at indexmove<T>(input: T[], fromIndex: number, toIndex: number): T[]- Move item between indexesswap<T>(input: T[], leftIndex: number, rightIndex: number): T[]- Swap two indexes
Object Utilities
ObjectRecord = Record<string, unknown>- Generic plain object typeObjectPath = string | (string | number)[]- Nested path inputPathSegment = string | number- Single path segment typeCleanObjectOptions- Options forcleanObjectDiffValueChange = { before: unknown; after: unknown }- Change record type fordiffObjectsDiffObjectsResult = { added: ObjectRecord; removed: ObjectRecord; changed: Record<string, DiffValueChange> }- Diff result typeMergeWithResolver = (currentValue, incomingValue, key) => unknown- Merge conflict resolver signaturepick<T, K>(input: T, keys: K[]): Pick<T, K>- Return only selected keysomit<T, K>(input: T, keys: K[]): Omit<T, K>- Exclude selected keyspickBy<T>(input: T, predicate: (value, key, input) => boolean): Partial<T>- Keep entries matching predicateomitBy<T>(input: T, predicate: (value, key, input) => boolean): Partial<T>- Remove entries matching predicatemapValues<T, R>(input: T, mapper: (value, key, input) => R): Record<string, R>- Map values, preserve keysmapKeys<T>(input: T, mapper: (key, value, input) => string): ObjectRecord- Map keys, preserve valuesmapEntries<T>(input: T, mapper: (value, key, input) => [string, unknown]): ObjectRecord- Map entries into new key/value pairsentries<T>(input: T): [key, value][]- Return object entriesfromEntries(input: [string, unknown][]): ObjectRecord- Build object from entriestoPairs<T>(input: T): [key, value][]- Convert object to key/value pairsfromPairs(input: [string, unknown][]): ObjectRecord- Build object from key/value pairsfilterObject<T>(input: T, predicate: (value, key, input) => boolean): Partial<T>- Filter entries by predicatereduceObject<T, R>(input: T, reducer: (acc, value, key, input) => R, initialValue: R): R- Reduce entries into a single valuerenameKeys<T>(input: T, mapping: Record<string, string>): ObjectRecord- Rename keys using mappinginvert(input: ObjectRecord): ObjectRecord- Swap keys and valuesisEmptyObject(input: ObjectRecord): boolean- Check whether object has no own keysget(input: object, path: ObjectPath, defaultValue?): unknown- Safely read nested valueset<T>(input: T, path: ObjectPath, value: unknown): T- Immutable nested writehasPath(input: object, path: ObjectPath): boolean- Check whether path existsunset<T>(input: T, path: ObjectPath): T- Immutable nested deleteupdate<T>(input: T, path: ObjectPath, updater: (currentValue: unknown) => unknown): T- Update nested value by callbackdeepPick<T>(input: T, paths: ObjectPath[]): Partial<T>- Keep only selected nested pathsdeepOmit<T>(input: T, paths: ObjectPath[]): T- Remove selected nested pathsmerge(...inputs: ObjectRecord[]): ObjectRecord- Shallow merge objectsmergeWith(resolver: MergeWithResolver, ...inputs: ObjectRecord[]): ObjectRecord- Shallow merge with custom conflict resolverdeepMerge(...inputs: ObjectRecord[]): ObjectRecord- Deep merge objects recursivelydefaults(input: ObjectRecord, ...sources: ObjectRecord[]): ObjectRecord- Fill undefined keys from defaultscloneDeep<T>(input: T): T- Deep clone object/array/date structuressafeJsonClone<T>(input: T): T- Clone with JSON serialization semanticsdeepFreeze<T>(input: T): T- Deep freeze object/array graphcleanObject(input: ObjectRecord, options?: CleanObjectOptions): ObjectRecord- Remove configurable empty valuesdiffObjects(left: ObjectRecord, right: ObjectRecord): DiffObjectsResult- Diff added, removed, and changed pathstransformKeysDeep(input: ObjectRecord, mapper: (key, value, path) => string): ObjectRecord- Recursively transform object keystransformValuesDeep(input: ObjectRecord, mapper: (value, path) => unknown): ObjectRecord- Recursively transform leaf valuesflattenObject(input: ObjectRecord, options?: FlattenObjectOptions): ObjectRecord- Flatten nested structure into path keysunflattenObject(input: ObjectRecord, options?: UnflattenObjectOptions): ObjectRecord- Expand path keys into nested structure
Date/Time Utilities
DateInput = Date | string | number- Accepted date input typeisValidDate(value: DateInput): boolean- Check whether input is a valid dateparseDate(value: DateInput): Date | null- Parse date input safelyformatDate(date: DateInput, pattern: string): string- Format usingYYYY,MM,DD,HH,mm,ss,SSStoISODate(date: DateInput): string- ISO date (YYYY-MM-DD)toISODateTime(date: DateInput): string- ISO date-time stringaddDays(date: DateInput, amount: number): Date- Add daysaddMonths(date: DateInput, amount: number): Date- Add monthsaddYears(date: DateInput, amount: number): Date- Add yearssubtractDays(date: DateInput, amount: number): Date- Subtract dayssubtractMonths(date: DateInput, amount: number): Date- Subtract monthssubtractYears(date: DateInput, amount: number): Date- Subtract yearsstartOfDay(date: DateInput): Date- Start of local dayendOfDay(date: DateInput): Date- End of local daystartOfWeek(date: DateInput, weekStartsOn?: number): Date- Start of local weekendOfWeek(date: DateInput, weekStartsOn?: number): Date- End of local weekstartOfMonth(date: DateInput): Date- Start of local monthendOfMonth(date: DateInput): Date- End of local monthstartOfYear(date: DateInput): Date- Start of local yearendOfYear(date: DateInput): Date- End of local yeardifferenceInDays(a: DateInput, b: DateInput): number- Signed whole-day differencedifferenceInHours(a: DateInput, b: DateInput): number- Signed whole-hour differencedifferenceInMinutes(a: DateInput, b: DateInput): number- Signed whole-minute differencedifferenceInSeconds(a: DateInput, b: DateInput): number- Signed whole-second differenceisBefore(a: DateInput, b: DateInput): boolean- Check ifais beforebisAfter(a: DateInput, b: DateInput): boolean- Check ifais afterbisSameDay(a: DateInput, b: DateInput): boolean- Check if both dates are on the same dayisSameMonth(a: DateInput, b: DateInput): boolean- Check if both dates are in the same monthisToday(date: DateInput): boolean- Check if date is todayisYesterday(date: DateInput): boolean- Check if date is yesterdayisTomorrow(date: DateInput): boolean- Check if date is tomorrowminDate(dates: DateInput[]): Date | undefined- Earliest date in a listmaxDate(dates: DateInput[]): Date | undefined- Latest date in a listclampDate(date: DateInput, min: DateInput, max: DateInput): Date- Clamp date between boundsgetDaysInMonth(date: DateInput): number- Number of days in monthgetWeekday(date: DateInput): number- Weekday index (0-6)fromUnixTimestamp(seconds: number): Date- Date from UNIX secondstoUnixTimestamp(date: DateInput): number- UNIX seconds from datetoUTC(date: DateInput): Date- Convert local components to UTC-based datetoLocalTime(date: DateInput): Date- Convert UTC components to local-time datehumanizeDuration(milliseconds: number): string- Human-readable duration
Currency Utilities
MoneyInput = number | string- Accepted money input for exact dollar-to-cent conversiondollarsToCents(amount: MoneyInput): number- Convert decimal dollars to integer centscentsToDollars(cents: number): number- Convert integer cents to decimal dollarscentsToDollarsString(cents: number): string- Convert integer cents to exact fixed-point dollar stringformatCents(cents: number, options?: FormatCentsOptions): string- Localized currency formatting from centstoAccountingCurrencyString(cents: number, options?: AccountingFormatCentsOptions): string- Localized currency formatting with parentheses for negativesallocateCents(cents: number, parts: number): number[]- Split cents into near-equal integer partssplitEvenCents(totalCents: number, parts: number): number[]- Convenience even split wrapperweightedAllocateCents(cents: number, weights: number[]): number[]- Split cents using weighted allocationsumCents(values: number[]): number- Sum cent amountssubtractCents(a: number, b: number): number- Subtract one cent value from anothermultiplyCents(cents: number, factor: number, mode?: RoundingMode): number- Multiply and round centsaverageCents(values: number[], mode?: RoundingMode): number- Average cent values with roundingapplyRateToCents(cents: number, rate: number, mode?: RoundingMode): number- Apply a percentage-like rate to centsapplyBpsToCents(cents: number, bps: number, mode?: RoundingMode): number- Apply basis points to centstaxAmountCents(subtotalCents: number, taxRate: number, mode?: RoundingMode): number- Compute tax amount from subtotaldiscountAmountCents(subtotalCents: number, discountRate: number, mode?: RoundingMode): number- Compute discount amount from subtotaltotalWithTaxCents(subtotalCents: number, taxRate: number, mode?: RoundingMode): number- Compute subtotal plus taxpercentageOfTotal(partCents: number, totalCents: number, precision?: number): number- Compute percentage represented by a partisZeroCents(cents: number): boolean- Check if cents is exactly zeroisPositiveCents(cents: number): boolean- Check if cents is positiveisNegativeCents(cents: number): boolean- Check if cents is negativecompareCents(a: number, b: number): -1 | 0 | 1- Compare two cent valuesabsCents(cents: number): number- Absolute cents valuenegateCents(cents: number): number- Negate a cent valueclampCents(cents: number, min: number, max: number): number- Clamp cents between boundsminCents(values: number[]): number | undefined- Smallest cent value in a listmaxCents(values: number[]): number | undefined- Largest cent value in a listisValidCurrencyCode(code: string): boolean- Validate 3-letter ISO currency codenormalizeCurrencyCode(code: string): string- Normalize and validate currency codeparseCurrencyStringToCents(input: string): number- Parse currency-like strings into cents
Number Utilities
RoundToStepMode = 'half-up' | 'half-even' | 'up' | 'down' | 'toward-zero' | 'away-from-zero'- Step-rounding mode typeisFiniteNumber(value: unknown): value is number- Check whether value is a finite numberisInteger(value: unknown): value is number- Check whether value is an integerisSafeInteger(value: unknown): value is number- Check whether value is a safe integertoFiniteNumber(input: unknown, fallback?: number): number- Convert number-like input to finite numberclamp(value: number, min: number, max: number): number- Clamp value between boundsinRange(value: number, min: number, max: number, inclusive?: boolean): boolean- Check whether value is inside a rangeroundTo(value: number, decimals?: number): number- Round to decimal precisionfloorTo(value: number, decimals?: number): number- Floor to decimal precisionceilTo(value: number, decimals?: number): number- Ceil to decimal precisiontruncateTo(value: number, decimals?: number): number- Truncate to decimal precisionroundToStep(value: number, step: number, mode?: RoundToStepMode): number- Round to nearest increment stepalmostEqual(a: number, b: number, epsilon?: number): boolean- Compare numbers by epsilon tolerancesafeDivide(numerator: number, denominator: number, fallback?: number): number- Divide with fallback for zero denominatorpercent(part: number, total: number, precision?: number): number- Compute part percentage from totalpercentChange(previous: number, current: number, precision?: number): number- Compute percentage changemapRange(value: number, inMin: number, inMax: number, outMin: number, outMax: number, clampOutput?: boolean): number- Map value between rangesnormalize(value: number, min: number, max: number): number- Normalize to a 0..1 scalelerp(start: number, end: number, t: number): number- Linear interpolationrandomInt(min: number, max: number, random?: () => number): number- Random integer in inclusive rangerandomFloat(min: number, max: number, random?: () => number): number- Random float in half-open rangesumNumbers(values: number[]): number- Sum finite number arraysmin(values: number[]): number | undefined- Get smallest number from an arraymax(values: number[]): number | undefined- Get largest number from an arraymean(values: number[]): number- Compute arithmetic meanmedian(values: number[]): number | undefined- Compute median valuemode(values: number[]): number[]- Compute sorted mode valuesvariance(values: number[], sample?: boolean): number- Compute population or sample variancestandardDeviation(values: number[], sample?: boolean): number- Compute population or sample standard deviationquantile(values: number[], q: number): number | undefined- Compute quantile with linear interpolationzScore(value: number, meanValue: number, standardDeviationValue: number): number- Compute standardized z-scoreformatNumber(value: number, options?: Intl.NumberFormatOptions, locales?: string | string[]): string- Format numbers with IntltoThousands(value: number, separator?: string): string- Format number with grouped thousands separatorparseNumber(input: string, locales?: string | string[]): number- Parse localized numeric stringscoerceNumber(input: unknown): number | null- Best-effort finite number coercionsafeAdd(a: number, b: number): number- Add finite numbers and validate overflowsafeSubtract(a: number, b: number): number- Subtract finite numbers and validate overflowsafeMultiply(a: number, b: number): number- Multiply finite numbers and validate overflowmod(value: number, divisor: number): number- Mathematical modulo with non-negative resultsign(value: number): -1 | 0 | 1- Return normalized sign indicatorisEven(value: number): boolean- Check whether a finite integer is evenisOdd(value: number): boolean- Check whether a finite integer is oddbetween(value: number, a: number, b: number, inclusive?: boolean): boolean- Check whether value lies between boundssnap(value: number, anchors: number[]): number- Snap a value to nearest anchor
Validation Utilities
ValidationPredicate<T = unknown> = (value: unknown) => value is T- Base type for reusable validation predicatesShapeValidator = (value: unknown) => boolean- Shape-field validator signatureValidationShape = Record<string, ShapeValidator>- Object-shape validator mapUuidVersion = 1 | 3 | 4 | 5- Supported UUID versionsUrlValidationOptions- URL validation options (protocols,allowLocalhost)isNonEmptyTrimmedString(value: unknown): value is string- Check for non-empty strings after trimmingisPositiveSafeInteger(value: unknown): value is number- Check for positive safe integersisValidDateRange(start: DateInput, end: DateInput, inclusive?: boolean): boolean- Validate ordered date rangesisEmail(value: unknown): value is string- Check pragmatic email formatisUrl(value: unknown, options?: UrlValidationOptions): value is string- Check URL format with protocol optionsisUUID(value: unknown, version?: UuidVersion): value is string- Check UUID format with optional version constraintisIPv4(value: unknown): value is string- Check IPv4 formatisHexColor(value: unknown): value is string- Check hex color format (#RGB,#RRGGBB, with optional alpha)isLuhnNumber(value: unknown): value is string- Check Luhn checksum formathasRequiredKeys(value: unknown, keys: readonly string[]): boolean- Check required own keys on objectsvalidateShape<T>(value: unknown, shape: ValidationShape): value is T- Validate object fields with predicate mapoptional<T>(validator: ValidationPredicate<T>): ValidationPredicate<T | undefined>- Allowundefinedfor a validatornullable<T>(validator: ValidationPredicate<T>): ValidationPredicate<T | null>- Allownullfor a validatorarrayOf<T>(validator: ValidationPredicate<T>): ValidationPredicate<T[]>- Build array validators from item validatorsallOf(...validators: ValidationPredicate[]): ValidationPredicate- Compose validators with logical ANDoneOf(...validators: ValidationPredicate[]): ValidationPredicate- Compose validators with logical OR
JSON Utilities
JsonReviver = (key: string, value: unknown) => unknown- JSON parse reviver signatureJsonReplacer = (key: string, value: unknown) => unknown- JSON stringify replacer signatureSafeParseJsonSuccess<T> = { ok: true; value: T }- Successful safe parse result typeSafeParseJsonFailure = { ok: false; error: SyntaxError }- Failed safe parse result typeSafeParseJsonResult<T> = SafeParseJsonSuccess<T> | SafeParseJsonFailure- Safe parse result union typeisValidJson(input: string): boolean- Check whether input is valid JSON textminifyJson(input: string): string- Remove unnecessary whitespace from JSON textparseJson<T = unknown>(input: string): T- Parse JSON and throw on invalid inputparseJsonWithReviver<T = unknown>(input: string, reviver: JsonReviver): T- Parse JSON using a custom reviversafeParseJson<T = unknown>(input: string): SafeParseJsonResult<T>- Parse JSON without throwingparseJsonOrDefault<T>(input: string, fallback: T): T- Parse JSON or return fallbackprettifyJson(input: string, space?: number): string- Format JSON text with indentationredactJson(input: unknown, paths: string[], mask?: unknown): unknown- Redact selected dot-paths from a cloned valuejsonByteSize(input: unknown): number- Compute UTF-8 byte length of serialized JSONstringifyJson(input: unknown, space?: number): string- Serialize JSON-safe valuesstringifyJsonWithReplacer(input: unknown, replacer: JsonReplacer, space?: number): string- Serialize with a custom replacerstableStringifyJson(input: unknown, space?: number): string- Serialize with deterministic key ordering
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Publishing (Maintainers)
Use Docker-based publish flow:
npm run publish:packageUseful options:
npm run publish:package -- --dry-run
npm run publish:package -- --otp <code>Authentication is read from NPM_TOKEN or your ~/.npmrc.
When NPM_TOKEN is set, it is used as the publish credential source for that run.
If npm returns E403 with a 2FA message, use one of:
- A granular npm token with package publish permission and bypass 2FA enabled.
- Your user auth plus OTP:
npm run publish:package -- --otp <fresh-6-digit-code>📄 License
MIT License - see the LICENSE file for details.
Copyright (c) 2024 Joao Ley
🗺️ Roadmap
- [x] Core data structures (Stack, Queue, List, Tree, Binary Search Tree, Graph, Hash Table)
- [x] String manipulation utilities
- [x] Array utilities
- [x] Object transformation utilities
- [x] JSON helpers
- [x] Number utilities
- [x] Date/Time helpers
- [x] Validation utilities
- [x] Currency helpers
📣 30-Day Growth Plan
Week 1
- Publish stable versions with complete changelog notes.
- Pin top examples in README around search-heavy helpers (
debounce-style use-cases, date formatting, object path access, currency math). - Add npm badge links and verify package metadata.
Week 2
- Publish 3 short dev posts with practical snippets (object transforms, date helpers, JSON safety helpers).
- Share each post in TypeScript, Node.js, and frontend communities.
- Open a "good first issue" set to encourage outside contributions.
Week 3
- Add benchmark and bundle-size section in README for trust and clarity.
- Collect feedback from first users and prioritize the top 5 requested helpers.
- Ship at least one release based on real user requests.
Week 4
- Build a small example repo (API utilities or frontend data utils) that uses this package.
- Publish a migration guide from common lodash helper usage to this package.
- Review npm page analytics and refine README keywords/examples from real search terms.
💡 Why all-in-one?
This library was created to provide a comprehensive utility toolkit while maintaining the educational value of understanding how these structures and algorithms work under the hood. By avoiding external dependencies, it offers:
- Transparency - See exactly how everything works
- Reliability - No dependency security concerns
- Performance - Optimized implementations without bloat
- Learning - Great resource for understanding data structures
Built with ❤️ using TypeScript and Node.js
