API Reference
The entire public surface of the library, one entry per export. The why behind each design lives in the guide; this page is the contract. Every signature below ships in the 1.1 kB build.
Reactive(Class)
Transforms a class's prototype in place — once, idempotently — and returns the same constructor, typed as a reactive class.
function Reactive<C>(
targetClass: C,
): ReactiveClass<C> & { Instance: ReactiveInstance<InstanceType<C>> }What the transform does:
- Getters returning
ref()/computed()become lazily-cached Refs/Computeds — created on first access, the same object forever after (Reactive State). - Getters returning plain values de-optimize back to native prototype getters — the recommended default for derivations; reach for
computed()only when the work is expensive or you need render suppression (Computed & Watch). - Getters named
$…are cached whole on first access — the singleton slot for composables and stores. - Methods become lazily-bound, referentially-stable functions:
thisis always correct,instance.method === instance.methodis always true. - Injects
$watch,$watchEffectand$stopEffectson the prototype. - Idempotent — safe to call many times and at every level of an inheritance chain; each prototype is processed once.
class $Counter {
get count() {
return ref(0)
}
increment() {
this.count.value++
}
}
const Counter = Reactive($Counter)
new Counter().increment()For anything with a future — parents, children, cross-file references — export through the namespace pattern.
instance.$watch(source, callback, options?)
Same signature as Vue's watch, but the watcher registers in the instance's own lazily-created, detached effect scope — owned by the instance, not by whichever component constructed it. Use it for instances that outlive components; component-scoped instances use plain watch in the constructor instead (Lifecycle & Teardown).
const stop = instance.$watch(
() => instance.count.value,
(count, oldCount) => console.log(count, oldCount),
)
stop() // stop just this watcherThe scope is allocated on the first $watch/$watchEffect call only — pure-data instances that never watch allocate nothing.
instance.$watchEffect(effect, options?)
Vue's watchEffect, registered in the same lazy per-instance scope. Returns the stop handle.
instance.$watchEffect(() => render(instance.width.value, instance.height.value))instance.$stopEffects(options?)
Disposes the instance, in order:
- stops the effect scope — every
$watch/$watchEffectwatcher; - clears all cached Refs/Computeds and bound methods, so the instance can be garbage-collected.
instance.$stopEffects() // stop + clear (disposal is a reset)
instance.$stopEffects({ reset: false }) // stop the watchers ONLYAccessing a member after the default call re-materializes it fresh — the initializers run again. With { reset: false } every cached cell survives with its current value: the watchers die, the state stays, and the instance can $watch again in a fresh scope — the suspend/resume pattern (keep watcher wiring in a startWatchers() method the constructor calls, and resuming is one call). There are no teardown hooks — richer cleanup is an ordinary method of yours that does its own work and then calls $stopEffects() (Lifecycle & Teardown).
propsWithDefaults(defaults, typedProps, cloner?)
Merges plain default values into defineComponent-style prop definitions, wrapping object and array defaults in factory functions that copy their plain containers for each component instance.
const props = propsWithDefaults(
{ size: { w: 10, h: 10 }, label: 'box', items: [] },
{
size: { type: Object },
label: { type: String },
items: { type: Array },
},
)- Default cloner:
clonecopies acyclic plain-object and array trees. Nested callbacks, class constructors, class instances and other opaque objects (Map,Set,Date, typed arrays) retain their references. cloneroverride: passstructuredClonefor supported data when built-in objects need independent copies or the data contains cycles. A custom cloner can supply other ownership rules.- Required props and primitive/function/class defaults pass through unwrapped.
clone(value)
The configuration copier used by propsWithDefaults and nestedProps. It recursively copies plain objects and arrays, preserving sparse array holes and null object prototypes. Other values retain their identity, including nested class constructors and callbacks. Container trees must be acyclic; opaque objects remain shared.
function clone<T>(value: T): TnestedProps(props, defaults, customCloner?) — from ivue/extras
Fills every nested object prop from the class's own defaults, in place, at the seam where props enter the class, and returns the props typed as complete (NestedProps<P, D>; NestedPartial<T> is what a page may pass). Vue resolves a default only when a prop is absent, so a supplied partial object arrives with its sibling leaves gone; this completes it. Arrays are taken whole. customCloner is the copy policy for each default branch written in, the same knob propsWithDefaults has: clone by default, structuredClone for Date/Map/Set copies, the identity only when the caller owns a fresh tree per instance.
function nestedProps<P extends object, D extends object>(
props: P,
defaults: D,
customCloner?: (value: unknown) => unknown
): NestedProps<P, D>Static(Class) — from ivue/extras
The static-side sibling of Reactive(), for stateless capability classes — function bags published behind a namespace's replaceable Class slot. Imported from the separate ivue/extras entry so the primary ivue entry stays the bare engine. This page is the contract; the guide is Static() — Capability Classes.
import { Static } from 'ivue/extras';Static() returns a subclass of the given class (the raw class is never touched — it stays a clean foundation for extends) and transforms two member kinds:
- Static methods bind lazily with stable identity.
Class.methodis the same function on every read, bound to the receiving class — safe to detach, hand to a router, keep in a registry. Because binding resolves through the receiver at first read, a subclass's overrides are honored. - Get-only static accessors named
$…become compute-once-per-receiver caches. The getter body runs on first read through a given class; the result is stored on that receiver and returned forever after. The guard checks own properties only, so a parent's cache can never shadow a subclass: each class in a hierarchy derives through its own overrides on its own first read, in any read order.
class $ScrollMomentum {
// a live knob — subclasses pinch it, so NO $ prefix
static get friction() {
return 2;
}
// derived once per receiver — the $ prefix is the API
static get $atRest() {
return { velocity: 0, threshold: this.friction * 10 };
}
static settle(velocity: number) {
return Math.abs(velocity) < this.$atRest.threshold;
}
}
export namespace ScrollMomentum {
export const $Class = Static($ScrollMomentum); // anchor — children `extends` this
export let Class = $Class; // selection — kernels/tests swap this
}Semantics to rely on:
The
$prefix promises stable identity per receiver — nothing more. The getter body runs once per class; every later read returns the same value. Whether that value is immutable config or a deliberately mutable memo table is the author's design — the engine does not freeze it. A static getter that must stay live — a knob for test subclasses, a fresh-per-read value — must not use the prefix. (Measured on Node 26: the caching getter's warm read costs ~4–6 ns more than a plain property — invisible at any real call frequency; in a genuinely hot loop, hoist the value into a local once.)Caching is per receiver: when a subclass overrides an input,
Sub.$x !== Base.$x. Compare by value, or through one receiver. Bound methods follow the same rule —Sub.methodbinds toSub, in any read order.$semantics are granted by the transform. A raw class, a raw subclass, or a class only passed throughReactive()keeps native getter behavior — exactly as an unwrapped class's instance$-getters aren't cached either. A class that needs instance reactivity and static$-caches composes the transforms:tsexport namespace Settings { // the anchor: statics wrapped once, at definition export const $Class = Static($Settings); // Reactive() is in-place — Class === $Class export let Class = Reactive($Class); export type Instance = typeof Class.Instance; }Static()wraps the statics at the anchor, so subclasses and test doubles inherit working static semantics by extending$Class;Reactive()then transforms the prototype in place. Instances carry full reactive semantics; the static surface carries binding and$-caching.Accessor pairs with a setter, getters without the
$prefix, and instance members are untouched byStatic().
LazyShared<T> — from ivue/extras
class LazyShared<T> {
constructor(make: () => T);
get value(): T; // constructs on first read, then memoizes
reset(): void; // drop the value; the next read constructs again
}The safe shared-store cell for static classes. It closes a triangle no other member kind can:
- A
$-prefixed static getter caches per receiver — right for memos and per-class tuning, wrong for a shared store: a subclass readingthis.$storesilently forks the registry. - A plain
static readonlyfield is shared and never forks — but its initializer runs at module load, so constructing another namespace's class there races import cycles. LazySharedis both: the field eagerly stores the CELL (load-safe — a thunk evaluates nothing), the thunk runs on the first.valueread (cycle-safe — every module in any import cycle has finished loading), and memoization lives inside the cell (fork-safe — no receiver, subclass included, can fork it).
import { Static, LazyShared } from 'ivue/extras';
class $SearchRegistry {
// ONE backend for the whole hierarchy — the field IS the pin
protected static readonly sharedBackend = new LazyShared(
() => new SearchBackend.Class(),
);
protected static get $backend() {
return this.sharedBackend.value;
}
}
export namespace SearchRegistry {
export const $Class = Static($SearchRegistry);
export let Class = $Class;
}Guarantees:
- A thunk that reads its own cell (directly or through another cell) throws a named cycle error instead of a bare stack overflow.
- A thunk that throws leaves the cell retryable, never poisoned — the next read runs the thunk again.
reset()exists for tests and process recomposition; production code never resets.
The pattern in full — when a shared store beats a $-getter, and how the two divide the static world — is in Caches, Registries & self.
isClass(value)
function isClass(value: any): booleantrue for ES classes, false for arrow functions, normal functions, and non-functions. Used internally by propsWithDefaults; exported because it keeps being useful.
Types
All types are erased from production output. The first group supports ivue's extensible-component architecture directly; the later groups are available when an application needs their narrower transformations.
Extensible component types
| Type | Meaning |
|---|---|
ExtractPropDefaultTypes<O> | Extracts the resolved prop values from a Vue runtime props object and marks every key as assigned, matching a defaults object consumed by propsWithDefaults() |
ExtractEmitTypes<T> | Converts an object of emit validators into the overloaded emit function accepted by defineEmits and class constructors |
ExtendSlots<T> | Keeps every slot in T and adds typed before--* and after--* extension slots around each one |
These three types keep inherited props, emits, and slots aligned as a component grows. The complete pattern and examples live in Extensible Components.
Reactive class types
| Type | Meaning |
|---|---|
ReactiveInstance<T> | T plus $watch/$watchEffect/$stopEffects, with ref-returning getters re-typed as writable — the type of every unwrapping surface (defineExpose, reactive() interop) |
ReactiveClass<C> | Preserves C's constructor parameters and produces ReactiveInstance<InstanceType<C>> |
Props utility types
| Type | Meaning |
|---|---|
VuePropsObject | The runtime-props shape accepted by propsWithDefaults(): Record<string, { type; default?; required? }> |
VuePropsWithDefaults<T> | The output shape of propsWithDefaults(), with every descriptor's default key present in the type |
General utility types
These exports are optional conveniences. Applications can use their own equivalents without changing how ivue works.
| Type | Meaning |
|---|---|
AnyFn | Any callable function type |
RecordToUnion<T> | Converts a record into the union of its value types |
ValueOf<T, K> | Selects the value type at key K |
UnionToIntersection<U> | Converts a union into an intersection |
PrefixKeys<T, P> | Remaps every string key in T with prefix P |
FnParameter<F, K> | Selects parameter K from function F |
IFnParameters<T, K> | Extracts the full parameter tuple from function member K of T |
IFnParameter<T, P, K> | Selects parameter K from function member P of T, including optional members |
The formal specification
Each guarantee the engine maintains — its mechanism, and what it makes impossible — is on The Invariants Behind ivue.