@derivesome/core
v1.0.23
Published
A lightweight reactive state management library with automatic dependency tracking.
Downloads
2,562
Readme
@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(); // read and subscribe (inside an effect/derived)
count.peek(); // read without subscribing
count.set(1); // update value — notifies subscribers only if value changed
count.set(n => n + 1); // functional updateValues 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(); // tears down all subscriptionsisReference
import { isReference } from '@derivesome/core';
isReference(count); // true
isReference(42); // falsederived
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(); // 0
count.set(5);
doubled.peek(); // 10
label.peek(); // "Count is: 10"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; // only subscribed when enabled is true
});isDerived
import { isDerived } from '@derivesome/core';
isDerived(doubled); // true
isDerived(count); // falseeffect
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());
});
// logs: "Hello, Alice"
name.set('Bob');
// logs: "Hello, 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); // logs: "count changed to 1"
unsubscribe(); // stop listening
count.set(2); // nothing loggedobserve accepts an optional options object:
count.observe(
(value) => console.log(value),
{
immediate: true, // call the callback immediately with the current value
cleanup: () => { // called when the subscription is removed
console.log('cleaned up');
},
}
);isObservable
import { isObservable } from '@derivesome/core';
isObservable(count); // truePubSub
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); // logs: "received 42"
unsub(); // unsubscribe
ps.publish(99); // nothing logged
ps.dispose(); // remove all subscribers and effectsaddEffect registers a function that participates in the reactive context system (re-runs through Context.runEffect):
ps.addEffect(() => {
// re-run as a tracked effect when published
});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');
// { type: 'push', items: ['d'] }
items.pop();
// { type: 'pop', removed: 'd' }
items.splice(0, 1, 'z');
// { type: 'splice', start: 0, deleteCount: 1, removed: ['a'], added: ['z'] }
items.set(0, 'x');
// { type: 'set', index: 0, value: 'x' }
items.sort();
// { type: 'sort' }
items.reverse();
// { type: 'reverse' }
items.dispose(); // remove all subscribersMutation types
| Method | Mutation type | Extra fields |
|---------------|----------------|-------------------------------------------------|
| 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) } });
// [Reference<1>, Reference<2>, Reference<3>]
findRefs([a, b]);
// [Reference<1>, Reference<2>]
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';
// Primitive matching
match('hello', 'hello'); // true
match(42, 42); // true
match(42, 99); // false
// Object structural matching (partial — only pattern keys are checked)
const user = { role: 'admin', name: 'Alice' };
match(user, { role: 'admin' }); // true — narrows type
match(user, { role: 'guest' }); // false
// Array matching (positional)
match([1, 2, 3], [1, 2]); // true (pattern checked index-by-index)
// Type narrowing
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; // shape is narrowed to circle
}
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);
// [
// { type: 'changed', path: ['y'], oldValue: 2, newValue: 3 },
// { type: 'added', path: ['z'], newValue: 4 },
// ]
const result = patch({ ...a }, diffs);
// { x: 1, y: 3, z: 4 }Works recursively on nested objects and arrays:
diff([1, 2, 3], [1, 2, 3, 4]);
// [{ type: 'added', path: [3], newValue: 4 }]
diff({ a: { b: 1 } }, { a: { b: 2 } });
// [{ type: 'changed', path: ['a', 'b'], oldValue: 1, newValue: 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; // 3 (top of stack)
stack.push(4);
stack.current; // 4
stack.pop(); // 4
stack.current; // 3