@derivesome/core
A lightweight reactive state management library with automatic dependency tracking.
Table of Contents
Core Concepts
Reactivity is built on automatic dependency tracking. When a ref.get() is called inside an effect or derived, that ref registers the running effect as a subscriber. When the ref's value changes, all subscribed effects re-run automatically. This means you never manually declare dependencies — the system infers them at runtime.
ref
ref(value) creates a reactive reference holding a single value.
import { ref } from '@derivesome/core';
const count = ref(0);
count.get();
count.peek();
count.set(1);
count.set(n => n + 1);
Values are compared with strict equality (===). If the new value is the same as the previous one, no notification is sent.
ref also supports dispose() to remove all subscribers and effects:
count.dispose();
isReference
import { isReference } from '@derivesome/core';
isReference(count);
isReference(42);
derived
derived(fn) creates a computed value that re-evaluates automatically when its reactive dependencies change. It is itself a Reference, so it can be used anywhere a ref can.
import { ref, derived } from '@derivesome/core';
const count = ref(0);
const doubled = derived(() => count.get() * 2);
const label = derived(() => `Count is: ${doubled.get()}`);
doubled.peek();
count.set(5);
doubled.peek();
label.peek();
Dependencies are tracked dynamically per evaluation. If a branch is not taken, its refs are not subscribed:
const enabled = ref(false);
const value = ref(42);
const result = derived(() => {
if (!enabled.get()) return -1;
return value.get() * 2;
});
isDerived
import { isDerived } from '@derivesome/core';
isDerived(doubled);
isDerived(count);
effect
effect(fn) runs a function immediately and re-runs it whenever any ref.get() called inside it changes.
import { ref, effect } from '@derivesome/core';
const name = ref('Alice');
effect(() => {
console.log('Hello,', name.get());
});
name.set('Bob');
Effects are the primary way to cause side effects in response to reactive state changes. Unlike derived, they don't return a value.
Observable / observe
Every ref implements the Observable interface, which provides observe — a way to subscribe to value changes outside of the effect/derived system.
import { ref } from '@derivesome/core';
const count = ref(0);
const unsubscribe = count.observe((value) => {
console.log('count changed to', value);
});
count.set(1);
unsubscribe();
count.set(2);
observe accepts an optional options object:
count.observe(
(value) => console.log(value),
{
immediate: true,
cleanup: () => {
console.log('cleaned up');
},
}
);
isObservable
import { isObservable } from '@derivesome/core';
isObservable(count);
PubSub
pubsub() is the low-level primitive underlying ref. It provides a typed publish/subscribe channel with no built-in value storage.
import { pubsub } from '@derivesome/core';
const ps = pubsub<number>();
const unsub = ps.subscribe((value) => {
console.log('received', value);
});
ps.publish(42);
unsub();
ps.publish(99);
ps.dispose();
addEffect registers a function that participates in the reactive context system (re-runs through Context.runEffect):
ps.addEffect(() => {
});
ArrayProxy
ArrayProxy<T> extends the native Array and emits a typed mutation event whenever the array is modified. Useful for building reactive list state.
import { ArrayProxy } from '@derivesome/core';
const items = new ArrayProxy('a', 'b', 'c');
const unsub = items.subscribe((mutation) => {
console.log(mutation);
});
items.push('d');
items.pop();
items.splice(0, 1, 'z');
items.set(0, 'x');
items.sort();
items.reverse();
items.dispose();
Mutation types
push | push | items: T[] |
pop | pop | removed: T | undefined |
shift | shift | removed: T | undefined |
unshift | unshift | items: T[] |
splice | splice | start, deleteCount, removed, added |
sort | sort | — |
reverse | reverse | — |
fill | fill | value, start, end |
copyWithin | copyWithin | target, start, end |
.set(i, v) | set | index, value |
Utilities
findRefs
Recursively walks any value (object, array, or primitive) and returns all Reference instances found within it.
import { ref, findRefs } from '@derivesome/core';
const a = ref(1);
const b = ref(2);
findRefs({ a, b, nested: { c: ref(3) } });
findRefs([a, b]);
findRefs(42);
match
match(input, pattern) is a structural pattern matching utility. It checks whether input structurally matches pattern, working as a type guard.
import { match } from '@derivesome/core';
match('hello', 'hello');
match(42, 42);
match(42, 99);
const user = { role: 'admin', name: 'Alice' };
match(user, { role: 'admin' });
match(user, { role: 'guest' });
match([1, 2, 3], [1, 2]);
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number };
function area(shape: Shape): number {
if (match(shape, { kind: 'circle' as const })) {
return Math.PI * shape.radius ** 2;
}
return shape.side ** 2;
}
diff / patch
diff(a, b) computes a list of structural differences between two values. patch(target, diffs) applies those diffs to produce a new value.
import { diff, patch } from '@derivesome/core';
const a = { x: 1, y: 2 };
const b = { x: 1, y: 3, z: 4 };
const diffs = diff(a, b);
const result = patch({ ...a }, diffs);
Works recursively on nested objects and arrays:
diff([1, 2, 3], [1, 2, 3, 4]);
diff({ a: { b: 1 } }, { a: { b: 2 } });
Stack
A simple typed stack used internally by the context system. Exported for external use.
import { Stack } from '@derivesome/core';
const stack = new Stack([1, 2, 3]);
stack.current;
stack.push(4);
stack.current;
stack.pop();
stack.current;