Skip to main content

API Reference

9 min read

Vue Composables

Vue composables API reference. All composables take an explicit system parameter – no provide/inject needed.


Quick Reference

ExportTypeDescription
useFactComposableRead single/multi facts
useDerivedComposableRead single/multi derivations
useSelectorComposableSelect from all facts with custom equality
useEventsComposableTyped event dispatchers
useDispatchComposableLow-level event dispatch
useWatchComposableSide-effect watcher for facts or derivations (auto-detects kind)
useInspectComposableSystem inspection (unmet, inflight, settled)
useConstraintStatusComposableReactive constraint inspection
useExplainComposableReactive requirement explanation
useRequirementStatusComposableSingle/multi requirement status (takes statusPlugin)
useOptimisticUpdateComposableOptimistic mutations with rollback
useDirectiveComposableScoped system with selected or all subscriptions
createTypedHooksFactoryCreate typed composables for a schema
useTimeTravelComposableReactive time-travel state (canUndo, canRedo, undo, redo)
shallowEqualUtilityShallow equality for selectors

useFact

Subscribe to a single fact or multiple facts. Returns a reactive Ref.

// Single key
function useFact<T>(system: System, factKey: string): Ref<T | undefined>

// Multi-key
function useFact<T extends Record<string, unknown>>(system: System, factKeys: string[]): ShallowRef<T>

useFact Usage

<script setup>
import { useFact } from '@directive-run/vue';
import { system } from './system';

// Subscribe to a single fact – returns Ref<number | undefined>
const count = useFact(system, 'count');

// Subscribe to multiple facts – returns ShallowRef<{ userId, loading }>
const multi = useFact(system, ['userId', 'loading']);
</script>

<template>
  <p>Count: {{ count }}</p>
  <p>User: {{ multi.userId }}</p>
</template>

Need a transform?

Use useSelector to derive values from facts. It auto-tracks dependencies and supports custom equality.


useDerived

Subscribe to a single derivation or multiple derivations.

// Single key
function useDerived<T>(system: System, derivationId: string): Ref<T>

// Multi-key
function useDerived<T extends Record<string, unknown>>(system: System, derivationIds: string[]): ShallowRef<T>

useDerived Usage

<script setup>
import { useDerived } from '@directive-run/vue';
import { system } from './system';

// Subscribe to a single derivation
const total = useDerived(system, 'cartTotal');

// Subscribe to multiple derivations at once
const state = useDerived(system, ['isRed', 'elapsed']);
</script>

<template>
  <p>Total: ${{ total }}</p>
  <p>{{ state.isRed ? 'Red' : 'Not red' }}</p>
</template>

Need a transform?

Use useSelector to derive values from facts. It auto-tracks dependencies and supports custom equality.


useSelector

Auto-tracking selector over facts and derivations. Uses withTracking() to detect which keys the selector accesses, then subscribes only to those.

function useSelector<R>(
  system: System,
  selector: (state: Record<string, unknown>) => R,
  equalityFn?: (a: R, b: R) => boolean,
): Ref<R>

useSelector Usage

<script setup>
import { useSelector, shallowEqual } from '@directive-run/vue';
import { system } from './system';

// Select and combine values from multiple facts with shallow equality
const summary = useSelector(
  system,
  (state) => ({
    userName: state.user?.name,
    itemCount: state.items?.length ?? 0,
  }),
  shallowEqual,
);
</script>

<template>
  <p>{{ summary.userName }} has {{ summary.itemCount }} items</p>
</template>

useEvents

Returns the system's typed events dispatcher object.

function useEvents<M extends ModuleSchema>(system: System<M>): System<M>["events"]

useEvents Usage

<script setup>
import { useEvents } from '@directive-run/vue';
import { system } from './system';

// Get typed event dispatchers for the module
const events = useEvents(system);
</script>

<template>
  <button @click="events.increment()">+1</button>
  <button @click="events.addItem({ name: 'Widget' })">Add</button>
</template>

useDispatch

Get a low-level dispatch function for sending events.

function useDispatch<M extends ModuleSchema>(system: System<M>): (event: InferEvents<M>) => void

useDispatch Usage

<script setup>
import { useDispatch } from '@directive-run/vue';
import { system } from './system';

// Get the low-level dispatch function
const dispatch = useDispatch(system);
</script>

<template>
  <button @click="dispatch({ type: 'increment' })">+1</button>
</template>

useWatch

Watch a fact or derivation and execute a callback when it changes. Auto-detects whether the key refers to a fact or a derivation – no discriminator needed. Does not cause re-renders; use for side effects only.

// Unified API – auto-detects fact vs derivation
function useWatch<T>(
  system: System,
  key: string,
  callback: (newValue: T, previousValue: T | undefined) => void,
): void

// Deprecated – still works but prefer the unified form above
function useWatch<T>(
  system: System,
  kind: "fact",
  factKey: string,
  callback: (newValue: T | undefined, previousValue: T | undefined) => void,
): void

useWatch Usage

<script setup>
import { useWatch } from '@directive-run/vue';
import { system } from './system';

// Watch a derivation – auto-detected
useWatch(system, 'pageViews', (newVal, prevVal) => {
  analytics.track('pageViews', { from: prevVal, to: newVal });
});

// Watch a fact – also auto-detected, no "fact" discriminator needed
useWatch(system, 'userId', (newId, prevId) => {
  console.log(`User changed from ${prevId} to ${newId}`);
});
</script>

Deprecated pattern

The four-argument form useWatch(system, "fact", "key", cb) still works but is deprecated. Use useWatch(system, "key", cb) instead.


useInspect

Get system inspection data reactively. Supports optional throttling for high-frequency updates.

function useInspect(system: System, options?: { throttleMs?: number }): ShallowRef<InspectState>

useInspect Returns

interface InspectState {
  isSettled: boolean;
  unmet: Requirement[];
  inflight: Requirement[];
  isWorking: boolean;
  hasUnmet: boolean;
  hasInflight: boolean;
}

useInspect Usage

<script setup>
import { useInspect } from '@directive-run/vue';
import { system } from './system';

// Get reactive system inspection data
const inspection = useInspect(system);

// With throttling to limit update frequency
const throttled = useInspect(system, { throttleMs: 200 });
</script>

<template>
  <div v-if="inspection.isWorking">
    <p>Unmet: {{ inspection.unmet.length }}</p>
    <p>Inflight: {{ inspection.inflight.length }}</p>
  </div>
</template>

useConstraintStatus

Get all constraints or a single constraint by ID. Reactively updates when constraint state changes.

// All constraints
function useConstraintStatus(system: System): ComputedRef<ConstraintInfo[]>

// Single constraint
function useConstraintStatus(system: System, constraintId: string): ComputedRef<ConstraintInfo | null>

useConstraintStatus Usage

<script setup>
import { useConstraintStatus } from '@directive-run/vue';
import { system } from './system';

// Get all constraints for the debug panel
const allConstraints = useConstraintStatus(system);

// Check a specific constraint by ID
const transition = useConstraintStatus(system, 'transition');
</script>

<template>
  <p v-if="transition">Constraint active: {{ transition.id }}</p>
  <ul>
    <li v-for="c in allConstraints" :key="c.id">{{ c.id }}: {{ c.active }}</li>
  </ul>
</template>

useExplain

Reactively returns the explanation string for a requirement type.

function useExplain(system: System, requirementId: string): Ref<string | null>

useExplain Usage

<script setup>
import { useExplain } from '@directive-run/vue';
import { system } from './system';

// Get a detailed explanation of why a requirement was generated
const explanation = useExplain(system, 'FETCH_USER');
</script>

<template>
  <p v-if="explanation">Why: {{ explanation }}</p>
</template>

useRequirementStatus

Get requirement status reactively. Takes the statusPlugin as the first parameter.

// Single type
function useRequirementStatus(statusPlugin: StatusPlugin, type: string): ShallowRef<RequirementTypeStatus>

// Multi-type
function useRequirementStatus(statusPlugin: StatusPlugin, types: string[]): ShallowRef<Record<string, RequirementTypeStatus>>

useRequirementStatus Returns

interface RequirementTypeStatus {
  pending: number;
  inflight: number;
  failed: number;
  isLoading: boolean;
  hasError: boolean;
  lastError: Error | null;
}

useRequirementStatus Usage

<script setup>
import { useRequirementStatus } from '@directive-run/vue';
import { statusPlugin } from './system';

// Track a single requirement type
const status = useRequirementStatus(statusPlugin, 'FETCH_USER');

// Track multiple requirement types at once
const multi = useRequirementStatus(statusPlugin, ['FETCH_USER', 'FETCH_POSTS']);
</script>

<template>
  <Spinner v-if="status.isLoading" />
  <ErrorBanner v-else-if="status.hasError" :error="status.lastError" />
  <UserContent v-else />
</template>

useOptimisticUpdate

Optimistic update composable. Saves a snapshot before mutating, monitors a requirement type via the status plugin, and rolls back on failure.

function useOptimisticUpdate(
  system: System,
  statusPlugin?: StatusPlugin,
  requirementType?: string,
): OptimisticUpdateResult

useOptimisticUpdate Returns

interface OptimisticUpdateResult {
  mutate: (updateFn: () => void) => void;
  isPending: Ref<boolean>;
  error: Ref<Error | null>;
  rollback: () => void;
}

useOptimisticUpdate Usage

<script setup>
import { useOptimisticUpdate } from '@directive-run/vue';
import { system, statusPlugin } from './system';

// Set up optimistic mutations with automatic rollback
const { mutate, isPending, error, rollback } = useOptimisticUpdate(system, statusPlugin, 'TOGGLE_LIKE');

function toggleLike() {
  // Optimistically update the UI before the server responds
  mutate(() => {
    system.facts.liked = !system.facts.liked;
    system.facts.likeCount += system.facts.liked ? 1 : -1;
  });
}
</script>

<template>
  <button @click="toggleLike" :disabled="isPending">
    {{ isPending ? 'Saving...' : 'Like' }}
  </button>
  <p v-if="error">Failed: {{ error.message }}</p>
</template>

useDirective

Create a scoped Directive system tied to the component lifecycle. The system is created on mount, started automatically, and destroyed on unmount. Two modes:

  • Selective – pass facts and/or derived keys to subscribe to specific state
  • Subscribe all – omit keys to subscribe to all facts and derivations
function useDirective<M extends ModuleSchema>(
  moduleDef: ModuleDef<M>,
  config?: {
    facts?: string[];
    derived?: string[];
    plugins?: Plugin[];
    debug?: DebugConfig;
    errorBoundary?: ErrorBoundaryConfig;
    tickMs?: number;
    zeroConfig?: boolean;
    initialFacts?: Record<string, any>;
    status?: boolean;
  },
): {
  system: System<M>;
  facts: ShallowRef<InferFacts<M>>;
  derived: ShallowRef<InferDerivations<M>>;
  events: System<M>["events"];
  dispatch: (event: InferEvents<M>) => void;
  statusPlugin?: StatusPlugin;
}

useDirective Usage

<script setup>
import { useDirective } from '@directive-run/vue';
import { counterModule } from './counter-module';

// Subscribe all: omit keys for everything
const { facts, derived, events, dispatch } = useDirective(counterModule, {
  debug: { timeTravel: true },
  status: true,
});
</script>

<template>
  <p>Count: {{ facts.count }}</p>
  <p>Double: {{ derived.double }}</p>
  <button @click="events.increment()">+1</button>
</template>

Selective subscriptions:

<script setup>
import { useDirective } from '@directive-run/vue';
import { counterModule } from './counter-module';

// Selective: subscribe to specific keys only
const { facts, derived, dispatch } = useDirective(counterModule, {
  facts: ['count'],
  derived: ['doubled'],
});
</script>

<template>
  <p>{{ facts.count }}</p>
  <button @click="dispatch({ type: 'increment' })">+1</button>
</template>

createTypedHooks

Factory function that returns typed versions of the core composables for a specific module schema. Provides full autocompletion for fact keys, derivation IDs, and event types. Returned hooks take system as the first parameter.

function createTypedHooks<M extends ModuleSchema>(): {
  useFact: <K extends keyof InferFacts<M>>(system: System<M>, factKey: K) => Ref<InferFacts<M>[K] | undefined>;
  useDerived: <K extends keyof InferDerivations<M>>(system: System<M>, derivationId: K) => Ref<InferDerivations<M>[K]>;
  useDispatch: (system: System<M>) => (event: InferEvents<M>) => void;
  useEvents: (system: System<M>) => System<M>["events"];
}

createTypedHooks Usage

// typed-hooks.ts
import { createTypedHooks } from '@directive-run/vue';
import type { MyModuleSchema } from './my-module';

// Create typed composables – full autocomplete for fact keys and event types
export const { useFact, useDerived, useDispatch, useEvents } =
  createTypedHooks<MyModuleSchema>();
<script setup>
import { useFact, useDerived } from './typed-hooks';
import { system } from './system';

// Fully typed – factKey autocompletes, return type inferred
const count = useFact(system, 'count');       // Ref<number | undefined>

// Derivation types are also fully inferred
const total = useDerived(system, 'cartTotal'); // Ref<number>
</script>

useTimeTravel

Reactive time-travel composable. Returns a ShallowRef that updates when snapshots are taken or navigation occurs. Returns null when time-travel is disabled.

function useTimeTravel(system: System): ShallowRef<TimeTravelState | null>

useTimeTravel Returns

interface TimeTravelState {
  canUndo: boolean;
  canRedo: boolean;
  undo: () => void;
  redo: () => void;
  currentIndex: number;
  totalSnapshots: number;
}

useTimeTravel Usage

<script setup>
import { useTimeTravel } from '@directive-run/vue';
import { system } from './system';

// Get reactive time-travel controls (null when disabled)
const tt = useTimeTravel(system);
</script>

<template>
  <div v-if="tt">
    <button :disabled="!tt.canUndo" @click="tt.undo()">Undo</button>
    <button :disabled="!tt.canRedo" @click="tt.redo()">Redo</button>
    <span>{{ tt.currentIndex + 1 }} / {{ tt.totalSnapshots }}</span>
  </div>
</template>

Enable time-travel in the system configuration:

// Enable time-travel debugging on the system
const system = createSystem({
  module: myModule,
  debug: { timeTravel: true, maxSnapshots: 100 },
});

shallowEqual

Utility function for shallow equality comparison. Useful as the equalityFn parameter for useSelector.

function shallowEqual(a: unknown, b: unknown): boolean

shallowEqual Usage

<script setup>
import { useSelector, shallowEqual } from '@directive-run/vue';
import { system } from './system';

// Use shallowEqual to prevent updates when x/y values haven't changed
const coords = useSelector(
  system,
  (facts) => ({ x: facts.x, y: facts.y }),
  shallowEqual,
);
</script>

Next Steps

Previous
React Hooks

We care about your data. We'll never share your email.

Powered by Directive. This signup uses a Directive module with facts, derivations, constraints, and resolvers – zero useState, zero useEffect. Read how it works

Directive - Constraint-Driven State Management for TypeScript