StunkStunk
Core Concepts

Chunks

The core primitive of Stunk — a reactive, atomic unit of state.

A chunk is the fundamental building block of Stunk. It's a small, self-contained reactive container that holds a single value of any type — a number, string, object, array, or null.

import { chunk } from "stunk";

const count = chunk(0);
const name = chunk("Fola");
const user = chunk({ name: "Fola", age: 25 });
const items = chunk<string[]>([]);
const empty = chunk<string | null>(null); // null is valid in v3

Chunks are defined outside components and functions. They are global reactive state — not component-local.

undefined is not a valid initial value. Passing undefined to chunk() will throw an error. Use null or a typed default instead.


API

get()

Returns the current value and registers the chunk as a tracked dependency inside computed().

const count = chunk(0);
count.get(); // 0

peek()

Returns the current value without registering it as a dependency. Use this inside computed() when you need a value but don't want changes to it triggering a recompute.

const count = chunk(0);
const multiplier = chunk(2);

const result = computed(() => count.get() * multiplier.peek());
// result only recomputes when count changes — not when multiplier changes

set(value | updater)

Sets a new value. Accepts either a direct value or an updater function that receives the current value.

const count = chunk(0);

count.set(10); // direct value
count.set((prev) => prev + 1); // updater function

const user = chunk({ name: "Fola", age: 25 });
user.set((prev) => ({ ...prev, age: 26 }));

set() only notifies subscribers if the new value is different from the current value. Setting the same value twice won't trigger a re-render.

Object shape validation (dev only)

In development, Stunk validates that the new value matches the shape of the initial value — same keys, same types. This catches accidental shape mutations early.

const user = chunk({ name: "Fola", age: 25 });

// ✅ Same shape — works
user.set({ name: "Tunde", age: 30 });

// ⚠️ Unknown key — warns in dev
user.set({ name: "Tunde", age: 30, role: "admin" } as any);

For a harder enforcement that throws instead of warning, use strict mode:

const user = chunk({ name: "Fola", age: 25 }, { strict: true });

// 🚨 Throws in development
user.set({ name: "Tunde", age: 30, role: "admin" } as any);
// Error: [chunk_0] Unexpected keys in set(): role.

subscribe(callback)

Subscribes to value changes. The callback is called only on changes — not immediately on subscribe.

Returns an unsubscribe function.

const count = chunk(0);

const unsubscribe = count.subscribe((value) => {
  console.log("count:", value);
});

count.set(1); // logs: "count: 1"
count.set(2); // logs: "count: 2"

unsubscribe();
count.set(3); // nothing logged

In v3, subscribe() no longer fires immediately with the current value on registration. If you need the current value on mount, call chunk.get() directly alongside subscribe().


reset()

Resets the chunk back to its initial value and notifies all subscribers.

const count = chunk(0);

count.set(42);
count.reset();
count.get(); // 0

destroy()

Clears all subscribers, resets to initial value, and removes the chunk from the internal registry.

const count = chunk(0);
count.subscribe(console.log);
count.destroy(); // all subscribers cleared

After destroy(), the chunk is no longer reactive. Subsequent set() calls update the value but no subscribers are notified.


derive(fn)

Creates a new read-only chunk derived from this one. Updates automatically when the source changes.

const price = chunk(100);
const discounted = price.derive((p) => p * 0.9);

discounted.get(); // 90
price.set(200);
discounted.get(); // 180

See Derive & Select for full details.


Configuration

chunk() accepts an optional config object as a second argument:

const user = chunk(
  { name: "Fola", age: 25 },
  {
    name: "user", // name shown in dev warnings
    strict: true, // throws on unknown keys in set() (dev only)
    middleware: [logger], // middleware applied on every set()
  },
);

Middleware

Middleware functions run on every set() call and can transform or validate the value:

import { logger, nonNegativeValidator } from "stunk/middleware";

const count = chunk(0, {
  middleware: [logger(), nonNegativeValidator],
});

count.set(5); // logs: "Setting value: 5"
count.set(-1); // throws: "Value must be non-negative!"

You can also pass named middleware for better dev-mode error messages:

const count = chunk(0, {
  middleware: [
    { name: "logger", fn: logger() },
    { name: "validator", fn: nonNegativeValidator },
  ],
});

See Middleware for all built-in options and how to write your own.


TypeScript

Chunk types are fully inferred from the initial value:

const count = chunk(0); // Chunk<number>
const name = chunk("Stunk"); // Chunk<string>
const user = chunk({ name: "Fola", age: 25 }); // Chunk<{ name: string; age: number }>

You can also provide an explicit type:

const items = chunk<string[]>([]);
const status = chunk<"idle" | "loading" | "error">("idle");
const value = chunk<string | null>(null);

What's next?

On this page