Core API
The core API provides the fundamental reactive building blocks that work anywhere (client, server, workers, etc.).
AbstractReactiveValue<T, TPlain = T>
Abstract base class for all reactive values. Contains all the common logic for reactive capabilities.
Note: You typically don't use this class directly. Use ReactiveValue for primitives, or one of the specialized classes (ReactiveObject, ReactiveArray, etc.) for complex types.
ReactiveValue<T>
Concrete class for reactive primitive values. Extends AbstractReactiveValue<T, T> and adds runtime validation to ensure only primitive values are used.
Constructor
new ReactiveValue<T>(initialValue: T)
Methods
get(): T- Gets the current valueset(newValue: T | ((current: T) => T)): void- Sets a new valuesubscribe(subscriber: (value: T, prevValue: T) => void): () => void- Subscribes to changes[Symbol.dispose](): void- Cleans up all subscriptions (implements Disposable interface)
Example
const count = new ReactiveValue(0);
// Get
const current = count.get(); // 0
// Set
count.set(10);
count.set((current) => current + 1);
// Subscribe
const unsubscribe = count.subscribe((newValue, prevValue) => {
console.log(`${prevValue} -> ${newValue}`);
});
ReactiveObject<T, TPlain = T>
For plain objects with Immer support (default option, you can opt-out).
Constructor
new ReactiveObject<T>(
initialValue: T,
options?: ReactiveObjectOptions<T, TPlain>
)
Options
interface ReactiveObjectOptions<TValue, TPlain> {
// Custom equality
compare?: {
equals?: (a: TValue, b: TValue) => boolean;
deepEquals?: boolean; // default: true
};
// Enable/Disable Immer (default: true)
useImmer?: boolean;
// Convert to plain object
toPlainObject?: (value: TValue) => TPlain;
}
Methods
get(): T- Gets the current objectset(producer: (draft: T) => void | T): void- Updates using Immer or callbacksubscribe(subscriber: (value: T, prevValue: T) => void): () => voidtoPlainObject(): TPlain- Converts to plain object (if configured)
Example
// With Immer (default)
const user = new ReactiveObject({ name: "John", age: 30 });
user.set((draft) => {
draft.age = 31;
});
// Without Immer
const user = new ReactiveObject({ name: "John" }, { useImmer: false });
user.set((current) => ({ ...current, name: "Jane" }));
// With toPlainObject
const user = new ReactiveObject(
{ tags: new Set(["admin"]) },
{
toPlainObject: (v) => ({
...v,
tags: Array.from(v.tags),
}),
},
);
ReactiveArray<TValueItem, TPlainItem = TValueItem, TPlain = TPlainItem[]>
For arrays of values.
Constructor
new ReactiveArray<TValueItem>(
initialValue: TValueItem[],
options?: ReactiveArrayOptions<TValueItem, TPlainItem, TPlain>
)
Methods
get(): TValueItem[]- Gets the current arrayset(producer: (draft: TValueItem[]) => void | TValueItem[]): void- Updates using Immersubscribe(subscriber: (value: TValueItem[], prevValue: TValueItem[]) => void): () => voidtoPlainObject(): TPlain- Converts to plain array
Example
const items = new ReactiveArray([1, 2, 3]);
items.set((draft) => {
draft.push(4);
});
items.set((current) => [...current, 5]);
ReactiveStore<T>
Container for coordinating multiple reactive value instances.
Constructor
new ReactiveStore<T extends ReactiveStoreType<T>>(values: T)
Properties
values: T- Direct access to reactive value instances
Methods
keys(): (keyof T)[]- Gets all keyssubscribe(subscriber: (changedKeys: PropertyKey[]) => void): () => voidbatchNotifications(callback: (scopeValues: ScopeValues<T>, scopeMethods: ScopeMethods<T>) => void | Promise<void>): Promise<void>- Batch notifications for all changesrollbackBlock(callback: (scopeValues: ScopeValues<T>, rollback: () => never, scopeMethods: ScopeMethods<T>) => void | Promise<void>): Promise<void>- Create a rollback block over the entire storetoPlainObject(): ExtractReactiveValues<T>- Converts all values to plain objects[Symbol.dispose](): void- Cleans up all subscriptions (implements Disposable interface)
Example
const store = new ReactiveStore({
count: new ReactiveValue(0),
name: new ReactiveValue("John"),
});
// Direct access
store.values.count.set(10);
// Batching - all changes are batched together
await store.batchNotifications(({ count, name }) => {
count.set(10);
name.set("Jane");
});
// Rollback - entire store can be rolled back
await store.rollbackBlock(({ count }, rollback) => {
count.set(20);
if (shouldRollback) {
rollback(); // Restores all values to their state before the block
}
});
ComputedValue<T>
Value automatically calculated from dependencies.
Constructor
new ComputedValue<T>(
compute: () => T,
dependencies: AbstractReactiveValue<any>[],
options?: { memo?: boolean }
)
Methods
get(): T- Gets the computed valuesubscribe(subscriber: (value: T, prevValue: T) => void): () => void
Example
const count = new ReactiveValue(10);
const double = new ComputedValue(() => count.get() * 2, [count]);
double.get(); // 20
// With memoization
const expensive = new ComputedValue(() => heavyComputation(), [dependency], {
memo: true,
});
ReactiveReference<TSource>
Reference to another reactive value (or any AbstractReactiveValue).
Constructor
new ReactiveReference<TSource extends AbstractReactiveValue<any>>(
sourceValue: TSource,
options?: ReactiveReferenceOptions
)
Options
interface ReactiveReferenceOptions {
readonly?: boolean; // default: true
}
Example
const source = new ReactiveValue(0);
// Read-only (default)
const ref = new ReactiveReference(source);
ref.set(10); // Throws error
// Read-write
const ref = new ReactiveReference(source, { readonly: false });
ref.set(10); // Modifies source
ReactiveSet and ReactiveMap
For Set and Map collections.
const tags = new ReactiveSet(["admin", "user"]);
const metadata = new ReactiveMap([["key", "value"]]);
These work similarly to reactive values but are specialized for Set and Map data structures.