npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

utilful

v2.5.1

Published

A collection of TypeScript utilities

Readme

utilful

A collection of TypeScript utilities that I use across my projects.

Table of Contents

Installation

Run the following command to add utilful to your project.

# npm
npm install -D utilful

# pnpm
pnpm add -D utilful

# yarn
yarn add -D utilful

API

Array

toArray

Converts MaybeArray<T> to Array<T>.

type MaybeArray<T> = T | T[]

declare function toArray<T>(array?: MaybeArray<T> | null | undefined): T[]

CSV

createCSV

Converts an array of objects to a comma-separated values (CSV) string. You can either specify which columns to include explicitly, or let the function automatically infer all columns from your data.

// With explicit columns
declare function createCSV<T extends Record<string, unknown>>(
  data: readonly T[],
  columns: readonly (keyof T)[],
  options?: CSVCreateOptions
): string

// With automatic column inference
declare function createCSV<T extends Record<string, unknown>>(
  data: readonly T[],
  options?: CSVCreateOptions
): string

Example with explicit columns:

const data = [
  { name: 'John', age: '30', city: 'New York' },
  { name: 'Jane', age: '25', city: 'Boston' }
]

// Only include 'name' and 'age' columns
const csv = createCSV(data, ['name', 'age'])
// name,age
// John,30
// Jane,25

Example with automatic column inference:

When you omit the columns parameter, createCSV automatically collects all unique keys from your data in first-seen order. This is particularly useful when working with data that has varying structures:

const rows = [
  { name: 'John', age: '30' },
  { name: 'Jane', city: 'Boston' },
  { name: 'Bob', age: '40', city: 'Chicago' }
]

// All columns are automatically detected: name, age, city
const csv = createCSV(rows)
// name,age,city
// John,30,
// Jane,,Boston
// Bob,40,Chicago

parseCSV

Parses a comma-separated values (CSV) string into an array of objects.

[!NOTE] The first row of the CSV string is used as the header row.

type CSVRow<T extends string = string> = Record<T, string>

declare function parseCSV<Header extends string>(
  csv?: string | null | undefined,
  options?: {
    /** @default ',' */
    delimiter?: string
    /**
     * Trim whitespace from headers and values.
     * @default true
     */
    trim?: boolean
    /**
     * Throw error if row has more fields than headers.
     * @default true
     */
    strict?: boolean
  }
): CSVRow<Header>[]

Example:

const csv = `
name,age
John,30
Jane,25
`.trim()

const data = parseCSV<'name' | 'age'>(csv) // [{ name: 'John', age: '30' }, { name: 'Jane', age: '25' }]

Defu

Recursively assign default properties. Simplified version based on unjs/defu.

defu

Recursively assigns missing properties from defaults to the source object. The source object takes precedence over defaults.

The function replaces null and undefined values in the source with defaults, concatenates arrays (source + defaults), and recursively merges nested objects.

type PlainObject = Record<PropertyKey, any>

declare function defu<T extends PlainObject>(
  source: T,
  ...defaults: PlainObject[]
): T

Example:

import { defu } from 'utilful'

const result = defu(
  { a: 1, b: { x: 1 } },
  { a: 2, b: { y: 2 }, c: 3 }
)
// Result: { a: 1, b: { x: 1, y: 2 }, c: 3 }

Array concatenation example:

const result = defu(
  { items: ['a', 'b'] },
  { items: ['c', 'd'] }
)
// Result: { items: ['a', 'b', 'c', 'd'] }

Handling null/undefined:

const result = defu(
  { name: null, age: undefined },
  { name: 'John', age: 30, city: 'NYC' }
)
// Result: { name: 'John', age: 30, city: 'NYC' }

createDefu

Creates a custom defu function with a custom merger.

type DefuMerger<T extends PlainObject = PlainObject> = (
  target: T,
  key: PropertyKey,
  value: any,
  namespace: string,
) => boolean | void

declare function createDefu(merger?: DefuMerger): DefuFn

Example:

import { createDefu } from 'utilful'

// Custom merger that adds numbers instead of replacing them
const addNumbers = createDefu((obj, key, val) => {
  if (typeof val === 'number' && typeof obj[key] === 'number') {
    obj[key] += val
    return true // Indicates the merger handled this property
  }
})

const result = addNumbers({ cost: 15 }, { cost: 10 })
// Result: { cost: 25 }

Emitter

Tiny functional event emitter / pubsub, based on mitt.

Example:

import { createEmitter } from 'utilful'

// eslint-disable-next-line ts/consistent-type-definitions
type Events = {
  foo: { a: string }
}

const emitter = createEmitter<Events>()

// Listen to an event
emitter.on('foo', e => console.log('foo', e))

// Listen to all events
emitter.on('*', (type, e) => console.log(type, e))

// Fire an event
emitter.emit('foo', { a: 'b' })

// Clearing all events
emitter.events.clear()

// Working with handler references:
function onFoo() {}
emitter.on('foo', onFoo) // Listen
emitter.off('foo', onFoo) // Unlisten

JSON

tryParseJSON

Type-safe wrapper around JSON.stringify.

Falls back to the original value if the JSON serialization fails or the value is not a string.

declare function tryParseJSON<T = unknown>(value: unknown): T

cloneJSON

Clones the given JSON value.

[!NOTE] The value must not contain circular references as JSON does not support them. It also must contain JSON serializable values.

declare function cloneJSON<T>(value: T): T

Module

interopDefault

Interop helper for default exports.

declare function interopDefault<T>(m: T | Promise<T>): Promise<T extends {
  default: infer U
} ? U : T>

Example:

import { interopDefault } from 'utilful'

async function loadModule() {
  const mod = await interopDefault(import('./module.js'))
}

Object

memoize

A simple general purpose memoizer utility.

  • Lazily computes a value when accessed
  • Auto-caches the result by overwriting the getter

Useful for deferring initialization or expensive operations. Unlike a simple getter, there is no runtime overhead after the first invokation, since the getter itself is overwritten with the memoized value.

declare function memoize<T>(getter: () => T): { value: T }

Example:

const myValue = lazy(() => 'Hello, World!')
console.log(myValue.value) // Computes value, overwrites getter
console.log(myValue.value) // Returns cached value
console.log(myValue.value) // Returns cached value

objectKeys

Strictly typed Object.keys.

declare function objectKeys<T extends Record<any, any>>(obj: T): Array<`${keyof T & (string | number | boolean | null | undefined)}`>

objectEntries

Strictly typed Object.entries.

declare function objectEntries<T extends Record<any, any>>(obj: T): Array<[keyof T, T[keyof T]]>

deepApply

Deeply applies a callback to every key-value pair in the given object, as well as nested objects and arrays.

declare function deepApply<T extends Record<any, any>>(data: T, callback: (item: T, key: keyof T, value: T[keyof T]) => void): void

Result

The Result type represents either success (Ok) or failure (Err). It provides a type-safe way to handle errors without relying on exceptions.

type Result<T, E> = Ok<T, E> | Err<T, E>

Both Ok and Err carry phantom types for proper type inference in unions.

Basic example:

import { err, ok } from 'utilful'

function divide(a: number, b: number) {
  if (b === 0) {
    return err('Division by zero')
  }
  return ok(a / b)
}

const result = divide(10, 2)
if (result.ok)
  console.log('Result:', result.value)
else
  console.error('Error:', result.error)

Fluent chaining:

import { toResult } from 'utilful'

const name = toResult(() => JSON.parse(jsonString))
  .map(data => data.user)
  .map(user => user.name)
  .unwrapOr('Anonymous')

ok

Creates a successful result.

declare function ok<T, E = never>(value: T): Ok<T, E>

err

Creates an error result.

declare function err<T = never, E extends string = string>(error: E): Err<T, E>
declare function err<T = never, E = unknown>(error: E): Err<T, E>

isOk / isErr

Type guards for narrowing Result types.

declare function isOk<T, E>(result: Result<T, E>): result is Ok<T, E>
declare function isErr<T, E>(result: Result<T, E>): result is Err<T, E>

Example:

const result = toResult(() => JSON.parse(str))
if (isOk(result)) {
  console.log(result.value) // TypeScript knows this is Ok
}

Result.map

Transforms the success value. No-op on Err.

ok(2).map(x => x * 3) // Ok(6)
err('fail').map(x => x * 3) // Err('fail')

Result.mapError

Transforms the error value. No-op on Ok.

err('fail').mapError(e => e.toUpperCase()) // Err('FAIL')
ok(42).mapError(e => e.toUpperCase()) // Ok(42)

Result.andThen

Chains a function that returns a Result. Useful for composing fallible operations.

ok(2).andThen(x => x > 0 ? ok(x) : err('negative')) // Ok(2)
err('fail').andThen(x => ok(x * 2)) // Err('fail') - short-circuits

Result.unwrap

Extracts the value or throws an error.

ok(42).unwrap() // 42
err('fail').unwrap() // throws Error
err('fail').unwrap('custom message') // throws Error('custom message')

Result.unwrapOr

Extracts the value or returns a fallback.

ok(42).unwrapOr(0) // 42
err('fail').unwrapOr(0) // 0

Result.match

Pattern matches on the result.

result.match({
  ok: value => `Success: ${value}`,
  err: error => `Error: ${error}`,
})

toResult

Wraps a function or promise that might throw and returns a Result.

declare function toResult<T, E = unknown>(fn: () => T): Result<T, E>
declare function toResult<T, E = unknown>(promise: Promise<T>): Promise<Result<T, E>>

Example:

// Synchronous
const result = toResult(() => JSON.parse('{"foo":"bar"}'))

// Asynchronous
const result = await toResult(fetch('https://api.example.com'))

unwrapResult

Converts a Result to a plain object with value and error properties.

declare function unwrapResult<T, E>(result: Ok<T, E>): { value: T, error: undefined }
declare function unwrapResult<T, E>(result: Err<T, E>): { value: undefined, error: E }
declare function unwrapResult<T, E>(result: Result<T, E>): { value: T, error: undefined } | { value: undefined, error: E }

tryCatch

Combines toResult and unwrapResult into one step. Executes a function and returns { value, error } directly.

declare function tryCatch<T, E = unknown>(fn: () => T): { value: T, error: undefined } | { value: undefined, error: E }
declare function tryCatch<T, E = unknown>(promise: Promise<T>): Promise<{ value: T, error: undefined } | { value: undefined, error: E }>

Example:

// Synchronous
const { value, error } = tryCatch(() => JSON.parse('{"foo":"bar"}'))

// Asynchronous
const { value, error } = await tryCatch(fetch('https://api.example.com').then(r => r.json()))

String

template

Simple template engine to replace variables in a string.

declare function template(
  str: string,
  variables: Record<string | number, any>,
  fallback?: string | ((key: string) => string)
): string

Example:

import { template } from 'utilful'

const str = 'Hello, {name}!'
const variables = { name: 'world' }

console.log(template(str, variables)) // Hello, world!

generateRandomId

Generates a random string. The function is ported from nanoid. You can specify the size of the string and the dictionary of characters to use.

declare function generateRandomId(size?: number, dict?: string): string

License

MIT License © 2024-PRESENT Johann Schopplich