
immutable update utils for JS Object and Arrays.
This library only does simple updates (e.g setting, modifying or deleting a value), for more powerful combinators, see space-lift which also fully includes immupdate.
Pros:
Cons:
Here's everything that can be imported from immupdate:
import { update, deepUpdate, DELETE } from 'immupdate'
update updates the shallow properties of an object
deepUpdate can update one arbitrarily nested property in a JSON tree
DELETE is a special marker used with update and immupdate to delete a property
import { update, DELETE } from 'immupdate'
type Person = { id: number, name: string, tatoo?: string }
const jose: Person = {
id: 33,
name: 'Jose',
tatoo: '自由'
}
const carla = update(jose, {
name: 'Carla',
tatoo: DELETE
})
The differences with Object.assign or the { ...source } notation is that you can delete a property and update() is typesafe: You can't make a typo, add completely new keys, etc.
Two ways to do it:
1) update performs shallow updates, so we will just have to call it multiple times.
import { update } from 'immupdate'
interface Person {
id: string
prefs: {
csvSep: ',' | ';'
timezone: number
otherData?: {
nestedData: {}
}
},
friends: number[]
}
const person = {
id: 33,
prefs: {
csvSep: ',',
timezone: 2,
otherData: {
nestedData: {}
}
},
friends: [1, 2, 3]
}
const newPrefs = update(person.prefs, { csvSep: ';' })
const personWithNewPrefs = update(person, { prefs: newPrefs })
2) but this can get tedious pretty fast, especially once we reach 3 levels of nesting or more. deepUpdate to the rescue:
import { deepUpdate } from 'immupdate'
const personWithNewPrefs = deepUpdate(person)
.at('prefs')
.at('csvSep')
.set(';')
This is fonctionally equivalent to the code using multiple update calls.
person was only updated where necessary. Below in green are the paths that were updated. This is much more efficient than deep cloning.

Two choices:
1) Using update which doesn't specifically deal with Arrays, so we want to prepare the updated Array instance beforehand.
import { update } from 'immupdate'
const person = {
friends: [
{ id: 1, name: 'biloute' },
{ id: 2, name: 'roberto' },
{ id: 3, name: 'jesus' }
]
}
const newFriends = person.friends.map(f => f.id === 3 ? update(f, { name: 'rocky' }) : f)
const newPerson = update(person, { friends: newFriends })
2) Using deepUpdate:
import { deepUpdate } from 'immupdate'
const newPerson = deepUpdate(person)
.at('friends')
.at(person.friends.findIndex(f => f.id === 3))
.abortIfUndef()
.at('name')
.set('rocky')
import { deepUpdate } from 'immupdate'
const person = {
friends: [
{ id: 1, name: 'biloute' },
{ id: 2, name: 'roberto' },
{ id: 3, name: 'jesus' }
]
}
const newPerson = deepUpdate(person)
.at('friends')
.at(person.friends.findIndex(f => f.id === 3))
.abortIfUndef()
.at('name')
.modify(name => `MC ${name}`)
If there is a nullable key (or An array index being used) anywhere in the update path and it's not the last key (i.e, right before modify or set)
then it's a potentially unsafe operation and the library will ask you to decide what to do with it (it won't compile until you do).
You can either:
1) Abort the whole thing (the returned value is the same as the passed one)
import { deepUpdate } from 'immupdate'
interface Person {
prefs?: {
lang: string
}
}
deepUpdate<Person>({})
.at('prefs')
.abortIfUndef()
.at('lang')
.set('en')
Or
2) Specify a default value to be used if the current value is null/undefined
import { deepUpdate } from 'immupdate'
interface Person {
prefs?: {
lang: string
}
}
const defaultPrefs = { lang: 'en' }
deepUpdate<Person>({})
.at('prefs')
.withDefault(defaultPrefs)
.at('lang')
.set('en')
import { deepUpdate } from 'immupdate'
type A = { type: 'a', data: string }
type B = { type: 'b', data: number }
type Container = { aOrB: A | B }
const isA = (u: A | B): u is A => u.type === 'a'
const container = { aOrB: { type: 'a', data: 'aa' } }
deepUpdate(container)
.at('aOrB')
.abortIfNot(isA)
.at('data')
.set('bb')
Additionally, if you're also using space-lift, you can update Option values anywhere in a tree. Updating a Option<T> work exactly like a updating T | undefined normally would so you still have to explicitely tell deepUpdate what to do in case it encounters a None:
const obj = {
a: Some({
b: Some({ c: 1 }),
x: { y: 'y' }
})
}
const result = deepUpdate(obj)
.at('a')
.abortIfUndef()
.at('b')
.withDefault({ c: 5 })
.at('c')
.set(10)
$ claude mcp add immupdate \
-- python -m otcore.mcp_server <graph>