The extension points
The three ops
Section titled “The three ops”A store gets its reactive semantics from its NodeOps. The engine uses them for each write, each epoch and each reference.
| Op | Signature | Default |
|---|---|---|
splash |
(value: Optional<unknown>, target, store) => boolean |
Write the value unless it is structurally equal. Give true for a change |
flow |
(target, store) => ReadonlySet<NodeId> |
The value readers of the target |
deref |
(root, path, store) => Optional<unknown> |
Go through slots, then through the own fields of the value |
The contracts
Section titled “The contracts”splashis the only place that writesnode.value. It receivesnonefor an expression without a result. A change from a value tononeis a change.flowgives the nodes that a change of the target makes dirty. The epoch sorts its closure, thus the readers evaluate after their inputs.- A custom
derefmust resolve through slots like the default op. The engine finds the readers of a node from the slots, thus a different resolution can miss a change. - An op can write to the store. The write does not go into the current epoch: the flush does it in a follow-up epoch. After 1000 follow-ups, the flush throws an error.
Class methods
Section titled “Class methods”classNodeOps(b, fallback?) resolves the three ops from the class that owns each node. A node belongs to the nearest instance up its slot tree. The class methods splash, flow and deref resolve through the extends chain, and Top gives the defaults.
registerClass(b, componentClass("Clamped", { x: { expr: lit(0) } }, undefined, { splash: (value, target, store) => defaultOps.splash( value.tag === "some" ? { tag: "some", value: Math.min(Number(value.value), 10) } : value, target, store, ),}));Each cell of an instance of Clamped keeps its value at 10 or less. A new registration of the class changes the semantics of its live instances.