Introduction
Deterministic, incremental layout invalidation engine.
What is Inval?
Inval is a dependency graph engine for tracking computed values. It's not a framework, not a renderer, and not a state management library.
It answers one question: "What geometry changed, and what depends on it?"
When you build interactive UIs — dashboards, virtualized lists, resizable panels — layout changes cascade. A sidebar width change affects content width, which changes text wrapping, which shifts card heights, which breaks scroll positions.
Most apps handle this by recomputing everything on every change. Inval gives you the primitive to do it incrementally.
Why Inval?
Popular virtualization libraries struggle with dynamic height issues:
- TanStack Virtual — issues #832, #659: scrolling with dynamic height lags and stutters
- react-window — issue #741: VariableSizeList causes major scroll jitters
Root cause: No dependency graph means recomputing ALL nodes on ANY change.
Tip
Inval solves this at the engine level. Declare what depends on what, and the engine handles incremental updates automatically.
Quick Example
import { input, node, why } from '@blu3ph4ntom/inval'
// Declare inputs
const width = input(800)
const height = input(600)
// Computed node with dependencies
const area = node({
dependsOn: { w: width, h: height },
compute: ({ w, h }) => w * h
})
area.get() // 480000 — computes lazily
area.get() // 480000 — cached, zero cost
width.set(1000)
area.get() // 600000 — recomputes only what changed
why(area) // ['area', 'width'] — trace invalidation path Key Principles
DAG
Nodes form a directed acyclic graph. Cycles are detected at construction time.
Lazy
node.get() recomputes only if dirty. Returns cached value otherwise.
Incremental
input.set() walks children, marks only affected nodes dirty.
Pure
No DOM. You measure, you pass values in. Framework agnostic.
Zero deps
Pure TypeScript. 12KB packed. No external runtime dependencies.
Debuggable
why(), inspect(), toDot() for full visibility.