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 🙏

© 2025 – Pkg Stats / Ryan Hefner

bedit

v0.0.11

Published

like immer but weird (and kinda cool)

Readme

bedit

A weird (but kinda cool) immutable state utility for TypeScript.

It's like immer but:

  • 🕵️‍♀️ No Proxy instances getting in the way when you're trying to debug.
  • 📈 10x faster (tbh only 5x but emotionally 10x)
  • 📉 Tiny (2kB minified)
  • 💅 An "innovative" API (your AI agent will enjoy the challenge)

Installation

npm install bedit

Usage

The edit function creates a shallow clone of the input, and passes it to a callback.

import { edit, setIn, updateIn, editIn } from 'bedit'
const state = {
  user: { name: 'Nick Cave', preferences: { theme: 'dark' } },
  todos: [
    { id: '0', title: 'Go fishing', completed: false },
    { id: '1', title: 'Buy milk', completed: true },
    { id: '2', title: 'Write a song', completed: false },
  ],
  filter: 'all',
}

const nextState = edit(state, (draft) => {
  // `draft` is a regular JS object, not a Proxy.
  // You can edit it at the top level.
  draft.filter = 'completed'

  // TypeScript will prevent you from making deep edits.
  // ❌ Type error: `theme` is readonly
  draft.user.preferences.theme = 'light'

  // Instead, call `setIn` on the draft object to assign deeply.
  setIn(draft).user.preferences.theme('light')
  setIn(draft).todos[2].completed(true)

  // Use `updateIn` to apply a function to a value.
  updateIn(draft).todos[1].title((title) => title.toUpperCase() + '!!!')

  // `updateIn` can also be used to call methods on collections.
  updateIn(draft).todos.push({ id: '3', title: 'Buy bread', completed: false })
  updateIn(draft).todos.filter((todo) => !todo.completed)
  updateIn(draft).todos.sort((a, b) => a.title.localeCompare(b.title))

  // Use `editIn` to edit a shallow clone of a subtree.
  editIn(draft).todos[0]((todo) => {
    todo.title = 'Do the dishes'
    todo.completed = false
  })
})

You can call setIn and friends on non-draft objects too, it will return a new state with the edit applied. This is useful if you only need to make one change at a time.

const nextState = setIn(state).user.name('Nicholas Cage')

Or if you only need to edit one level.

const nextState = editIn(state).todos[1]((todo) => {
  todo.completed = false
  todo.title = todo.title.toUpperCase() + '!!!'
})

Maps

Use .key(k) to drill into values inside a Map.

const state = {
  users: new Map([
    ['user1', { name: 'John', age: 30 }],
    ['user2', { name: 'Jane', age: 25 }],
  ]),
}

const nextState = setIn(state).users.key('user1').name('Wilberforce')

Freezing objects at development time

TypeScript should prevent unsafely mutating data within bedit's draft functions if your data is well-typed and you don't use as any ! But who knows what might happen later, in the outside world. Shit's crazy out there.

For extra peace of mind, call setDevMode(true) early in your application's boot process to freeze objects at development time.

This will cause errors to be thrown if you try to mutate an object that is supposed to be immutable.

import { setDevMode } from 'bedit'
if (process.env.NODE_ENV === 'development') {
  setDevMode(true)
}

Performance

bedit seems to be:

  • About 5x faster than immer's production mode.
  • About 3x faster than mutative (same API as immer but highly optimized)

The benchmarks could be more thorough so take this for what it's worth.

https://github.com/ds300/bedit/tree/main/bench

Limitations

  • 🩹 No support for patch generation/application.

  • 👭 Works only with data supported by structuredClone (So yes ✅ to Map, Set, plain objects, and arrays. And no ❌ to custom classes, objects with symbol keys or getters/setters, etc)

  • 🤷‍♂️ LLMs really do suck at using bedit. They get it if you point them at the README but otherwise they make a lot of mistakes (which is bad !!) !

  • It currently returns a new object even if an edit is ineffectual, e.g.

    const foo = { bar: 'baz' }
    const nextState = setIn(foo).bar('baz')
    newState !== foo // sadly true

    This could be fixed partially for certain usage patterns (PRs welcome).

API

edit

Edit a shallow clone of a value.

import { edit } from 'bedit'
const nextState = edit(
  { name: 'John', preferences: { theme: 'dark' } },
  (draft) => {
    // ✅ No type error, safe to mutate the top-level draft object
    draft.name = 'Jane'

    // ❌ Type error: `theme` is readonly
    draft.preferences.theme = 'light'

    // ✅ use `setIn(draft)` to mutate deeply and safely
    setIn(draft).preferences.theme('light')
  },
)
// nextState = {name: 'Jane', preferences: {theme: 'light'}}

setIn

Assign a value to a nested property.

import { setIn } from 'bedit'
const nextState = setIn({ a: { b: { c: 1 } } }).a.b.c(2)
// nextState = {a: {b: {c: 2}}}

updateIn

Get the previous value (without cloning it) and return a new version.

import { updateIn } from 'bedit'
const nextState = updateIn({ a: { b: { c: 1 } } }).a.b.c((c) => c + 4)
// nextState = {a: {b: {c: 5}}}

editIn

Edit a shallow clone of a subtree.

import { editIn } from 'bedit'
const nextState = editIn({ a: { b: { c: 1 } } }).a.b((b) => {
  b.c = 4
})
// nextState = {a: {b: {c: 4}}}

TypeScript will prevent you from making deep edits.

editIn({ a: { b: { c: 1 } } }).a((a) => {
  // ❌ Type error: `c` is readonly
  a.b.c = 3
})

All bedit functions can be used inside an editIn block. If you call them on the root 'draft' object, you don't need to reassign the result.

editIn({ a: { b: { c: 1 } } }).a((a) => {
  setIn(a).b.c(3)
})

To mutate the root object, you can just not specify a path.

editIn({ a: { b: { c: 1 } } })((obj) => {
  obj.a = { b: { c: 3 } }
})

Zustand Integration

bedit provides integration with Zustand stores. Simply beditify your store and use bedit functions directly:

import { beditify } from 'bedit/zustand'
import { setIn, updateIn } from 'bedit'
import { create } from 'zustand'

const useStore = create(() => ({
  count: 0,
  user: { name: 'John', theme: 'light' },
  todos: [],
}))

// Beditify the store to enable bedit functions
const store = beditify(useStore)

// Use bedit functions directly on the store
setIn(store).user.name('Jane')
updateIn(store).count((c) => c + 1)
updateIn(store).todos.push({ id: 1, text: 'Learn bedit' })

// Write your own helper functions as needed
const increment = (n: number) => {
  updateIn(store).count((c) => c + n)
}

const loadUser = async (userId: string) => {
  const user = await fetch(`/api/users/${userId}`).then((r) => r.json())
  setIn(store).user(user)
}

increment(5)
await loadUser('user123')

// Your original useStore hook still works as usual
function MyComponent() {
  const count = useStore((s) => s.count)
  return <div>{count}</div>
}

Custom State Container Integration

You can integrate bedit with any state container by implementing the BeditStateContainer interface. This allows bedit functions to work directly with your store.

See Custom State Container Integration docs for implementation details and examples.