Skip to main content
Version: v1.0

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 value
  • set(newValue: T | ((current: T) => T)): void - Sets a new value
  • subscribe(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 object
  • set(producer: (draft: T) => void | T): void - Updates using Immer or callback
  • subscribe(subscriber: (value: T, prevValue: T) => void): () => void
  • toPlainObject(): 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 array
  • set(producer: (draft: TValueItem[]) => void | TValueItem[]): void - Updates using Immer
  • subscribe(subscriber: (value: TValueItem[], prevValue: TValueItem[]) => void): () => void
  • toPlainObject(): 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 keys
  • subscribe(subscriber: (changedKeys: PropertyKey[]) => void): () => void
  • batchNotifications(callback: (scopeValues: ScopeValues<T>, scopeMethods: ScopeMethods<T>) => void | Promise<void>): Promise<void> - Batch notifications for all changes
  • rollbackBlock(callback: (scopeValues: ScopeValues<T>, rollback: () => never, scopeMethods: ScopeMethods<T>) => void | Promise<void>): Promise<void> - Create a rollback block over the entire store
  • toPlainObject(): 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 value
  • subscribe(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.