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 v3Chunks 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(); // 0peek()
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 changesset(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 loggedIn 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(); // 0destroy()
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 clearedAfter 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(); // 180See 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);