@huukhanhnguyen/model / nodes/CollectionNode
nodes/CollectionNode
CollectionNode
One lane's ordered key list.
Extends
LaneNode<readonlystring[]>
Constructors
Constructor
new CollectionNode(
scope,lane,read):CollectionNode
Parameters
scope
unknown
lane
string
read
() => readonly EntryHeader[]
Returns
Overrides
_dependents
_dependents:
Set<BaseNode>
Inherited from
_epoch
_epoch:
number=0
Bumped on every dirty (see setDirty/markDirty) — a stale async settle compares its captured epoch against the current one and discards itself on mismatch (a newer edit superseded it while it was in flight).
Inherited from
_hooks?
optional_hooks?:NodeHooks
Lifecycle observers attached to THIS node — see nodes/hooks.ts. Also carried by an DataDefinition, the declaration surface a node's hooks can be attached to before the node exists.
Inherited from
_reads
_reads:
Set<BaseNode>
Reverse index of nodes this node currently reads (static expression refs + dynamic dependOn edges). Cleared at the start of every dirty recompute and on dispose so a dropped Table.get/sampleTexture edge stops cascading markDirty (and a disposed node is not retained).
Inherited from
attributes
attributes:
Record<string,unknown> ={}
Inherited from
collection
collection:
BaseCollection
Inherited from
error?
optionalerror?:string=""
Last error message; empty string when valid, undefined after a successful eval.
Inherited from
id
id:
string
Inherited from
isDirty
isDirty:
boolean=true
True when the node needs re-evaluation before its value can be trusted.
Inherited from
lane
readonlylane:string
parent?
optionalparent?:any
Inherited from
pending
pending:
boolean=false
True while an async compute() is in flight (its result hasn't settled yet).
Inherited from
sourceId
sourceId:
string=""
Inherited from
value
value:
unknown=undefined
Cached evaluated value; undefined when the node is in an error state.
Inherited from
Accessors
dependents
Get Signature
get dependents():
Set<BaseNode>
Returns
Set<BaseNode>
Inherited from
errorLogPrefix
Get Signature
get
protectederrorLogPrefix():string
console.error prefix on a compute failure — override to match a subclass's existing log message (includes node-specific context).
Returns
string
Inherited from
model
Get Signature
get model():
any
Returns
any
Inherited from
owner
Get Signature
get owner():
any
Walk up to the nearest owner that has a nodes collection (ModelScope).
Returns
any
Inherited from
resolving
Get Signature
get resolving():
boolean
True while THIS instance is mid-track/pull — Model.ts's root-fold re-entrancy guard reads this on the shared SceneNode so a whole-scene consumer re-entering the fold mid-walk fails with a named cycle.
Returns
boolean
Inherited from
symbol
Get Signature
get symbol():
string
Returns
string
Inherited from
valueType
Get Signature
get valueType():
string
Returns
string
Inherited from
Methods
_commit()
protected_commit(raw,isAsync,settle?):void
Protected (not private): ExpressionNode/ContainerNode's evaluate() overrides call this directly to reuse the exact same commit path for a volatile step that bypasses the isDirty gate.
Parameters
raw
unknown
isAsync
boolean
settle?
Returns
void
Inherited from
_dispose()
_dispose():
void
Returns
void
Inherited from
_fail()
protected_fail(err,isAsync,settle?):void
Protected (not private) — see _commit's doc.
Parameters
err
unknown
isAsync
boolean
settle?
Returns
void
Inherited from
addListener()
addListener(
event,listener): () =>void
Parameters
event
string
listener
Returns
() => void
Inherited from
compute()
compute():
unknown
Compute the raw value for this node; override in subclasses to produce actual output.
Returns
unknown
Inherited from
copy()
copy():
any
Duplicate this node in its collection, inserting it right after the original.
Returns
any
Inherited from
display()
display(
listener?):string
Return the human-readable display string for this node's value (or its error message).
Parameters
listener?
() => void
Returns
string
Inherited from
entry()
entry(
key):EntryHeader|undefined
One entry's header, as its own dependency. Reading this does NOT register a membership edge — a consumer that only ever asks about sheet "A-101" is not dirtied when sheet "A-102" is added.
Parameters
key
string
Returns
EntryHeader | undefined
errorValue()
protectederrorValue():unknown
Value to fall back to when compute()/postCompute() throws. Override for a node whose "empty" isn't undefined (ContainerNode: []).
Returns
unknown
Inherited from
evaluate()
evaluate(
listener?):unknown
Evaluate this node, cache the result, and notify dependents; returns the cached value. When compute() returns a thenable, this returns the prior (stale) value synchronously and settles later — see core/maybeAsync.ts.
Parameters
listener?
() => void
Returns
unknown
Inherited from
finishInit()
finishInit():
void
Assembly is complete: this node has its full structure and its children. Every assembly path calls it at the point wiring is genuinely done, which is NOT one line:
- the classes that take everything as constructor parameters (the LaneNode family, ExpressionNode, OptionNode, ArgumentNode) call it at the end of their own constructor;
DataNodecalls it after its args are pushed —fromRegistryandcopy, never its constructor, which sets only method+definition;ContainerNodeafter its children are attached,ParameterNodeafter its args are wired (NodeCollection: createFromJSON, addContainer, addEntry).
Guarded to fire at most once. That is what makes NESTED assembly safe — createFromJSON delegates to fromRegistry, and one node must see one init, not two — and makes a redundant call harmless rather than wrong.
Returns
void
Inherited from
getError()
getError(
listener?):string|undefined
Parameters
listener?
() => void
Returns
string | undefined
Inherited from
hasSourceChanged()
protectedhasSourceChanged(source):boolean
True when source differs (by content) from what was last recorded via snapshotSource. Model's blanket "some lane was touched" handler (_invalidateLaneNodes) calls this (through each subclass's own hasChanged()) to dirty PRECISELY instead of coarsely: diffing one record/chain's own (small) JSON is cheap, unlike the whole-lane JSON.stringify hash the old _texturesSignature paid on every resolve of every chain. A node that's never resolved (fresh, isDirty already true) doesn't need this at all — it recomputes on its own regardless.
Parameters
source
unknown
Returns
boolean
Inherited from
headers()
headers():
EntryHeader[]
Every entry's header, in order — membership edge PLUS one per-entry edge each. The shape an index table wants.
Returns
isPending()
isPending(
listener?):boolean
True while an async compute() is still settling (see pending).
Parameters
listener?
() => void
Returns
boolean
Inherited from
isValid()
isValid(
listener?):boolean
Parameters
listener?
() => void
Returns
boolean
Inherited from
keys()
keys(): readonly
string[]
Ordered keys. Order is part of the value — a reorder is an ordinary edit and dirties readers, which is what a drawing index needs.
Returns
readonly string[]
notify()
notify(
event, ...args):void
Parameters
event
string
args
...unknown[]
Returns
void
Inherited from
postCompute()
protectedpostCompute(raw):unknown
Post-process compute()'s raw result into this.value; throw to signal a failure (caught by evaluate()'s template, same as a compute() throw). Override for a node whose value isn't a sanitizer-typed scalar: ContainerNode returns the data list as-is; ArgumentNode routes through the domain coercion pipeline (nodeMethods/parameter.ts + data/ asset resolution) instead of convert/validate.
Parameters
raw
unknown
Returns
unknown
Inherited from
pull()
protectedpull(compute): readonlystring[]
Single cached-value convenience over track — the common case (one value per node, dirtied wholesale on setDirty/markDirty). A failed compute leaves isDirty untouched (still true, or whatever it was) so the NEXT resolve retries fresh — matching the pre-existing "throws every time until fixed" contract these lane nodes replace.
Parameters
compute
() => readonly string[]
Returns
readonly string[]
Inherited from
remove()
remove():
void
The removal door. BaseObject.remove() detaches from the collection and notifies "remove"; the pair brackets that, so beforeRemove still sees the node in its collection with its read edges intact.
afterRemove means "AFTER THE NODE LEFT ITS COLLECTION" — it does NOT promise the node's read edges are gone. Deliberately does not retractReads(): removing a node does not retract its edges today, only the disposal call sites do (tables.ts), and adding it here would be a behaviour change rather than an observation point.
Not anchored at retractReads() — that runs on EVERY dirty recompute (3 of its 6 call sites: evaluate, LaneNode.pull, evaluateStep), so hooks there would report a live node as removed on every evaluate.
Returns
void
Inherited from
removeListener()
removeListener(
event,listener):void
Parameters
event
string
listener
Returns
void
Inherited from
retractReads()
retractReads():
void
Drop every reverse read edge recorded in _reads.
Returns
void
Inherited from
revalidate()
revalidate():
void
Re-check membership and every header against what was last read, and dirty the readers of whatever moved. Called at a public pull entry (Model._syncCollections), NOT by a subscription.
It has to exist because "validate on read" only fires if the reader actually reads: a table's ChainNode returns its cached value without calling compute at all, so nothing would ever reach the collection again. This is the same verify step a pull-based incremental system (Salsa, Adapton) runs before trusting a memo — cheap here because a lane's key list and a two-field header are small, and because it never touches a body.
Returns
void
setError()
setError(
message):void
Set an error message on this node and notify listeners. Fires "error" (specific) and "change" (the universal event — see ParameterNode.setKey's doc).
Parameters
message
string
Returns
void
Inherited from
snapshotSource()
protectedsnapshotSource(source):void
Record a content snapshot of source (the record/chain JSON THIS node was built from — never the computed value, which can be large/ non-JSON, e.g. a texture's pixel buffer) — call once per successful resolve, right after reading the source.
Parameters
source
unknown
Returns
void
Inherited from
toJSON()
toJSON():
BaseObjectJSON
Returns
Inherited from
track()
protectedtrack<R>(compute):R
Cycle + read-tracking wrapper around compute — always registers dependOn(this) first (harmless when nothing is currently evaluating) and throws instead of recursing when THIS instance is already mid-resolve on the current call stack (A reads B reads A, any number of hops) — the guard replacing the old per-lane bespoke ones (Model._resolvingTables and friends) for the graph-integrated path. Does NOT itself decide caching — a subclass with one cached value uses pull; one that caches per call-arguments (ChainNode's resolveWith) calls this directly and manages its own memo.
Type Parameters
R
R
Parameters
compute
() => R
Returns
R
Inherited from
EntryNode
One lane entry's header. Value = the header record; changing it dirties whoever read THIS entry, not the lane.
Extends
Constructors
Constructor
new EntryNode(
scope,key,read):EntryNode
Parameters
scope
unknown
key
string
read
() => EntryHeader | undefined
Returns
Overrides
_dependents
_dependents:
Set<BaseNode>
Inherited from
_epoch
_epoch:
number=0
Bumped on every dirty (see setDirty/markDirty) — a stale async settle compares its captured epoch against the current one and discards itself on mismatch (a newer edit superseded it while it was in flight).
Inherited from
_hooks?
optional_hooks?:NodeHooks
Lifecycle observers attached to THIS node — see nodes/hooks.ts. Also carried by an DataDefinition, the declaration surface a node's hooks can be attached to before the node exists.
Inherited from
_reads
_reads:
Set<BaseNode>
Reverse index of nodes this node currently reads (static expression refs + dynamic dependOn edges). Cleared at the start of every dirty recompute and on dispose so a dropped Table.get/sampleTexture edge stops cascading markDirty (and a disposed node is not retained).
Inherited from
attributes
attributes:
Record<string,unknown> ={}
Inherited from
collection
collection:
BaseCollection
Inherited from
error?
optionalerror?:string=""
Last error message; empty string when valid, undefined after a successful eval.
Inherited from
id
id:
string
Inherited from
isDirty
isDirty:
boolean=true
True when the node needs re-evaluation before its value can be trusted.
Inherited from
key
readonlykey:string
parent?
optionalparent?:any
Inherited from
pending
pending:
boolean=false
True while an async compute() is in flight (its result hasn't settled yet).
Inherited from
sourceId
sourceId:
string=""
Inherited from
value
value:
unknown=undefined
Cached evaluated value; undefined when the node is in an error state.
Inherited from
Accessors
dependents
Get Signature
get dependents():
Set<BaseNode>
Returns
Set<BaseNode>
Inherited from
errorLogPrefix
Get Signature
get
protectederrorLogPrefix():string
console.error prefix on a compute failure — override to match a subclass's existing log message (includes node-specific context).
Returns
string
Inherited from
model
Get Signature
get model():
any
Returns
any
Inherited from
owner
Get Signature
get owner():
any
Walk up to the nearest owner that has a nodes collection (ModelScope).
Returns
any
Inherited from
resolving
Get Signature
get resolving():
boolean
True while THIS instance is mid-track/pull — Model.ts's root-fold re-entrancy guard reads this on the shared SceneNode so a whole-scene consumer re-entering the fold mid-walk fails with a named cycle.
Returns
boolean
Inherited from
symbol
Get Signature
get symbol():
string
Returns
string
Inherited from
valueType
Get Signature
get valueType():
string
Returns
string
Inherited from
Methods
_commit()
protected_commit(raw,isAsync,settle?):void
Protected (not private): ExpressionNode/ContainerNode's evaluate() overrides call this directly to reuse the exact same commit path for a volatile step that bypasses the isDirty gate.
Parameters
raw
unknown
isAsync
boolean
settle?
Returns
void
Inherited from
_dispose()
_dispose():
void
Returns
void
Inherited from
_fail()
protected_fail(err,isAsync,settle?):void
Protected (not private) — see _commit's doc.
Parameters
err
unknown
isAsync
boolean
settle?
Returns
void
Inherited from
addListener()
addListener(
event,listener): () =>void
Parameters
event
string
listener
Returns
() => void
Inherited from
compute()
compute():
unknown
Compute the raw value for this node; override in subclasses to produce actual output.
Returns
unknown
Inherited from
copy()
copy():
any
Duplicate this node in its collection, inserting it right after the original.
Returns
any
Inherited from
display()
display(
listener?):string
Return the human-readable display string for this node's value (or its error message).
Parameters
listener?
() => void
Returns
string
Inherited from
errorValue()
protectederrorValue():unknown
Value to fall back to when compute()/postCompute() throws. Override for a node whose "empty" isn't undefined (ContainerNode: []).
Returns
unknown
Inherited from
evaluate()
evaluate(
listener?):unknown
Evaluate this node, cache the result, and notify dependents; returns the cached value. When compute() returns a thenable, this returns the prior (stale) value synchronously and settles later — see core/maybeAsync.ts.
Parameters
listener?
() => void
Returns
unknown
Inherited from
finishInit()
finishInit():
void
Assembly is complete: this node has its full structure and its children. Every assembly path calls it at the point wiring is genuinely done, which is NOT one line:
- the classes that take everything as constructor parameters (the LaneNode family, ExpressionNode, OptionNode, ArgumentNode) call it at the end of their own constructor;
DataNodecalls it after its args are pushed —fromRegistryandcopy, never its constructor, which sets only method+definition;ContainerNodeafter its children are attached,ParameterNodeafter its args are wired (NodeCollection: createFromJSON, addContainer, addEntry).
Guarded to fire at most once. That is what makes NESTED assembly safe — createFromJSON delegates to fromRegistry, and one node must see one init, not two — and makes a redundant call harmless rather than wrong.
Returns
void
Inherited from
getError()
getError(
listener?):string|undefined
Parameters
listener?
() => void
Returns
string | undefined
Inherited from
hasSourceChanged()
protectedhasSourceChanged(source):boolean
True when source differs (by content) from what was last recorded via snapshotSource. Model's blanket "some lane was touched" handler (_invalidateLaneNodes) calls this (through each subclass's own hasChanged()) to dirty PRECISELY instead of coarsely: diffing one record/chain's own (small) JSON is cheap, unlike the whole-lane JSON.stringify hash the old _texturesSignature paid on every resolve of every chain. A node that's never resolved (fresh, isDirty already true) doesn't need this at all — it recomputes on its own regardless.
Parameters
source
unknown
Returns
boolean
Inherited from
header()
header():
EntryHeader|undefined
The entry's header fields. Returns undefined once the entry is gone — a reader that held a key across a delete gets a miss, not a throw (the collection it came from has already dirtied that reader).
Returns
EntryHeader | undefined
isPending()
isPending(
listener?):boolean
True while an async compute() is still settling (see pending).
Parameters
listener?
() => void
Returns
boolean
Inherited from
isValid()
isValid(
listener?):boolean
Parameters
listener?
() => void
Returns
boolean
Inherited from
notify()
notify(
event, ...args):void
Parameters
event
string
args
...unknown[]
Returns
void
Inherited from
postCompute()
protectedpostCompute(raw):unknown
Post-process compute()'s raw result into this.value; throw to signal a failure (caught by evaluate()'s template, same as a compute() throw). Override for a node whose value isn't a sanitizer-typed scalar: ContainerNode returns the data list as-is; ArgumentNode routes through the domain coercion pipeline (nodeMethods/parameter.ts + data/ asset resolution) instead of convert/validate.
Parameters
raw
unknown
Returns
unknown
Inherited from
pull()
protectedpull(compute):EntryHeader
Single cached-value convenience over track — the common case (one value per node, dirtied wholesale on setDirty/markDirty). A failed compute leaves isDirty untouched (still true, or whatever it was) so the NEXT resolve retries fresh — matching the pre-existing "throws every time until fixed" contract these lane nodes replace.
Parameters
compute
() => EntryHeader
Returns
Inherited from
remove()
remove():
void
The removal door. BaseObject.remove() detaches from the collection and notifies "remove"; the pair brackets that, so beforeRemove still sees the node in its collection with its read edges intact.
afterRemove means "AFTER THE NODE LEFT ITS COLLECTION" — it does NOT promise the node's read edges are gone. Deliberately does not retractReads(): removing a node does not retract its edges today, only the disposal call sites do (tables.ts), and adding it here would be a behaviour change rather than an observation point.
Not anchored at retractReads() — that runs on EVERY dirty recompute (3 of its 6 call sites: evaluate, LaneNode.pull, evaluateStep), so hooks there would report a live node as removed on every evaluate.
Returns
void
Inherited from
removeListener()
removeListener(
event,listener):void
Parameters
event
string
listener
Returns
void
Inherited from
retractReads()
retractReads():
void
Drop every reverse read edge recorded in _reads.
Returns
void
Inherited from
setError()
setError(
message):void
Set an error message on this node and notify listeners. Fires "error" (specific) and "change" (the universal event — see ParameterNode.setKey's doc).
Parameters
message
string
Returns
void
Inherited from
snapshotSource()
protectedsnapshotSource(source):void
Record a content snapshot of source (the record/chain JSON THIS node was built from — never the computed value, which can be large/ non-JSON, e.g. a texture's pixel buffer) — call once per successful resolve, right after reading the source.
Parameters
source
unknown
Returns
void
Inherited from
toJSON()
toJSON():
BaseObjectJSON
Returns
Inherited from
track()
protectedtrack<R>(compute):R
Cycle + read-tracking wrapper around compute — always registers dependOn(this) first (harmless when nothing is currently evaluating) and throws instead of recursing when THIS instance is already mid-resolve on the current call stack (A reads B reads A, any number of hops) — the guard replacing the old per-lane bespoke ones (Model._resolvingTables and friends) for the graph-integrated path. Does NOT itself decide caching — a subclass with one cached value uses pull; one that caches per call-arguments (ChainNode's resolveWith) calls this directly and manages its own memo.
Type Parameters
R
R
Parameters
compute
() => R
Returns
R