Architecture
Architecture
Understand the internal architecture visually.
1. Core Runtime
signal(input)
→
immutable value storage
→
value getter
→
register current effect
→
value setter / mutation
→
run dependent effects synchronously
The core runtime is small on purpose:
- `signal()` creates mutable source state
- reading `.value` adds the current effect to the signal
- writing `.value` or mutating arrays/objects triggers dependent effects immediately
2. Dependency Tracking
effect(fn)
→
_currentSignalEffect
→
signal.value getter
→
signal._effects
Tracking is global and temporary:
- `effect()` sets a module-level current effect before executing the callback
- each signal getter registers that effect in the signal's local `_effects` set
- when execution ends, the current effect is cleared
3. Derived Signal Model
derive(fn)
→
internal derived source signal
→
internal updater effect
→
computed value
→
internal derived source signal
Derived signals are not a separate storage engine:
- they are implemented using an internal source signal
- an internal effect recomputes the value
- the public derived signal exposes read-only `.value`, `prevValue`, and `dispose()`
4. Disposal
dispose() called
→
canDisposeNow = true
→
cleanup on next signal update
→
removed from signal._effects
Disposal is intentionally lazy:
- calling `dispose()` marks the effect
- the effect is removed on the next update cycle
- this keeps disposal simple and synchronous with the rest of the runtime
5. Data Flow
source signal
→
derived signal
→
effect
→
side effects
The actual data flow is:
- a source signal stores state
- derived signals read source signals and recompute immediately
- effects observe signals by reading `.value`
- updates propagate synchronously through the dependency set
6. Internal State
- Source signals store `_value` and `_effects`
- The Effects store `canDisposeNow` and `dispose()`
- Derived signals store an internal source signal plus an updater effect
- Array and object signals add shape-specific helpers on top of the same source signal core
7. Why This Design
- It keeps the runtime small and explicit
- It avoids hidden batching or deferred execution
- It matches the repository's semantics and behavioral tests exactly
- It makes the signal graph easy to reason about because every dependency comes from an actual getter read