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

@cyca/utils

v0.1.1

Published

Personal JS utils I use in the daily

Readme

@cyca/utils

A collection of utility functions and hooks for my TypeScript/JavaScript projects.

Installation

npm install @cyca/utils
# or
pnpm add @cyca/utils
# or
bun add @cyca/utils

Table of Contents


Core Utilities

invariant(condition, message)

Throws an error if the condition is falsey. Inspired by tiny-invariant.

import { invariant } from '@cyca/utils'

invariant(typeof value === 'string', 'value must be a string')

Parameters:

  • condition: any - The condition to check
  • message: string | (() => string) - The message to throw (or a callback to generate the message)

Throws: InvariantError if condition is falsey

invariantResponse(condition, message, responseInit?)

Throws a 400 Response if the condition is falsey. Useful for server-side validation.

import { invariantResponse } from '@cyca/utils'

invariantResponse(typeof value === 'string', 'value must be a string')

Parameters:

  • condition: any - The condition to check
  • message: string | (() => string) - The message to throw
  • responseInit?: ResponseInit - Additional response init options

Throws: Response if condition is falsey

isValidHttpUrl(string)

Checks if a string is a valid HTTP URL.

import { isValidHttpUrl } from '@cyca/utils'

if (isValidHttpUrl('https://example.com')) {
  // valid URL
}

delay(ms, value?)

Promise wrapper around setTimeout.

import { delay } from '@cyca/utils'

await delay(1000) // wait 1 second
const result = await delay(500, 'value') // wait 500ms and return 'value'

Parameters:

  • ms: number - Milliseconds to wait
  • value?: T - Optional value to resolve with

Returns: Promise<unknown>

prependHttp(url, options?)

Prepends http:// or https:// to a URL if it doesn't already have a protocol.

import { prependHttp } from '@cyca/utils'

prependHttp('example.com') // 'http://example.com'
prependHttp('example.com', { https: true }) // 'https://example.com'

Parameters:

  • url: string - The URL to prepend
  • options?: { https?: boolean } - Options object

Returns: string

getUrlImageSize(url)

Gets the width and height of an image from a URL.

import { getUrlImageSize } from '@cyca/utils'

const { width, height } = await getUrlImageSize('https://example.com/image.jpg')

Returns: Promise<{ width: number; height: number }>

toBase64(str)

Converts a string to base64.

import { toBase64 } from '@cyca/utils'

const encoded = toBase64('hello world')

escapeRegExp(string)

Escapes special characters in a string for use in a regular expression.

import { escapeRegExp } from '@cyca/utils'

const escaped = escapeRegExp('hello.world')

higlightWords(textToHighlight, searchTerm, options?)

Highlights words in text that match a search term.

import { higlightWords } from '@cyca/utils'

const result = higlightWords('Hello world', 'world', {
  caseSensitive: false,
  mark: (part, matched) => matched ? `<mark>${part}</mark>` : part
})

Parameters:

  • textToHighlight: string - The text to search in
  • searchTerm: string - The term to search for
  • options? - Configuration object
    • caseSensitive?: boolean - Whether to match case
    • mark?: (part: string, matched: boolean) => T - Function to transform matched/unmatched parts
    • append?: (accum: T, part: P) => T | null - Function to accumulate results

debounce(func, timeout)

Creates a debounced function that delays invoking func until after timeout milliseconds.

import { debounce } from '@cyca/utils'

const debouncedFn = debounce(() => console.log('called'), 300)

debounceAnimationFrame(func)

Creates a debounced function that uses requestAnimationFrame.

import { debounceAnimationFrame } from '@cyca/utils'

const debouncedFn = debounceAnimationFrame(() => console.log('called'))

noop()

A no-op function that returns null.

import { noop } from '@cyca/utils'

const callback = noop()

Deep Omit

deepOmit(object, path)

Deeply omits a property from an object using a path array.

import { deepOmit } from '@cyca/utils/deep-omit'

const obj = { a: { b: { c: 1, d: 2 } } }
const result = deepOmit(obj, ['a', 'b', 'c'])
// result: { a: { b: { d: 2 } } }

Can also be used in curried form:

const omitPath = deepOmit(['a', 'b', 'c'])
const result = omitPath(obj)

Type-safe: The path is type-checked against the object structure.


Jotai Utilities

useAtomCallback(callback, options?)

A custom version of Jotai's useAtomCallback that keeps the latest callback identity.

import { useAtomCallback } from '@cyca/utils/jotai'

const handleClick = useAtomCallback((get, set) => {
  const value = get(someAtom)
  set(anotherAtom, value + 1)
})

Parameters:

  • callback: (get: Getter, set: Setter, ...args: Args) => Result - The callback function
  • options?: Options - Jotai store options

Returns: A memoized callback function

useSelectAtom(atom, selector)

Selects a derived value from an atom using a selector function.

import { useSelectAtom } from '@cyca/utils/jotai'

const userName = useSelectAtom(userAtom, (user) => user.name)

Parameters:

  • atom: Atom<V> - The atom to read from
  • selector: (v: V) => S - Function to derive value

Returns: The selected value

withSnapshotHistory(targetAtom, limit?)

Wraps an atom with undo/redo history functionality.

import { withSnapshotHistory, COMMIT, UNDO, REDO, RESET } from '@cyca/utils/jotai'

const countAtom = atom(0)
const historyAtom = withSnapshotHistory(countAtom, 10)

// In a component:
const [{ value, previous, canUndo, canRedo }, dispatch] = useAtom(historyAtom)

// Update the value (doesn't create history entry)
dispatch(5)

// Commit to history
dispatch(COMMIT)

// Undo/redo
dispatch(UNDO)
dispatch(REDO)

// Reset history
dispatch(RESET)

Parameters:

  • targetAtom: AnyWritableAtom<T> - The atom to track
  • limit?: number - Maximum history entries (default: unlimited)

Actions:

  • COMMIT - Record current value to history
  • UNDO - Move back one step
  • REDO - Move forward one step
  • RESET - Clear all history
  • SetStateAction<T> - Update value without creating history

init(callback, store?)

Initializes state or side effects when the store is created.

import { init } from '@cyca/utils/jotai'

const cleanup = init((get, set, store) => {
  // Initialize something
  set(someAtom, initialValue)
  
  // Return cleanup function
  return () => {
    // Cleanup
  }
})

Parameters:

  • callback: (get: Getter, set: Setter, store: Store) => void | (() => void) - Initialization function
  • store?: Store - Jotai store (uses default if not provided)

Returns: Cleanup function if provided by callback


Event Emitter (Mitt)

mitt(all?)

Creates a tiny event emitter.

import { mitt } from '@cyca/utils/mitt'

type Events = {
  'user:login': { id: string }
  'user:logout': void
}

const emitter = mitt<Events>()

emitter.on('user:login', (data) => {
  console.log('User logged in:', data.id)
})

emitter.emit('user:login', { id: '123' })

Parameters:

  • all?: EventHandlerMap<Events> - Optional initial handlers

Returns: Emitter<Events> with methods:

  • on(type, handler) - Subscribe to event
  • off(type, handler) - Unsubscribe from event
  • emit(type, event) - Emit event
  • all - Map of all handlers

bind(emitter, event, listener)

Binds a listener to an event and returns an unbind function.

import { mitt, bind } from '@cyca/utils/mitt'

const emitter = mitt()
const unbind = bind(emitter, 'event', (data) => {
  console.log(data)
})

// Later...
unbind()

Returns: () => void - Unbind function

bindAll(emitter, listeners)

Binds multiple listeners at once and returns an unbind function.

import { mitt, bindAll } from '@cyca/utils/mitt'

type Events = {
  'login': { id: string }
  'logout': void
}

const emitter = mitt<Events>()
const unbind = bindAll(emitter, {
  login: (data) => console.log('Login:', data.id),
  logout: () => console.log('Logout')
})

// Later...
unbind()

Returns: () => void - Unbind function that removes all listeners


React Hooks

useMediaQuery(query)

Hook to match media queries.

import { useMediaQuery } from '@cyca/utils/react'

const isMobile = useMediaQuery('(max-width: 768px)')

Returns: boolean | undefined - undefined during SSR

useClientLayoutEffect

Use useEffect during SSR and useLayoutEffect in the browser to avoid warnings.

import { useClientLayoutEffect } from '@cyca/utils/react'

useClientLayoutEffect(() => {
  // Safe to use in SSR
}, [])

useConstant(input)

Creates a value exactly once. Unlike useMemo, this is guaranteed.

import { useConstant } from '@cyca/utils/react'

const expensiveObject = useConstant(() => createExpensiveObject())

usePreviousEffect(effect, inputs)

Runs an effect with access to previous dependency values.

import { usePreviousEffect } from '@cyca/utils/react'

usePreviousEffect(([prevCount]) => {
  console.log(`Count changed from ${prevCount} to ${count}`)
}, [count])

useHasChangedEffect(effect, deps, equal?)

Runs an effect only when a dependency has changed.

import { useHasChangedEffect } from '@cyca/utils/react'

useHasChangedEffect(() => {
  console.log('User changed')
}, [user], (a, b) => a.id === b.id)

usePreviousValue(value)

Returns the previous value from the last render.

import { usePreviousValue } from '@cyca/utils/react'

const previousCount = usePreviousValue(count)

useHasChanged(value)

Returns true if the value has changed since the last render.

import { useHasChanged } from '@cyca/utils/react'

const hasChanged = useHasChanged(count)

useEvent(callback)

Creates an always-stable function identity. Easier to use than useCallback.

import { useEvent } from '@cyca/utils/react'

const handleClick = useEvent(() => {
  console.log(count) // Always has latest value
})

useLatest(value)

A React helper hook for storing latest value in ref object (updated in useLayoutEffect's callback). Similar to useEvent()

import { useLatest } from '@cyca/utils/react'

const valRef = useLatest(value)

// latest val
valRef.current

useAsyncCallback()

Returns a handler with pending states for async operations.

import { useAsyncCallback } from '@cyca/utils/react'

const [{ pending, success, error }, wrapAsync] = useAsyncCallback()

const handleSubmit = wrapAsync(async () => {
  await api.submit()
})

if (pending) return <Spinner />
if (error) return <Error error={error} />

Returns: [{ pending: boolean, success: boolean, error: Error | null }, wrap]

useEffectOnce(effectCb)

Runs an effect only once, even in strict mode.

import { useEffectOnce } from '@cyca/utils/react'

useEffectOnce(() => {
  console.log('Runs only once')
})

useDeepMemo(memoFn, key)

Memoizes a value using deep equality comparison.

import { useDeepMemo } from '@cyca/utils/react'

const memoizedValue = useDeepMemo(() => {
  return computeExpensiveValue(obj)
}, [obj])

useDeepMemoEffect(callback, deps)

Like useEffect but with deep equality comparison for dependencies.

import { useDeepMemoEffect } from '@cyca/utils/react'

useDeepMemoEffect(() => {
  console.log('Object deeply changed')
}, [complexObject])

useForceUpdate()

Returns a function to force a re-render.

import { useForceUpdate } from '@cyca/utils/react'

const [key, forceUpdate] = useForceUpdate()

// Later...
forceUpdate() // Forces re-render

useIntersectionCallback(callback, options?)

Detects element visibility using Intersection Observer.

import { useIntersectionCallback } from '@cyca/utils/react'

const ref = useIntersectionCallback((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      console.log('Element is visible')
    }
  })
}, { threshold: 0.5 })

return <div ref={ref}>Content</div>

useIsIntersecting(options?)

Returns a boolean indicating if element is intersecting and a ref callback.

import { useIsIntersecting } from '@cyca/utils/react'

const [isVisible, ref] = useIsIntersecting({ threshold: 0.5 })

return <div ref={ref}>{isVisible ? 'Visible' : 'Hidden'}</div>

useIsVisible, useInView

Aliases for useIsIntersecting.

useFocusElementOnVisible()

Automatically focuses an element when it becomes visible.

import { useFocusElementOnVisible } from '@cyca/utils/react'

const ref = useFocusElementOnVisible()

return <input ref={ref} />

useCounter(initialValue?)

A simple counter abstraction.

import { useCounter } from '@cyca/utils/react'

const { count, increment, decrement, reset, setCount } = useCounter(0)

useSuspendAfterMount(callback)

Defers a suspense-triggering function until after mount.

import { useSuspendAfterMount } from '@cyca/utils/react'

const data = useSuspendAfterMount(() => fetchData())

useLazyRef(fn)

A wrapper for useRef with lazy initialization.

import { useLazyRef } from '@cyca/utils/react'

const ref = useLazyRef(() => new ExpensiveClass())

useInterval(callback, delay)

Runs a callback at a regular interval.

import { useInterval } from '@cyca/utils/react'

useInterval(() => {
  console.log('Tick')
}, 1000)

useAnimationFrame(cb, deps)

Runs a callback on every animation frame.

import { useAnimationFrame } from '@cyca/utils/react'

useAnimationFrame(({ time, delta }) => {
  // Animation logic
}, [])

useStyleMeasure(prefix?)

Dynamically measures element size and updates CSS custom properties.

import { useStyleMeasure } from '@cyca/utils/react'

const ref = useStyleMeasure('box')

// Sets --box-width and --box-height CSS variables
return <div ref={ref}>Content</div>

withHook(useHook)

Converts a React hook into a render prop component.

import { withHook } from '@cyca/utils/react'

const WithCounter = withHook(useCounter)

<WithCounter args={[0]}>
  {({ count, increment }) => (
    <button onClick={increment}>{count}</button>
  )}
</WithCounter>

withSuspense(Component, Fallback?)

Wraps a component with Suspense boundary.

import { withSuspense } from '@cyca/utils/react'

const UserProfile = withSuspense(
  UserProfileComponent,
  () => <Spinner />
)

License

MIT