Skip to main content

State that resolves itself.

Declare what must be true. Define how to make it true. Let Directive handle when and how to orchestrate it all.

publish.module.ts
publish.schema.ts
Editor.tsx
import { createModule } from '@directive-run/core';
import { publishSchema } from './publish.schema';
export default createModule("publish", {
schema: publishSchema,
events: {
publish: (facts, { draft }) => {
facts.draft = draft;
},
},
constraints: {
validate: {
when: (facts) => facts.draft && !facts.validated,
require: { type: "VALIDATE" },
},
save: {
when: (facts) => facts.validated && !facts.saved,
require: { type: "SAVE" },
},
},
resolvers: {
validate: {
requirement: "VALIDATE",
resolve: async (req, { facts }) => {
facts.validated = await checkDraft(facts.draft);
},
},
save: {
requirement: "SAVE",
resolve: async (req, { facts }) => {
await saveDraft(facts.draft);
facts.saved = true;
},
},
},
});

2 min read

Directive - Constraint-Driven State Management


Try It in 30 Seconds

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

const counter = createModule("counter", {
  schema: { count: t.number() },
  init: (facts) => { facts.count = 0; },

  constraints: {
    tooLow: {
      when: (facts) => facts.count < 1,
      require: { type: "INCREMENT" },
    },
  },

  resolvers: {
    increment: {
      requirement: "INCREMENT",
      resolve: (req, context) => { context.facts.count += 1; },
    },
  },
});

const system = createSystem({ module: counter });
system.start();
console.log(system.facts.count); // 1

The constraint detected count < 1, emitted a requirement, and the resolver fulfilled it – automatically.


Why Directive?

Traditional state management focuses on how state changes. You write reducers, actions, sagas, and thunks - all describing the mechanics of state transitions.

Directive flips this around. You declare what must be true, and let the runtime figure out how and when to make it happen.

// Traditional: describe how to change state
dispatch({ type: 'FETCH_USER', id: 123 })
dispatch({ type: 'FETCH_USER_SUCCESS', user })

// Directive: declare what must be true
constraints: {
  needsUser: {
    when: (facts) => facts.userId > 0 && !facts.user,
    require: { type: "FETCH_USER" }
  }
}

Key Features

Auto-Tracking Derivations

No manual dependency arrays. Just access what you need - Directive tracks it automatically.

derive: {
  fullName: (facts) => `${facts.firstName} ${facts.lastName}`,
  greeting: (facts, derived) => `Hello, ${derived.fullName}!` // Composition works too
}

Built-in Resilience

Retry policies, timeouts, and error boundaries out of the box.

resolvers: {
  fetchUser: {
    requirement: "FETCH_USER",
    retry: { attempts: 3, backoff: "exponential" },
    timeout: 5000,
    resolve: async (req, context) => { /* ... */ }
  }
}

Time-Travel Debugging

Full state history. Go back, go forward, export, import.

const system = createSystem({
  module: myModule,
  history: true,
});

system.history.goBack();
system.history.goForward();

Framework Agnostic

First-class React support, with Vue, Svelte, Solid, and Lit adapters available.

import { useFact, useDerived } from '@directive-run/react';

function UserProfile({ system }) {
  const user = useFact(system, 'user');
  const fullName = useDerived(system, 'fullName');
  // ...
}

Built with Directive


Get Started

npm install @directive-run/core

Then check out the Quick Start guide to build your first module.

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 State Management for TypeScript