Skip to main content

Core API

4 min read

Core API Overview

The Core API is the foundation of every Directive application. Six primitives work together to express complex behavior declaratively.


Primitives

Directive has four core pillars (Facts, Derivations, Constraints, Resolvers) plus two supporting primitives (Effects, Events):

PrimitiveRoleAnalogy
FactsObservable stateDatabase rows
DerivationsComputed values from factsSQL views
ConstraintsRules that must be trueBusiness rules
ResolversHow to fulfill requirementsAPI calls, side effects
EffectsFire-and-forget reactionsLogging, analytics
EventsExternal inputsButton clicks, messages

They compose inside a module, which is created and run as a system. See Module & System for how it all fits together.


How They Relate

                                   ┌──────────────┐
              ┌───────────────────►│  Derivations │
              │                    └──────────────┘

    ┌─────────┴─────────┐          ┌──────────────┐             ┌──────────────┐
    │       Facts       │─────────►│  Constraints │────────────►│  Resolvers   │
    └─────────┬─────────┘          └──────────────┘             └──────┬───────┘
              │                                                        │
              │                    ┌──────────────┐                    │
              └───────────────────►│   Effects    │      mutate facts ◄┘
                                   └──────────────┘
  1. Facts hold state. When facts change, everything downstream re-evaluates.
  2. Derivations are auto-tracked computed values – they re-run only when their dependencies change.
  3. Constraints declare what must be true. When a constraint's when condition is met, it emits a requirement.
  4. Resolvers match requirements by type and execute async work to fulfill them.
  5. Effects run whenever their dependencies change – for logging, analytics, or other side effects.
  6. Events are typed dispatchers that mutate facts from the outside (UI, network, etc.).

Quick Example

import { createModule, createSystem, t } from '@directive-run/core';

// Define a counter module with typed schema, computed values, and events
const counter = createModule('counter', {
  // Declare the shape of all state and computed values up front
  schema: {
    facts: { count: t.number().default(0) },
    derivations: { doubled: t.number() },
    events: { increment: {}, decrement: {} },
    requirements: {},
  },

  // Set the initial state when the system starts
  init: (facts) => { facts.count = 0; },

  // Derivations auto-track their dependencies – no manual subscriptions
  derive: {
    doubled: (facts) => facts.count * 2,
  },

  // Events are typed dispatchers that mutate facts from the outside
  events: {
    increment: (facts) => { facts.count += 1; },
    decrement: (facts) => { facts.count -= 1; },
  },
});

// Wrap the module in a system to start the reconciliation loop
const system = createSystem({ module: counter });
system.start();

// Dispatch an event to increment, then read the derived value
system.events.increment();
console.log(system.read('doubled')); // 2

Helpers & Constants

Beyond the six primitives, the core package exports several helpers:

ExportPurpose
tSchema type builders (t.string(), t.number(), t.object<T>(), etc.)
req / forTypeRequirement construction helpers
RequirementSetSet-like container for managing requirements
BackoffConstants for retry backoff strategies (Backoff.Exponential, etc.)
constraint / whenFluent constraint builders
module / systemBuilder-pattern alternatives to createModule / createSystem
isSingleModuleSystem / isNamespacedSystemType guards for system mode
constraintFactory / resolverFactoryFactory helpers for typed cross-module constraints and resolvers
DirectiveErrorBase error class for all Directive errors

Lower-Level APIs

For advanced use cases (custom tooling, framework adapters, testing infrastructure), the core package also exports the individual manager constructors that createSystem composes internally:

ExportPurpose
createFacts / createFactsStore / createFactsProxyRaw facts store and proxy creation
createDerivationsManagerAuto-tracked derivation layer
createEffectsManagerSide-effect scheduling
createConstraintsManagerConstraint evaluation engine
createResolversManagerRequirement resolution with retry and batching
createPluginManagerPlugin lifecycle management
createErrorBoundaryManager / createRetryLaterManagerError handling and retry-later scheduling
createHistoryManager / createDisabledHistoryTime-travel snapshot management
createEngineThe reconciliation loop that ties everything together

Most applications should use createModule + createSystem instead. These lower-level APIs are useful when you need to compose your own system-like abstraction, build framework adapters, or write advanced test harnesses.


Tracking Utilities

The dependency tracking system is also exported for custom derivation-like patterns:

ExportPurpose
withTracking(fn)Run a function while recording which facts it reads
withoutTracking(fn)Run a function that reads facts without recording dependencies
isTracking()Check whether dependency tracking is currently active
getCurrentTracker / trackAccessLow-level tracker access for custom reactive primitives

Where to Start

  • New to Directive? Start with Facts to understand state, then Derivations for computed values.
  • Building features? Jump to Constraints and Resolvers for the declarative resolution loop.
  • Adding side effects? See Effects for fire-and-forget reactions.
  • Handling user input? See Events for typed dispatchers.
  • Putting it all together? See Module & System.
Previous
Choosing Primitives

Stay in the loop. Sign up for our newsletter.

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 Runtime for TypeScript | AI Guardrails & State Management