Resources
•4 min read
FAQ
Common questions about Directive, answered.
Getting Started
What is Directive?
Directive is a constraint-driven state management library for TypeScript. Instead of manually dispatching actions or calling setters, you declare what must be true (constraints) and how to make it true (resolvers). The runtime automatically orchestrates state changes.
When should I use Directive?
Directive excels when your application has:
- Complex interdependencies between state values
- Async operations that depend on each other
- Business rules that must always be satisfied
- AI agents or workflows with multiple steps
For simple state (a counter, a toggle), Directive might be overkill. Consider starting with simpler solutions and migrating when complexity grows.
How is Directive different from Redux/Zustand?
| Aspect | Redux/Zustand | Directive |
|---|---|---|
| State updates | Imperative (dispatch actions) | Declarative (constraints) |
| Dependencies | Manual tracking | Automatic |
| Async handling | Middleware (thunks, sagas) | Built-in resolvers |
| Side effects | External (middleware) | First-class (effects) |
| Time-travel | Plugin required | Built-in |
How is Directive different from XState?
XState models explicit state machines with defined transitions. Directive models requirements that must be satisfied. XState is great for UI flows; Directive is great for data orchestration.
Constraints & Resolvers
Why didn't my constraint fire?
Common reasons:
- The
whencondition is false - Check that all conditions are met - Another constraint has higher priority - Check priority values
- The requirement is already being resolved - Resolvers dedupe by default
- The system hasn't settled - Call
await system.settle()to wait
Debug with the devtools plugin:
import { devtoolsPlugin } from '@directive-run/core/plugins';
// Attach devtools to see constraint evaluations and resolver activity
const system = createSystem({
module: myModule,
plugins: [devtoolsPlugin()],
});
Why is my resolver running multiple times?
- No deduplication key - Add a
keyfunction to your resolver - The constraint fires repeatedly - Check if your
whencondition oscillates - Facts are changing during resolution - Use
context.factscarefully
resolvers: {
fetchUser: {
requirement: "FETCH_USER",
// Dedupe by user ID so concurrent requests for the same user collapse
key: (req) => `fetch-user-${req.payload.userId}`,
resolve: async (req, context) => {
// ...
},
},
},
What's the difference between effects and resolvers?
| Effects | Resolvers |
|---|---|
| Fire-and-forget | Fulfill requirements |
| Run on fact changes | Run when constraints activate |
| No retry/timeout | Built-in retry/timeout |
| Synchronous or async | Always async |
Use effects for: logging, analytics, DOM updates, notifications. Use resolvers for: API calls, data loading, state transitions.
Performance
Are derivations expensive?
Derivations use dependency tracking and memoization. They only recompute when their dependencies change. For complex computations, they're often faster than manual memoization because tracking is automatic.
How many constraints are too many?
There's no hard limit. Constraint evaluation is O(n) where n is the number of constraints. In practice, hundreds of constraints work fine. If you have thousands, consider splitting into multiple modules.
Does Directive work with Server Components?
Yes! Directive is SSR-ready:
// Server: run the system and capture its state as a serializable snapshot
const system = createSystem({ module: myModule });
system.start();
const snapshot = system.getSnapshot();
// Client: restore from the server snapshot – no duplicate fetches
const clientSystem = createSystem({
module: myModule,
initialFacts: snapshot.facts,
});
clientSystem.start();
TypeScript
How do I get full type inference?
Use the t type builders in your schema:
import { t } from '@directive-run/core';
const myModule = createModule("app", {
schema: {
facts: {
userId: t.number(),
user: t.object<User>().nullable(),
status: t.literal("idle", "loading", "error"), // Union of string literals
},
},
// Types flow automatically to constraints, resolvers, and hooks
});
Why are my types not inferring?
Common issues:
- Missing explicit type on object - Use
t.object<MyType>() - Circular references - Break cycles with explicit types
- Complex unions - Simplify or use
t.custom<MyType>()
React Integration
Do I need to wrap my app in a Provider?
No. Directive uses a system-first pattern where hooks take the system as their first parameter. No provider or context is needed:
import { useFact } from '@directive-run/react';
// No Provider wrapper needed – pass the system directly
function MyComponent() {
const count = useFact(system, "count");
return <p>{count}</p>;
}
Why is my component re-rendering too often?
- Reading too broadly - Select only the specific facts you need
- Missing selector - Use
useSelectorfor fine-grained subscriptions - Derivation recreating - Check derivation dependencies
// Bad: re-renders on ANY change to the user object
const user = useFact(system, "user");
// Good: re-renders only when the name changes
const userName = useSelector(system, (state) => state.user?.name);
Getting Help
Where can I ask questions?
- GitHub Issues: github.com/directive-run/directive/issues
- GitHub Discussions: github.com/directive-run/directive/discussions
- Discord: discord.gg/directive
How do I report a bug?
- Check if it's already reported in GitHub Issues
- Create a minimal reproduction
- Include: Directive version, TypeScript version, error message, steps to reproduce
How do I contribute?
See our Contributing Guide. We welcome:
- Bug fixes
- Documentation improvements
- Feature proposals (open an issue first)
- Example applications
Next Steps
- Troubleshooting Guide - Common errors and solutions
- Glossary - Key terms and definitions
- Core Concepts - Deep dive into the mental model

