Getting Started

Get up and running with Inval in under 5 minutes.

Installation

Install via your preferred package manager:

BASH
npm install @blu3ph4ntom/inval

# or
bun add @blu3ph4ntom/inval

# or
pnpm add @blu3ph4ntom/inval

Or use the IIFE bundle directly in a script tag:

index.html HTML
<script src="https://unpkg.com/@blu3ph4ntom/inval/dist/inval.iife.js"></script>
<script>
  const { input, node, why } = Inval
  // ...
</script>

Inval requires Node.js 18+ and has zero runtime dependencies.

Quick Start

Let's build a simple dependency graph. We'll compute the area of a rectangle and see how Inval handles changes.

Step 1: Import the API

step1.ts TYPESCRIPT
import { input, node } from '@blu3ph4ntom/inval'

Step 2: Create input nodes

Input nodes are leaf nodes. They hold values that external code sets.

step2.ts TYPESCRIPT
const width = input(800)
const height = input(600)

width.get()   // 800
height.get()  // 600

Step 3: Create a computed node

Computed nodes declare their dependencies and compute lazily.

step3.ts TYPESCRIPT
const area = node({
  dependsOn: { w: width, h: height },
  compute: ({ w, h }) => w * h
})

area.get()  // 480000 — computes on first access

Step 4: Change an input and observe

step4.ts TYPESCRIPT
area.get()  // 480000 — cached, zero cost

width.set(1000)
area.get()  // 600000 — recomputes because width changed

Tip

The key insight: area.get() returns the cached value if nothing changed. It only recomputes when a dependency is dirty.

Your First Graph

Let's build something more realistic — a dashboard layout with dependent widgets.

dashboard.ts TYPESCRIPT
import { input, node, batch } from '@blu3ph4ntom/inval'

// External inputs
const dashboardWidth = input(1200)
const sidebarWidth = input(300)

// Computed: content area width
const contentWidth = node({
  dependsOn: { dash: dashboardWidth, sidebar: sidebarWidth },
  compute: ({ dash, sidebar }) => dash - sidebar
})

// Computed: chart width (60% of content)
const chartWidth = node({
  dependsOn: { content: contentWidth },
  compute: ({ content }) => content * 0.6
})

// Computed: table width (40% of content)
const tableWidth = node({
  dependsOn: { content: contentWidth },
  compute: ({ content }) => content * 0.4
})

// Initial values
chartWidth.get()  // 540 (900 * 0.6)
tableWidth.get()  // 360 (900 * 0.4)

// Change sidebar — chart and table both update
sidebarWidth.set(400)
chartWidth.get()  // 480 (800 * 0.6)
tableWidth.get()  // 320 (800 * 0.4)

// Atomic update: change both at once
const changed = batch(() => {
  dashboardWidth.set(1400)
  sidebarWidth.set(350)
})
// changed = Set of all dirtied nodes

Warning

Always use batch() when setting multiple inputs at once. This ensures a single atomic update instead of cascading invalidations.