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

ramjam

v1.3.5

Published

Functional JavaScript utilities that extend Ramda

Downloads

6

Readme

Ramda extensions

with state utils for React


adjustBy

merge partial updates to a collection item by matching key / value

const adjustById = adjustBy<'id'>('id')

const state: Item[] = [
  { id: 1, x: 1, y: 1 },
  { id: 2, x: 2, y: 2 }
]

const update = { id: 2, x: 42 }
adjustById<Item>(update, state)
// [
//  { id: 1, x: 1, y: 1 },
//  { id: 2, x: 42, y: 2 }
//]

const updateItem = adjustBy<'id', Item>('id', update)
const newState = adjustBy<'id', Item>('id', update, state)

allAbsent

allAbsent([[], {}, ''])
// true

allAbsent([[], {one: 1}, ''])
// false
const isSignupButtonShowing = allAbsent([user, signupToken])

allPresent

allPresent([['a'], { one: 1 }, 'a', 1])
// true

allPresent([[], { one: 1 }, 'a', 1])
// false
const isConfigShowing = allPresent([user, activeSettingsTab])

anyAbsent

anyAbsent([[], { one: 1 }, 'x'])
// true

anyAbsent([[42], { one: 1 }, 'x'])
// false
const isSpinnerShowing = allAbsent([user, guitars, fetchError])

anyPresent

anyPresent([['a'], {}, ''])
// true

anyPresent([[''], {}, ''])
// false
const isDashboardShowing = anyPresent([user, guitars])

appendOrRemove

toggle list item

const state = ['one', 'two', 'three']

appendOrRemove('two', state)
appendOrRemove('four', state)
// ['one', 'three', 'four']
const onClickCheckbox = value => {
  const newSelections = appendOrRemove(value, selections)
  setSelections(newSelections)
}

appendOrRemoveBy

toggle collection items by matching key / value

const list = [{ id: 1, color: 'blue' }, { id: 2, color: 'green' }, { id: 3, color: 'blue' }]
appendOrRemoveBy('color', { color: 'blue' }, list)
// list becomes [{ id: 2, color: 'green' }]
appendOrRemoveBy('id', { id: 4, color: 'red' }, list)
// list becomes [{ id: 2, color: 'green' }, { id: 4, color: 'red' }]
const toggleItem = appendOrRemoveBy('id')

appendOrRemoveStateBy

toggle collection items in state

const useItems = () => {
  const [items, setItems] = useState([{ id: 1, label: 'nice', color: 'blue' }])

  const toggleItem = appendOrRemoveStateBy(setItems, 'id')
  const toggleColor = appendOrRemoveStateBy(setItems, 'color')

  return { items, toggleColor, toggleItem }
}

toggleItem({ id: 1, label: 'nice', color: 'blue' })
// toggle item with id 1 
toggleColor({color: 'blue'})
// toggle all blue items

appendState

append item or array of items to state

const usePedals = () => {
  const [pedals, setPedals] = useState(['fuzz'])
  const addPedals = appendState<PedalType>(setPedals)
  return { addPedals, pedals }
}
const { addPedals } = usePedals() 
addPedals('delay')
// pedals state becomes ['fuzz', 'delay']
addPedals(['wah', 'phase'])
// pedals state becomes ['fuzz', 'delay', 'wah', 'phase']

camelToSnake

camelToSnake('oneTwoThree')
// 'one_two_three'
const toSqlKeys = mapKeys<Prev, Next>(camelToSnake)

const user = {
  familyName: '',
  givenName: ''
}

const createUserQuery = toSqlKeys(user)
// {
//   family_name: '',
//   given_name: ''
// }

extend

augment an object by applying a function to it

const sumValues = pipe(values, sum)

const extendSum = extend<Prev, 'sum', number>(sumValues, 'sum') 

extendSum({ one: 1, two: 2 })
// { one: 1, two: 2, sum: 3 }

first

for collections; returns an empty object when passed an empty array or anything other than an array

first( [{one: 1}] )
// {one: 1}
first(null)
// {}
first([])
// {}

flatPick

pluck potentially nested props from an object and flatten the result

const axiosErrorPaths = [
  ['message'],
  ['config', 'url'],
  ['config', 'data'],
  ['response', 'status'],
  ['response', 'statusText'],
  ['response', 'data']
]

const makeErrorResponse = flatPick<AxiosError, ErrorResponse>(axiosErrorPaths)

const onError = axiosError => {
  const response = makeErrorResponse(axiosError)
  return Promise.reject(response)
}

axios.interceptors.response.use(prop('data'), onError)
// {
//   message: 'Request failed with status code 502',
//   url: '/users',
//   data: 'config data',
//   status: 502,
//   statusText: 'Bad Gateway',
//   response_data: 'response data'
// }

getQueryParams

// https://nikhuber-guitars.com/dealers?type=orca%2059&country=thailand"

getQueryParams(window)
// {
//    type: 'orca 59',
//    country: 'thailand'
//  }
import { useEffect, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { getQueryParams } from 'utils'

const useQueryParams = () => {
  const { search } = useLocation()
  const [queryParams, setQueryParams] = useState<QueryParams>({})

  useEffect(() => {
    const newQueryParams = getQueryParams<QueryParams>(window)
    setQueryParams(newQueryParams)
  }, [search])

  return queryParams
}

export default useQueryParams

getRouteFragments

list of route fragments

// 'https://www.mesaboogie.com/en-US/Amp/?model=triple-rectifier'

getRouteFragments(window)
// ['en-US', 'Amp']

getUniqValues

unique values of a prop in a collection

const guitars: Guitar[] = [
  { id: 1, make: 'Gibson', pickup: 'PAF' },
  { id: 2, make: 'Gibson', pickup: '57 Classic' },
  { id: 3, make: 'Fender', pickup: 'Lace Sensor' }
]

const getBrandOptions = getUniqValues('make')

getBrandOptions<Guitar>(guitars)
// ['Gibson', 'Fender']

isAbsent

isAbsent({})
isAbsent([])
isAbsent('')
isAbsent(null)
isAbsent(undefined)
// true

isAbsent({ one: 1 })
isAbsent(['a'])
isAbsent('a')
isAbsent(false) // <-- emptiness; not truthiness (not intended for bools)
// false
const isSignupButtonShowing = isAbsent(user)

isPresent

isPresent({ one: 1 })
isPresent(['a'])
isPresent('a')
isPresent(false) // <-- emptiness; not truthiness (not intended for bools)
// true

isPresent({})
isPresent([])
isPresent('')
isPresent(null)
isPresent(undefined)
// false
const isDashboardLinkShowing = isPresent(user)

makeQueryParams

make query string from object, prepended by '?'

const members = {
  guitar: 'Leo Nocentelli',
  keyboard: 'Art Neville'
}

makeQueryParams(members)
// ?guitar=Leo%20Nocentelli&keyboard=Art%20Neville

mapKeys

apply a transform function to each key in an object, recursively

const toUpper = x => x.toUpperCase()
const gear = {
  cables: {
    toAmp: 'Boss',
    patch: 'George L'
  }
}

const toUpperKeys = mapKeys<Prev, Next>(toUpper)

toUpperKeys(gear)
// {
//    CABLES: {
//      TOAMP: 'Boss',
//      PATCH: 'George L'
//    }
// }

mapReplace

map replacements over a string

replaceFragments = mapReplace({
  'P Funk': 'P-Funk',
  In: 'in'
})

const makeBillboard = pipe(
  split('-'),
  map(ucFirst),
  join(' '),
  replaceFragments
)

const slug = 'p-funk-live-in-new-york'
makeBillBoard(slug)
// 'P-Funk Live in New York'

mergeState

merge state object updates

const useUser = () => {
  const [user, setUser] = useState<MaybeEmpty<User>>({})
  const updateUser = mergeState<User>(setUser)
  return { updateUser, user }
}
const { updateUser } = useUser() 

updateUser({ id: 1, age: 42 })
updateUser({ age: 43, points: 2 })
// user is now { id: 1, age: 43, points: 2 }

nl2br

insert <br /> tags into a value intended for a text node

*needs key solution

const titles = `The Bends
Ok Computer
Kid A`

nl2br(titles)
//['The Bends', <br />, 'Ok Computer', <br />, 'Kid A']  

notEmpty

complement(isEmpty)

prependState

prepend item or array of items to state arrays

const useInts = () => {
  const [ints, setInts] = useState([1])
  const prependInts = prependState<number>(setInts)
  return { ints, prependInts }
}
const { ints, prependInts } = useInts() 
prependInts(2)
// ints state becomes [2, 1]
prependInts([3, 4])
// ints state becomes [3, 4, 2, 1]

propEq

classic propEq before it was broken in 0.29

propEq('color', 'blue', { color: 'blue' })
//true
// implicit typing ok
const idEq = propEq('_id')
idEq(42)

propEq('_id', 42, item)

// this signature requires explicit typing
const isGreen = propEq<'color', Item>('color', 'green')

prune

pluck potentially nested props from an object

props not on the given object are set to null to keep them in json

const axiosErrorPaths = [
  ['message'],
  ['config', 'url'],
  ['config', 'data'],
  ['response', 'status'],
  ['response', 'statusText'],
  ['response', 'data']
]

const makeErrorResponse = prune<AxiosError, ErrorResponse>(axiosErrorPaths)
makeErrorResponse(axiosError)
// {  
//    message: 'Request failed with status code 502',
//    config: {
//      url: '/users',
//      data: 'config data'
//    },
//    response: {
//      status: 502,
//      statusText: 'Bad Gateway',
//      data: 'response data'
//    }
//  }

pruneOr

(prune with defaults)

pluck potentially nested props from an object

provide default values when a prop is absent

props not on the given object are set to null to keep them in json

const defaults = { 
  one: { 
    one1: 'default' 
  } 
}

const desiredShape = [ 
  ['one', 'one1'], 
  ['two', 'two1'], 
  ['four']
]

const toPrune = { 
  two: { 
    two1: 'incoming', 
    two2: 'incoming' 
  }, 
  three: 'incoming' 
}

const pruneResponse = pruneOr<AxiosError, ErrorResponse>(defaults, desiredShape)
pruneResponse(toPrune)
{ 
  one: { 
    one1: 'default'
  },
  { 
    two: { 
      two1: 'incoming'
    } 
  }, 
  {
    four: null 
  }
}

removeBy

Remove collection items by matching key/value

const colors = [
  { id: 1, color: 'red' },
  { id: 2, color: 'green' },
  { id: 3, color: 'blue' }, 
  { id: 4, color: 'blue' }
]
   
const removeById = removeBy<'id'>('id')
const itemToRemove = { id: 2, color: 'green' }
// this matches { id: 2 }
removeById<Item>(itemToRemove, colors)
// [
//   { id: 1, color: 'red' }, 
//   { id: 3, color: 'blue' }, 
//   { id: 4, color: 'blue' }
// ]

const removeBlueItems = removeBy<'color', Item>('color')
// this matches { color: 'blue' }
removeColor('blue', colors)
// [
//   { id: 1, color: 'red' }, 
//   { id: 2, color: 'green' }
// ]

removeStateBy

remove objects from a collection in state by matching key / value

const fetchedGuitars = [
  { id: 1, make: 'Gibson' },
  { id: 2, make: 'Gibson' },
  { id: 3, make: 'Fender' },
  { id: 4, make: 'Fender' }
]

const useGuitars = () => {
  const [guitars, setGuitars] = useState(fetchedGuitars)
  
  const removeGuitarBy = removeStateBy(setGuitars)
  const removeGuitar = removeGuitarBy('id')
  const removeGuitarMake = removeGuitarBy('make')

  return { guitars, removeGuitar, removeGuitarMake }
}

const { removeGuitar, removeGuitarMake } = useGuitars()

// this matches { id: 2 }
removeGuitar({ id: 2, make: 'Gibson' })
// guitars state becomes 
// [
//  { id: 1, make: 'Gibson' },
//  { id: 3, make: 'Fender' },
//  { id: 4, make: 'Fender' }
// ]

// this matches { make: 'Fender' }
removeGuitarMake('Fender')
// guitars state then becomes 
// [{ id: 1, make: 'Gibson' }]
  removeStateBy<Guitar, 'id'>(setGuitars, 'id', { id: 1 })
  removeStateBy<Guitar>(setGuitars)<'id'>('id', { id: 1 })
  removeStateBy<Guitar>(setGuitars)<'id'>('id')({ id: 1 })

renameKeys

apply a transform function to each key in an object, recursively

const guitar = {
  guitarId: 42,
  transduction: 'SH4',
  config: {
    transduction: true
  },
  make: 'Gibson'
}

const guitarKeyReplacements = {
  guitarId: 'id',
  transduction: 'pickups'
}

const renameGuitarKeys = renameKeys<Prev, Next>(guitarKeyReplacements)
renameGuitarKeys(guitar)
// {
//   id: 42,
//   pickups: 'SH4',
//   config: {
//     pickups: true
//   },
//   make: 'Gibson'
// }

snakeToCamel

snakeToCamel('one_two_three')
// oneTwoThree
const toJsKeys = mapKeys(snakeToCamel)

const queryResult = {
  user_id: 42,
  given_name: '',
  family_name: ''
}

toJsKeys(queryResult)
// {
//   userId: 42,
//   givenName: '',
//   familyName: ''
// }

updateState

create a side effect to update state by curring a transform function and a state setter

the transform function is of arity one or greater; previous state is the last arg

type TransformArgs = [Partial<Guitar>]

const useGuitar = () => {
  const [guitar, setGuitar] = useState<MaybeEmpty<Guitar>>({})
  const updateGuitar = updateState<Guitar, TransformArgs>(mergeDeepLeft, setGuitar)
  return { guitar, updateGuitar }
}

const { updateGuitar } = useGuitar()
// const updateGuitar: (args_0: Partial<Guitar>) => void
updateGuitar({ id: 1, status: 'new' })
updateGuitar({ status: 'used', make: 'Gibson' })
// guitar state becomes 
// { id: 1, status: 'used', make: 'Gibson' }