@pablo-v/utils-js
v1.0.0
Published
A collection of TypeScript utility functions leveraging modern JavaScript features including the Temporal API.
Maintainers
Readme
utils-js
A lightweight TypeScript utility library that covers the problems standard JavaScript leaves unsolved. Built on modern platform features including the Temporal API, fully tree-shakeable, and targeting ESM-only environments.
16 KB bundled. Zero runtime dependencies beyond an optional Temporal polyfill.
Requirements
- Node.js 22+ (or any modern bundler targeting ES2023)
- TypeScript 5.x (strict mode recommended)
- For Temporal utilities: native
Temporal(Node 22+ with--experimental-temporal) or@js-temporal/polyfill
Installation
npm install utils-jsIf you use the temporal module and your environment does not have native Temporal support:
npm install @js-temporal/polyfillModules
pipe
Left-to-right function composition, overloaded for up to 10 steps with full type inference.
import { pipe } from 'utils-js'
const result = pipe(
' hello world ',
(s) => s.trim(),
(s) => s.toUpperCase(),
(s) => s.split(' '),
)
// ['HELLO', 'WORLD']option
A minimal Option<T> monad — plain objects, no classes, fully tree-shakeable. All transformation functions are curried data-last for use with pipe.
import { pipe, fromNullable, optionFlatMap, optionMap, getOrElse } from 'utils-js'
const city = pipe(
fromNullable(user.address),
optionFlatMap((a) => fromNullable(a.city)),
optionMap((c) => c.toUpperCase()),
getOrElse('UNKNOWN'),
)API
| Function | Description |
|---|---|
| some(value) | Wrap a value in Some<T> |
| none | The singleton None constant |
| fromNullable(value) | T \| null \| undefined → Option<T> |
| fromThrowable(fn) | Wrap a synchronous throwing function |
| optionMap(fn) | Transform Some, pass None |
| optionFlatMap(fn) | Chain Option-returning functions |
| optionFilter(pred) | Some → None if predicate fails |
| getOrElse(default) | Unwrap or return fallback |
| optionMatch({ some, none }) | Exhaustive pattern matching |
| toNullable(opt) | Unwrap to T \| null |
| isSome(opt) / isNone(opt) | Type guards |
collections
groupByMultiple
Groups an array by multiple key functions, producing a nested Map tree. Fully typed up to 4 levels.
import { groupByMultiple } from 'utils-js'
const result = groupByMultiple(
events,
(e) => e.year,
(e) => e.month,
(e) => e.category,
)
// Map { 2026 => Map { 3 => Map { 'meeting' => [...] } } }deepDiff
Structural diff between two plain objects or arrays, returning an array of add / remove / change operations.
import { deepDiff } from 'utils-js'
deepDiff({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 3 }, d: 4 })
// [
// { op: 'change', path: ['b', 'c'], from: 2, to: 3 },
// { op: 'add', path: ['d'], value: 4 },
// ]slidingWindow
Overlapping or striding windows over an array. Useful for time-series analysis.
import { slidingWindow } from 'utils-js'
slidingWindow([1, 2, 3, 4, 5], 3) // [[1,2,3],[2,3,4],[3,4,5]]
slidingWindow([1, 2, 3, 4, 5], 3, 2) // [[1,2,3],[3,4,5]]topologicalSort
Topological sort of a DAG. Throws CycleError (with the cycle path) if a cycle is detected.
import { topologicalSort, CycleError } from 'utils-js'
try {
const order = topologicalSort(
['build', 'test', 'lint', 'deploy'],
(task) => dependencies[task],
)
} catch (e) {
if (e instanceof CycleError) console.error('Cycle:', e.cycle)
}weightedSample
Picks n items from an array without replacement, probability proportional to weight.
import { weightedSample } from 'utils-js'
const sample = weightedSample(
[{ label: 'A', weight: 10 }, { label: 'B', weight: 1 }, { label: 'C', weight: 5 }],
(item) => item.weight,
2,
)cartesianProduct
N-ary Cartesian product with full heterogeneous tuple type inference (up to 8 arrays).
import { cartesianProduct } from 'utils-js'
cartesianProduct(['S', 'M', 'L'], ['red', 'blue'])
// [['S','red'],['S','blue'],['M','red'],['M','blue'],['L','red'],['L','blue']]strings
fuzzyMatch
Returns candidates sorted by fuzzy match score (Levenshtein-based, normalized 0–1 where 1 = exact match).
import { fuzzyMatch } from 'utils-js'
fuzzyMatch('admin', ['administrator', 'administration', 'admire'], { threshold: 0.5 })
// [{ item: 'administrator', score: 0.83 }, ...]extractPattern
Extracts named segments from a string using a {name} pattern syntax.
import { extractPattern } from 'utils-js'
extractPattern('2026-03-13', '{year}-{month}-{day}')
// { year: '2026', month: '03', day: '13' }
extractPattern('users/42/settings', 'users/{id}/settings')
// { id: '42' }diffStrings
Word-level or character-level diff between two strings.
import { diffStrings } from 'utils-js'
diffStrings('hello world', 'hello there world')
// [
// { op: 'equal', value: 'hello ' },
// { op: 'insert', value: 'there ' },
// { op: 'equal', value: 'world' },
// ]
diffStrings('abc', 'aXc', { level: 'char' })
// [{ op: 'equal', value: 'a' }, { op: 'delete', value: 'b' }, { op: 'insert', value: 'X' }, ...]tokenize
Minimal lexer: tokenizes a string with ordered regex rules.
import { tokenize } from 'utils-js'
const tokens = tokenize('42 + foo', [
{ type: 'number', pattern: /\d+/ },
{ type: 'operator', pattern: /[+\-*/]/ },
{ type: 'ident', pattern: /[a-z_]\w*/i },
{ type: 'ws', pattern: /\s+/ },
])
// [{ type: 'number', value: '42', start: 0, end: 2 }, ...]interpolate
Template interpolation with inline transforms.
import { interpolate } from 'utils-js'
interpolate('Hello, {{name | upper}}! You have {{count}} messages.', {
name: 'pablo',
count: 3,
})
// 'Hello, PABLO! You have 3 messages.'Built-in transforms: upper, lower, trim, number, date. Custom transforms can be passed via options.transforms.
temporal
All functions accept and return native Temporal objects. The library does not bundle any polyfill.
businessDaysBetween
Business days between two dates, inclusive of both endpoints. Supports custom holiday lists and work-day configurations.
import { businessDaysBetween } from 'utils-js'
import { Temporal } from '@js-temporal/polyfill'
businessDaysBetween(
Temporal.PlainDate.from('2026-03-10'),
Temporal.PlainDate.from('2026-03-20'),
{ holidays: [Temporal.PlainDate.from('2026-03-17')] },
)
// 7availabilitySlots
Generates available time slots of a fixed duration within a range, excluding blocked intervals.
import { availabilitySlots } from 'utils-js'
const slots = availabilitySlots(
{ start: workdayStart, end: workdayEnd },
Temporal.Duration.from({ minutes: 30 }),
existingMeetings,
)
// [{ start: ZonedDateTime, end: ZonedDateTime }, ...]scheduleOverlap
Intersection of free-time windows across multiple entities — useful for finding shared availability.
import { scheduleOverlap } from 'utils-js'
scheduleOverlap([aliceFreeSlots, bobFreeSlots, charlieFreeSlots])
// Windows where all three are simultaneously freehumanRelative
Contextual human-readable relative time. Understands business days and locale.
import { humanRelative } from 'utils-js'
humanRelative(Temporal.Instant.from('2026-03-16T10:00:00Z'), {
timeZone: 'Europe/Madrid',
businessDays: true,
})
// 'in 3 business days'fiscalPeriod
Fiscal quarter and date boundaries for any fiscal year start month.
import { fiscalPeriod } from 'utils-js'
fiscalPeriod(Temporal.PlainDate.from('2026-03-13'), 4)
// { quarter: 4, fiscalYear: 2025, start: PlainDate('2026-01-01'), end: PlainDate('2026-03-31') }TypeScript
All public API types are exported:
import type {
Option, Some, None,
TimeRange, FiscalPeriod,
DiffOp, StringDiffOp,
TokenRule, Token,
BusinessDaysOptions, HumanRelativeOptions,
FuzzyMatchOptions, DiffStringsOptions, InterpolateOptions,
} from 'utils-js'License
MIT — see LICENSE.
