Utilities (massaman/*)

Overview

Check es-toolkit before writing any utility function. It likely already exists with better edge-case handling, tree-shaking, and type safety. In this project, import es-toolkit through massaman/* subpaths — massaman is the umbrella package the codebase uses for both es-toolkit and ts-pattern. Subpath mapping:

ConcernImport path
Predicatesmassaman/predicate
Object utilsmassaman/object
Array utilsmassaman/array
String utilsmassaman/string
Math utilsmassaman/math
Function utilsmassaman/function
Pattern matchmassaman/match
Control flowmassaman/control
Type conversionmassaman/conversion

These rules cover which utilities to reach for and when to write your own instead. Examples below use the project import form.

Rules

Use Type Guards for Runtime Checks

Functions for runtime type checking. Prefer these over manual typeof chains.

FunctionDescriptionExample
isNilCheck null or undefinedisNil(value)
isNullCheck null onlyisNull(value)
isUndefinedCheck undefined onlyisUndefined(value)
isStringCheck stringisString(value)
isNumberCheck number (not NaN)isNumber(value)
isBooleanCheck booleanisBoolean(value)
isPlainObjectCheck plain objectisPlainObject(value)
isArrayCheck arrayisArray(value)
isFunctionCheck functionisFunction(value)

Correct

import { isNotNil, isPlainObject, isString } from 'massaman/predicate'

// Filter out nil values
if (isNotNil(value)) {
  // value is not null or undefined
}

// Validate payload structure
if (isPlainObject(payload) && isString(payload.action)) {
  // payload is a plain object with string action
}

Use Object Utilities for Immutable Transforms

Functions for picking, omitting, and transforming object properties without mutation.

FunctionDescriptionExample
pickSelect propertiespick(obj, ['a', 'b'])
omitExclude propertiesomit(obj, ['secret'])
omitByExclude by predicateomitBy(obj, isNil)
pickBySelect by predicatepickBy(obj, isString)
mapValuesTransform valuesmapValues(obj, fn)
mapKeysTransform keysmapKeys(obj, fn)
mergeDeep merge objectsmerge(target, source)
cloneShallow cloneclone(obj)
cloneDeepDeep clonecloneDeep(obj)

Correct

import { omitBy, pick, omit } from 'massaman/object'
import { isNil } from 'massaman/predicate'

// Select specific fields for display
const summary = pick(config, ['name', 'root', 'scripts'])

// Remove internal fields before serializing
const safeConfig = omit(config, ['_resolved', '_path'])

// Remove nil values before writing config
const cleanConfig = omitBy(rawConfig, isNil)

Use Collection Utilities for Arrays

Functions for grouping, deduplicating, and batching arrays.

FunctionDescriptionExample
groupByGroup by key/functiongroupBy(scripts, 'workspace')
keyByCreate lookup by keykeyBy(scripts, 'name')
chunkSplit into chunkschunk(items, 10)
uniqRemove duplicatesuniq(array)
uniqByRemove duplicates by keyuniqBy(scripts, 'name')
differenceItems in first not seconddifference(a, b)
intersectionItems in bothintersection(a, b)
compactRemove falsy valuescompact(array)
flattenFlatten one levelflatten(nested)
flattenDeepFlatten all levelsflattenDeep(nested)

Correct

import { chunk, groupBy, keyBy, uniqBy } from 'massaman/array'

// Group scripts by workspace
const scriptsByWorkspace = groupBy(scripts, 'workspace')
// { root: [...], packages/lib: [...] }

// Create name lookup
const scriptsByName = keyBy(scripts, 'name')
// { build: script1, lint: script2 }

// Process workspaces in batches
const batches = chunk(workspaces, 10)
await batches.reduce((chain, batch) => chain.then(() => processBatch(batch)), Promise.resolve())

// Remove duplicate script names
const uniqueScripts = uniqBy(scripts, 'name')

Use Function Utilities for Scheduling and Caching

Functions for controlling execution timing and caching results.

FunctionDescriptionExample
debounceDelay until pausedebounce(fn, 300)
throttleLimit call frequencythrottle(fn, 1000)
memoizeCache resultsmemoize(fn)
onceCall only onceonce(fn)
noopNo-op functionnoop
identityReturn inputidentity

Correct

import { debounce, memoize, throttle } from 'massaman/function'

// Debounce file watcher callback to avoid redundant rebuilds
const onFileChange = debounce((path: string) => {
  rebuildWorkspace(path)
}, 300)

// Throttle log output to at most once per second
const logProgress = throttle((message: string) => {
  process.stdout.write(`\r${message}`)
}, 1000)

// Cache expensive config resolution
const resolveConfig = memoize((root: string) => {
  return loadAndMergeConfig(root)
})

Use String Utilities for Case Conversion

Functions for converting between naming conventions.

FunctionDescriptionExample
camelCaseConvert to camelCasecamelCase('foo-bar')
kebabCaseConvert to kebab-casekebabCase('fooBar')
snakeCaseConvert to snake_casesnakeCase('fooBar')
capitalizeUppercase first lettercapitalize('hello')
trimRemove whitespacetrim(' hello ')

Correct

import { camelCase, kebabCase } from 'massaman/string'

// Convert config keys from snake_case
const configKey = 'script_timeout'
const jsKey = camelCase(configKey) // 'scriptTimeout'

// Convert to kebab-case for file names
const moduleName = 'ConfigLoader'
const fileName = kebabCase(moduleName) // 'config-loader'

Avoid massaman/predicate for Trivial Operations

For standalone null checks, prefer inline comparison over importing isNil. Reserve predicate helpers for composed or higher-order contexts where they add clarity, such as callbacks to omitBy, filter, or other higher-order functions.

Correct

// Standalone null check - inline is clearer
if (x != null) {
  // ...
}

// Predicate context - isNil is idiomatic here
const cleanConfig = omitBy(rawConfig, isNil)
const validItems = items.filter((item) => !isNil(item.value))

// Complex grouping - use the utility
const grouped = groupBy(scripts, 'workspace')
const batches = chunk(workspaces, 100)

Incorrect

import { isNil } from 'massaman/predicate'

// For standalone null checks, prefer inline comparison
if (!isNil(x)) {
  // ...
}

Resources

References