The evaluation scope handed to template expressions, computed values and action bodies.
What was wrong with building it as an object
The scope used to be assembled with a spread:
const scope = { state: this.state, ...this.bridges, ...this.state, ...methods, … };
...this.state reads every key on the reactive proxy, eagerly, every time a
scope is built — which is once per render, per action call and per computed
evaluation. Three separate defects followed from that one line.
Bare identifiers were not reactive. The documented computed form
<computed value="count * 2" /> reads count out of the snapshot the
spread produced, not out of the proxy, so no dependency was registered for
the expression itself. state.count * 2 worked; the form in the README,
quickstart and state-management guide did not, and failed silently — the
first render was correct and the value then never changed again.
Computed values reported false cycles. Spreading the state proxy reads
the computed key currently being evaluated. That re-entered the computed's
own watcher mid-collection, tripping the re-entrancy guard: every app using
computed properties logged AVX_R04 Circular dependency detected on every
re-render, for a cycle that did not exist. Worse, the re-entrant
watcher.evaluate() reset the watcher's dependency set part-way through
collection, which is what actually lost the count dependency above. The
bogus warning and the missing dependency were the same event.
Reactivity was component-granular. Because the spread touched every key, the render watcher depended on all of them. Changing a key no expression mentioned still re-rendered the component, so the per-key precision the Proxy layer computes was discarded one level above it.
What this does instead
Nothing is read until an expression actually names it. The scope is a Proxy
over ordered layers; a get resolves through them and, for a state or
computed key, reads the live reactive proxy — inside whichever watcher is
currently evaluating. So count in count * 2 registers a dependency on
count and on nothing else.
Derivation instead of spreading
Several call sites layer extra names onto a scope — a <@for> loop variable,
an event, an action's args. Written as { ...scope, index } that would
reintroduce the eager read this module exists to remove, so scopes derive
instead: deriveScope pushes another layer without materialising
anything. It falls back to a spread for plain objects, so external code that
passes an ordinary object still works.
- Source:
Members
(static, constant) IS_REACTIVE_SCOPE :symbol
Marks a value as one of this module's scope proxies.
Type:
- symbol
- Source:
(static, constant) SCOPE_DERIVE :symbol
Derives a child scope from a scope proxy.
Type:
- symbol
- Source:
Methods
(static) createReactiveScope(layers) → {object}
Creates the evaluation scope.
Layers are given in precedence order, highest first: the first layer that binds a name wins. That mirrors the object-literal precedence the previous implementation had, where later spreads overwrote earlier ones.
Parameters:
| Name | Type | Description |
|---|---|---|
layers |
Array:.<ScopeLayer:> | The layers, highest precedence first. |
- Source:
Returns:
A scope proxy.
- Type
- object
(static) deriveScope(scope, extras) → {object}
Layers extra bindings on top of an existing scope.
Prefer this over { ...scope, extra } anywhere a scope gains names. A spread
of a scope proxy reads every key it can enumerate, which is exactly the eager
read this module removes.
Parameters:
| Name | Type | Description |
|---|---|---|
scope |
object | The scope to extend. May be a scope proxy or a plain object. |
extras |
object | The bindings to layer on top. |
- Source:
Returns:
A scope with the extras applied.
- Type
- object
(static) getterLayer(getters) → {ScopeLayer}
Builds a layer of lazily evaluated bindings.
Used for resources, whose value is produced by calling read() and must not
be produced merely because a scope was built.
Parameters:
| Name | Type | Description |
|---|---|---|
getters |
Object:.<string:, function(): any:> | Name to getter. |
- Source:
Returns:
The layer.
- Type
- ScopeLayer
(static) isReactiveScope(value) → {boolean}
Reports whether a value is a scope proxy created here.
Parameters:
| Name | Type | Description |
|---|---|---|
value |
any | The value to test. |
- Source:
Returns:
True for a reactive scope proxy.
- Type
- boolean
(static) objectLayer(source) → {ScopeLayer}
Builds a layer over a plain object.
Parameters:
| Name | Type | Description |
|---|---|---|
source |
object | The object to expose. |
- Source:
Returns:
The layer.
- Type
- ScopeLayer
(static) stateLayer(state, listKeys) → {ScopeLayer}
Builds a layer over the component's reactive state.
The names are known without reading anything: they come from the raw target and the compiler's computed list. A value is read from the proxy only when an expression asks for it, which is what makes the read land inside the watcher that is currently evaluating.
Parameters:
| Name | Type | Description |
|---|---|---|
state |
object | The reactive state proxy. |
listKeys |
function | Returns the bound names. |
- Source:
Returns:
The layer.
- Type
- ScopeLayer
(inner) get(key) → {any}
Parameters:
| Name | Type | Description |
|---|---|---|
key |
string | The name to read. |
- Source:
Returns:
The bound value.
- Type
- any
(inner) get(key) → {any}
Parameters:
| Name | Type | Description |
|---|---|---|
key |
string | The name to read. |
- Source:
Returns:
The value, read through the reactive proxy.
- Type
- any
(inner) get(key) → {any}
Parameters:
| Name | Type | Description |
|---|---|---|
key |
string | The name to read. |
- Source:
Returns:
The getter's result.
- Type
- any
(inner) has(key) → {boolean}
Parameters:
| Name | Type | Description |
|---|---|---|
key |
string | The name to test. |
- Source:
Returns:
Whether the object binds it.
- Type
- boolean
(inner) has(key) → {boolean}
Parameters:
| Name | Type | Description |
|---|---|---|
key |
string | The name to test. |
- Source:
Returns:
Whether state binds it.
- Type
- boolean
(inner) has(key) → {boolean}
Parameters:
| Name | Type | Description |
|---|---|---|
key |
string | The name to test. |
- Source:
Returns:
Whether a getter is bound.
- Type
- boolean
(inner) keys() → {Array:.<string:>}
- Source:
Returns:
The names bound.
(inner) keys() → {Array:.<string:>}
- Source:
Returns:
The names bound.
(inner) keys() → {Array:.<string:>}
- Source:
Returns:
The names bound.
(inner) set(key, value) → {boolean}
Parameters:
| Name | Type | Description |
|---|---|---|
key |
string | The name to write. |
value |
any | The value to assign. |
- Source:
Returns:
Always true.
- Type
- boolean
(inner) set(key, value) → {boolean}
Parameters:
| Name | Type | Description |
|---|---|---|
key |
string | The name to write. |
value |
any | The value to assign. |
- Source:
Returns:
Always true.
- Type
- boolean
Type Definitions
ScopeLayer
Type:
- object
Properties:
| Name | Type | Attributes | Description |
|---|---|---|---|
has |
function | Whether the layer binds a name. |
|
get |
function | The value bound to a name. |
|
set |
function |
<optional> |
Assigns to a bound name. |
keys |
function | The names the layer binds. |
- Source: