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

example.ts TYPESCRIPT
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.

Next Steps