@huukhanhnguyen/model / schema/nodes
schema/nodes
ArgumentNodeJSON
ArgumentNodeJSON =
ExpressionNodeJSON& {argName:string; }
ChildNode
ChildNode =
DataNodeHost|ContainerNodeHost|ParameterNodeHost|ExpressionNodeHost|BaseNodeHost
DirtyTarget
DirtyTarget = {
_dependents?:Iterable<DirtyTarget>;_epoch?:number;_hooks?:NodeHooks;isDirty?:boolean;parent?:DirtyTarget; }
_dependents?
optional_dependents?:Iterable<DirtyTarget>
_epoch?
optional_epoch?:number
_hooks?
optional_hooks?:NodeHooks
isDirty?
optionalisDirty?:boolean
parent?
optionalparent?:DirtyTarget
EntryHeader
EntryHeader =
Record<string,unknown> & {key:string; }
What a lane entry exposes without evaluating anything: its identity plus the plain header fields an index/schedule column can print.
EvaluateOutcome
EvaluateOutcome = {
value:unknown; } | {error:unknown; } | {superseded:true; }
How an evaluation attempt ended. Discriminated STRUCTURALLY ("error" in outcome) — there is deliberately no type tag, the same convention document/dataKind.ts states for the *Data stream: the kind is sniffed from the SHAPE. error carries the raw thrown value, not the message string BaseNode.error stringifies it into.
superseded is the third way an attempt ends: a newer dirty bumped the node's _epoch while an async compute was in flight, so the settle discards itself (core/maybeAsync.ts, DataNode's step epoch check). It produced no value and no error — but it ENDED, and an observer that started a timer on entry has to be able to stop it.
EvaluateSettle
EvaluateSettle = (
outcome,isAsync) =>void
Closes one evaluation attempt — see beginEvaluate (nodes/hooks.ts). Optional wherever it is threaded: a caller that did not open an attempt passes nothing, and then no afterEvaluate fires, because there was no beforeEvaluate to pair with.
Parameters
outcome
isAsync
boolean
Returns
void
ExpressionNodeJSON
ExpressionNodeJSON =
BaseObjectJSON& {expression:string; }
HookHost
HookHost = {
_hooks?:Hooks; }
Anything carrying a hook bag: a LIVE node, a plain DECLARATION object (an DataDefinition) hooks were attached to before any node existed, or the scene-tier host a renderer fires its frame transitions through.
_hooks?
optional_hooks?:Hooks
Hooks
Hooks =
NodeHooks&SceneHooks
Every point a hook bag can hold: the node-tier lifecycle (NodeHooks) plus the scene-tier mount pair (SceneHooks, schema/renderDiff.ts). One bag, two tiers — the key sets are disjoint, and addHook/callHook serve both.
KeyedNode
KeyedNode =
BaseNodeHost& {enabled:ArgumentNodeHost|null;key:string;label:ArgumentNodeHost; }
The shared surface both node kinds carry (see DataNode/ContainerNode). label is ALWAYS a live ArgumentNode (never null) — same "always constructed, default expression ''" contract ParameterNode.label uses — so the UI can always bind an editable field to it; hasLabelOf is the content-based check for "was this actually authored".
KeyedNodeEntry
KeyedNodeEntry = {
key:string;node:BaseNodeHost;useReferenceValue:boolean; }
One key reachable from an expression context, with the resolution strategy ExpressionNode._buildContext historically applied at that position: a top-level node always unwraps single-cardinality (resolveNodeValue); a nested operation resolves to its raw baked step output (referenceValue) when available, else falls back the same way. See NodeCollection.keyedNodes.
key
key:
string
node
node:
BaseNodeHost
useReferenceValue
useReferenceValue:
boolean
NodeHooks
NodeHooks = {
afterDirty?: (node) =>void;afterEvaluate?: (node,outcome,isAsync) =>void;afterRemove?: (node) =>void;beforeDirty?: (node,source) =>void;beforeEvaluate?: (node) =>void;beforeRemove?: (node) =>void;init?: (node) =>void; }
Node lifecycle observation points (see nodes/hooks.ts).
A hook OBSERVES — never DECIDES. Its return value is discarded: it cannot veto an evaluate, cannot rewrite the outcome, cannot stop dirty propagation. Everything that decides a node's VALUE stays a subclass override with exactly one owner (compute, postCompute, errorValue on BaseNode); if a composable hook could change a value, two systems would both have a claim on it and neither would be the answer.
PAIRING IS THE LAW: every before gets exactly one after, on every path, with no exception. That is why a FAILED evaluate reports through afterEvaluate carrying { error } rather than through a separate error point, and why an attempt discarded mid-flight still reports { superseded: true } — one attempt, one notification, whichever way it ended. beginEvaluate (nodes/hooks.ts) is what enforces it structurally.
The dirty pair is typed on DirtyTarget, not on a node: the dirty walk traverses an open structural shape and not every hop up the owner chain is a full BaseNode (see BaseNode.ts's markDirty).
init is the exception to the pairing law, and the only one: it has no before because there is no assembled node to hand over yet. It is also the only point with more than one firing site — assembly completes in several places (see BaseNode.finishInit), so it is guarded to fire once rather than pretending one line covers every class.
afterDirty?
optionalafterDirty?: (node) =>void
"This node is now dirty" — NOT "the cascade is complete". Both dirty points fire inside setDirty, so a markDirty cascade shows up as N pairs, one per node it touched. Firing after once at the end of the walk instead would leave every bare setDirty call (ContainerNode's child listeners) with a before and no after.
Parameters
node
Returns
void
afterEvaluate?
optionalafterEvaluate?: (node,outcome,isAsync) =>void
isAsync distinguishes the deferred settle from the inline one.
Parameters
node
outcome
isAsync
boolean
Returns
void
afterRemove?
optionalafterRemove?: (node) =>void
Parameters
node
Returns
void
beforeDirty?
optionalbeforeDirty?: (node,source) =>void
source is the node the dirty walk STARTED from — the same node when something dirtied it directly. The value is CAUSAL: it answers "this node is dirty BECAUSE that node changed".
Parameters
node
source
Returns
void
beforeEvaluate?
optionalbeforeEvaluate?: (node) =>void
Parameters
node
Returns
void
beforeRemove?
optionalbeforeRemove?: (node) =>void
Parameters
node
Returns
void
init?
optionalinit?: (node) =>void
The node has finished being ASSEMBLED — it has its full structure and its children. NOT "the constructor returned": for DataNode, ContainerNode and ParameterNode the constructor leaves assembly unfinished (args pushed, children attached, constraints applied afterwards), so each assembly path calls finishInit() at the point wiring is genuinely done. Fires at most once per node.
No before counterpart, and this is the one point that cannot have one: there is no assembled node to pass.
Parameters
node
Returns
void
ObjectChannel
ObjectChannel =
"content"|"transform"|"props"
Which half of an object moved — a renderer re-tessellates for "content", writes one matrix for "transform", repaints for "props".
ObjectChannelListener
ObjectChannelListener = (
key,channel) =>void
Parameters
key
string
channel
Returns
void
ObjectInstance
ObjectInstance = {
label?:string;layer?:string;material?:string;transformation:Transformation|null; }
The lifted placement of one scene object — what its whole content sits under, compiled from the object chain's TAIL instead of baked into geometry.
transformation is the product of the trailing move/rotate steps (null when the tail has none). The props are PLACEMENT DEFAULTS: the consumer applies each to a content document that carries none of its own — a document stamped inside the body always wins.
label?
optionallabel?:string
layer?
optionallayer?:string
material?
optionalmaterial?:string
transformation
transformation:
Transformation|null
ObjectSignature
ObjectSignature = {
content:string;props:string;transform:string; }
The three comparable halves of a resolved object — Model diffs these to pick a channel, and skips the emit entirely when all three match.
content
content:
string
props
props:
string
transform
transform:
string
OptionNodeJSON
OptionNodeJSON = {
input:string;label:string; }
input
input:
string
label
label:
string
ParameterNodeJSON
ParameterNodeJSON =
BaseObjectJSON& {animation?:AnimationBinding;args?:Argument[];flat?:boolean;key:string;label?:string;method:string; }