Plugins extend Directive systems with cross-cutting functionality like logging, persistence, and debugging. They hook into every stage of the system lifecycle without modifying core behavior.
Using Plugins
Add plugins when creating a system:
import{ createSystem }from'@directive-run/core';import{ loggingPlugin, devtoolsPlugin }from'@directive-run/core/plugins';// Pass plugins as an array –they hook into the system's lifecycle automaticallyconst system =createSystem({ module: myModule, plugins:[loggingPlugin(),devtoolsPlugin(),],});// Plugins are active as soon as the system startssystem.start();
Built-in Plugins
Plugin
Import
Purpose
loggingPlugin(options?)
@directive-run/core/plugins
Console logging for state changes, resolvers, and events
devtoolsPlugin(options?)
@directive-run/core/plugins
Browser devtools integration via window.__DIRECTIVE__
persistencePlugin(options)
@directive-run/core/plugins
Save and restore facts to storage
performancePlugin(options?)
@directive-run/core/plugins
Track constraint, resolver, effect, and reconciliation metrics
Plugin Order
Plugins execute in registration order. Put logging first to capture all events:
plugins:[// Logging first so it captures events from every plugin that followsloggingPlugin(),// Persistence restores saved state during init, before the engine runspersistencePlugin({ storage: localStorage, key:'my-app'}),// DevTools last –it can inspect the fully initialized systemdevtoolsPlugin(),]
If two plugins with the same name are registered, the second replaces the first with a warning.
Conditional Plugins
Enable plugins based on environment:
// Start with the plugins you always wantconst plugins =[persistencePlugin({ key:'my-app'}),];// Add dev-only plugins conditionally so they're tree-shaken from productionif(process.env.NODE_ENV==='development'){// unshift puts logging first so it captures everything plugins.unshift(loggingPlugin()); plugins.push(devtoolsPlugin());}const system =createSystem({ module: myModule, plugins,});system.start();
Complete Hook Reference
Every hook is optional. Implement only the ones you need.
Lifecycle Hooks
Hook
Parameters
When it fires
onInit
(system)
Once on creation, before start(). Only async hook.
A requirement is canceled (constraint no longer active)
Resolver Hooks
Hook
Parameters
When it fires
onResolverStart
(resolver, req)
A resolver begins processing a requirement
onResolverComplete
(resolver, req, duration)
A resolver succeeds (duration in ms)
onResolverError
(resolver, req, error)
A resolver fails after all retries exhausted
onResolverRetry
(resolver, req, attempt)
A resolver retries after failure
onResolverCancel
(resolver, req)
A resolver is canceled (requirement no longer needed)
Effect Hooks
Hook
Parameters
When it fires
onEffectRun
(id)
An effect executes
onEffectError
(id, error)
An effect throws an error
Time-Travel Hooks
Hook
Parameters
When it fires
onSnapshot
(snapshot: { id, timestamp, facts, trigger })
A time-travel snapshot is captured
onTimeTravel
(from, to)
Time-travel navigation occurs
Error Boundary Hooks
Hook
Parameters
When it fires
onError
(error: DirectiveError)
Any error occurs in the system
onErrorRecovery
(error, strategy: RecoveryStrategy)
Error recovery is attempted
Error Handling
Errors thrown inside plugin hooks are caught and logged. A failing plugin never breaks the system or blocks other plugins from running:
const flakyPlugin: Plugin ={ name:'flaky',// Even if a hook throws, the system catches it and keeps runningonFactSet:(key, value)=>{thrownewError('Plugin crash');// Caught internally –other plugins and the system continue normally},};
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