shelving/util/updatemodule

Updates<T> is the mutation language for Shelving data objects. An updates object uses encoded key prefixes to describe how each field should change — set, increment, or add/remove array items — and updateData() applies those changes immutably. This is the format consumed by the shelving/db layer when writing documents.

Things to know:

  • updateData() returns the same reference when no property actually changes, making it safe for equality checks and memoisation.
  • Nested fields are addressed with dot-path keys: "=user.name" targets data.user.name.
  • If a += or -= target field is not already a number, the increment value becomes the new value.
  • If a +[] target field is not already an array, the value(s) become the new array.
  • getUpdates() parses an Updates object into an array of structured Update objects — useful when you need to inspect or forward updates programmatically.

Usage

Key prefix syntax

Key syntaxAction
key or =keySet field to value
+=keyIncrement number
-=keyDecrement number
+[]keyAdd item(s) to array
-[]keyRemove item(s) from array

Use plain key for most sets. Use =key when TypeScript inference gets confused by deeply-nested paths — the = prefix forces leaf-only matching and is more precise.

Applying updates

ts
import { updateData } from "shelving/util";

const doc = { title: "Draft", count: 5, tags: ["a", "b"] };

updateData(doc, { title: "Published" });        // { title: "Published", count: 5, tags: [...] }
updateData(doc, { "=title": "Final" });         // same effect, more precise
updateData(doc, { "+=count": 3 });              // { ..., count: 8, ... }
updateData(doc, { "-=count": 1 });              // { ..., count: 4, ... }
updateData(doc, { "+[]tags": "c" });            // { ..., tags: ["a", "b", "c"] }
updateData(doc, { "-[]tags": "a" });            // { ..., tags: ["b"] }
updateData(doc, { "+[]tags": ["c", "d"] });     // add multiple at once

Nested paths

ts
import { updateData } from "shelving/util";

const doc = { user: { name: "Alice", score: 10 } };

updateData(doc, { "=user.name": "Bob" });       // { user: { name: "Bob", score: 10 } }
updateData(doc, { "+=user.score": 5 });         // { user: { name: "Alice", score: 15 } }

Inspecting updates

ts
import { getUpdates } from "shelving/util";

getUpdates({ "+=count": 1, "-[]tags": "old" });
// [
//   { action: "sum",  key: ["count"], value: 1 },
//   { action: "omit", key: ["tags"],  value: ["old"] },
// ]

Functions

Go

getUpdates()function

Decode the prop updates in an Updates object into a set of Update objects.

getUpdates(data: Updates<T>): ImmutableArray<Update>
Go

updateData()function

Return a copy of a data object with a set of updates applied.

updateData(data: T, updates: Updates<T>): T

Types

Go

Updatestype

Set of named updates for a data object, keyed by encoded update syntax.

{
	/**
	 * Set update (all branches)
	 * - Can set `a` and `a.a1` in `{ a: { a1: 123 } }`
	 * - Sometimes inference gets confused, if that happens use `=` syntax instead.
	 */
	readonly [K in BranchDataPath<T> as `${K}`]?: BranchData<T>[K] | undefined;
} & {
	/**
	 * Set update (leaves only)
	 * - Can set `a.a1` in `{ a: { a1: 123 } }`, but cannot set `a`
	 * - Deeply-nested properties don't always infer when leaves and branches are combined.
	 * - This syntax is more exact and will infer better.
	 */
	readonly [K in LeafDataPath<T> as `=${K}`]?: LeafData<T>[K] | undefined;
} & {
	/**
	 * Sum update.
	 * - Increment/decrement numbers.
	 */
	readonly [K in LeafDataPath<T> as `+=${K}` | `-=${K}`]?: LeafData<T>[K] extends number ? LeafData<T>[K] | undefined : never;
} & {
	/**
	 * With/omit update.
	 * - Add or remove items from arrays.
	 */
	readonly [K in LeafDataPath<T> as `+[]${K}` | `-[]${K}`]?: LeafData<T>[K] extends ImmutableArray<unknown>
		? LeafData<T>[K] | LeafData<T>[K][number] | undefined
		: never;
}
Go

Updatetype

A single decoded update to a keyed property in an object.

| { action: "set"; key: Segments; value: unknown } //
	| { action: "with"; key: Segments; value: ImmutableArray<unknown> } //
	| { action: "omit"; key: Segments; value: ImmutableArray<unknown> } //
	| { action: "sum"; key: Segments; value: number }