Select Cells provides minimal reactive primitives used by select/ui and
usable on their own. It focuses on explicit updates, path-based selection,
and lightweight pub/sub.
cell(value?, options?): Creates a mutable reactiveCell. Options:{ pending, schedule }wherescheduleis["defer"|"throttle"|"batch", delay], a shared utils/async scheduler, ornull.cells(value|object): Default export. Creates a single mutable reactiveCell, or a plain object of cells when given a plain object.cell.store(initial?)/cellStore(initial?): Root cell for store-mode UI trees (see bag vs store inref-ui.md). Same pattern ascell.derived.cell.store.map(shape)≡cells(shape).reconcile(target, value)/cell.reconcile(value): Diff-merge a plain tree onto a cell with batched path writes (bypasses schedule).browser(options?): Creates browser-backed cells forpath,query,hash,localStorage, and in-memory shared state.- Query and hash use the same hashformat serializer by default (
a=1,b=(2,3)).
- Query and hash use the same hashformat serializer by default (
deferred(value?, delay): Alias forcell(value, { schedule: ["defer", delay] })(debounced writes).derived(template, processor?, initial?): Creates a reactive derivation from a template containing cells.- Use
.updater(fn)on the returned derivation to forwardset(...)ormerge(...)writes into source cells.
- Use
effect(inputs, effector): Subscribes to all reactives ininputs, runseffector(expanded, path, origin), returns disposer.Cell(class): The base class for mutable reactive values.Selected(class): A reactive value linked to a specific path within a parent reactive value.Reactive(class): The abstract base class for all reactive types.
access(context, path, offset?): Safely reads a nested value from an object/array by path.assign(scope, path, value, merge?, offset?): Writes a nested value by path, creating intermediate objects/arrays as needed.undefinedpath entries pick the next free numeric slot in the current array or object, and create arrays when they need to materialize a missing container.walk(value, path?): Recursively iterates through a structure and yields[reactive, path]for every reactive value found.expand(value): Recursively resolves all reactive values within a structure to their plain values.
value: The current plain value of the reactive instance.revision: An integer that increments whenever the value changes.length: Returns the length of the underlying value if it is a collection.get(key?): Returns a child value by key, or the full value if no key is provided.map(functor): Returns a new array by applying the functor to each element of the underlying value.set(value, path?, force?): Updates the value (optionally at a specific path) and notifies subscribers.forcebypassesschedule.schedule(spec): Attaches/replaces/clears a write scheduler on aCell(construct-time or runtime).flush(): Runs the attached scheduler immediately (Cell).select(path): Returns aSelectedinstance linked to the specified path.sub(handler): Subscribes a handler to receive updates(value, path, origin).unsub(handler): Unsubscribes a previously registered handler.effect(handler): Subscribeshandlerand returns an idempotent unsubscriber callback.pub(value, path?, origin?): Manually publishes an update to all subscribers.merge(value): Merges or replaces the current value (CellandSelected).push(value): Appends a value to the underlying array (CellandSelected).refresh(): Re-evaluates the value from the source (SelectedandDerivationonly).dispose(): Releases resources for lifecycle-aware reactive instances (Selected,Cellschedule,Derivation).
- No write scheduler by default (
scheduleis opt-in); syncbatch()only coalesces pubs in one turn - Path-oriented updates and subscriptions are first-class
- Can be embedded in plain objects and arrays
- Small API surface designed for composition with Select/UI
import cell, { derived } from "@select/state/cells.js"
const count = cell(0)
const doubled = derived([count], (n) => n * 2)
count.sub((value) => {
console.log("count:", value, "doubled:", doubled.value)
})
count.set(1)
count.set(2)Write-through derivation example:
const count = cell(2)
const doubled = derived(count, (n) => n * 2).updater((value) => {
count.set(value / 2)
})
doubled.set(10)
console.log(count.value) // 5
console.log(doubled.value) // 10Multiple cell declaration example:
import cells from "@select/state/cells.js"
const { name, age } = cells({ name: "Ada", age: 37 })
name.set("Adele")
age.set(38)Store-mode root (app/tree state) example:
import cell from "@select/state/cells.js"
// One root cell for a whole document/tree (like cell.derived / cell.batch)
const state = cell.store({
logs: [],
filter: "all",
})
// Path write
state.set("warn", "filter")
// Full snapshot patch (structuredClone + edit, server payload, …)
state.reconcile({
logs: [{ type: "info", message: "hi" }],
filter: "warn",
})
// Fine-grained view
const logs = state.select("logs")
logs.push({ type: "error", message: "boom" })Pass the root cell into UI as top-level data so instances subscribe automatically:
Inspector.new().set({ value: state }).mount("#app")
// later:
state.reconcile(nextTree)Browser-backed state example:
import { browser } from "@select/state/browser.js"
const state = browser()
const sidebar = state.query.select("sidebar")
sidebar.set("open")
const prefs = state.local("prefs", { theme: "light" })
prefs.select("theme").set("dark")
const modal = state.internal("modal.open", false)
modal.set(true)Serializer note:
- Default query/hash format is hashformat (comma-separated atoms,
key=value, nested values in parentheses). - Legacy URLSearchParams-style query parsing (
a=1&b=2) is not supported by default. - Custom serializers can still be provided through
browser({ query, hash }). - See
browser.mdandref-browser.mdfor the browser-specific guide and API reference.
Path-based selection example:
const state = cell({ user: { profile: { name: "Ada" } } })
const name = state.select(["user", "profile", "name"])
name.sub((value) => console.log("name changed:", value))
state.set("Grace", ["user", "profile", "name"])Cells are designed to stay small. Prefer extension by composition:
wrap cell() and derived() in module-specific helpers for domain state.
import cell, { derived } from "@select/state/cells.js"
export const counter = (initial = 0) => {
const value = cell(initial)
const label = derived([value], (n) => `Count: ${n}`)
return { value, label }
}cell(value?, options?): Creates a mutable reactiveCell. Options:{ pending, schedule }.cells(value|object): Default export. Returns a single cell for non-object values, or an object of cells for plain-object input.browser(options?): Creates{ path, query, hash, local, internal }browser-backed reactive helpers.deferred(value?, delay): Alias forcell(value, { schedule: ["defer", delay] }).derived(template, processor?, initial?): Creates a reactive derivation from a template containing cells.walk(value, path?): Iterates through nested values and yields[reactive, path]entries.expand(value): Recursively expands reactive values to plain values.access(context, path, offset?): Reads a nested value fromcontextby path.assign(scope, path, value, merge?, offset?): Writes a nested value by path, creating intermediate containers as needed.Cell: Exported reactive class used for root mutable state.Selected: Exported reactive class used for path-based selections.Reactive: Base class for all reactive primitives.
reactive.sub(handler): Subscribes an update handler receiving(value, path, origin).reactive.unsub(handler): Unsubscribes a previously registered update handler.reactive.select(path): Creates aSelectedreactive value linked to a path in the parent value.reactive.pub(value, path?, origin?): Manually triggers an update notification.reactive.map(functor): Convenience helper to map over collection values.reactive.get(key?): Retrieves a value or child value.cell.set(value, path?, force?): Updates the cell value (optionally under a path) and publishes changes.forcebypassesschedule.cell.schedule(spec): Attaches/replaces/clears a write scheduler.cell.flush(): Runs the attached scheduler immediately.cell.merge(value): Merges arrays/objects, or replaces withvalue.cell.push(value): Appends a value to an underlying array cell.selected.refresh(): Re-evaluates the selected path against parent state and publishes updates.selected.set(value, path?, force?): Updates the parent cell through this selection.selected.merge(value): Merges arrays/objects at the selected path, or replaces withvalue.selected.push(value): Appends a value at the selected path (coercing non-arrays as needed).selected.dispose(): Unregisters this selection from its parent cell.derivation.refresh(): Forces re-computation of a derived value.derivation.updater(fn): Registers a single write-through handler used byderivation.set(...)andderivation.merge(...).derivation.unbind(): Unsubscribes from all source cells.derivation.dispose(): Unsubscribes from sources and clears derivation subscriptions/references.cell.dispose(): Cancels pending scheduled writes and detaches the schedule.