@directive-run/knowledge 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +63 -0
- package/ai/ai-adapters.md +250 -0
- package/ai/ai-agents-streaming.md +269 -0
- package/ai/ai-budget-resilience.md +235 -0
- package/ai/ai-communication.md +281 -0
- package/ai/ai-debug-observability.md +243 -0
- package/ai/ai-guardrails-memory.md +332 -0
- package/ai/ai-mcp-rag.md +288 -0
- package/ai/ai-multi-agent.md +274 -0
- package/ai/ai-orchestrator.md +227 -0
- package/ai/ai-security.md +293 -0
- package/ai/ai-tasks.md +261 -0
- package/ai/ai-testing-evals.md +378 -0
- package/api-skeleton.md +5 -0
- package/core/anti-patterns.md +382 -0
- package/core/constraints.md +263 -0
- package/core/core-patterns.md +228 -0
- package/core/error-boundaries.md +322 -0
- package/core/multi-module.md +315 -0
- package/core/naming.md +283 -0
- package/core/plugins.md +344 -0
- package/core/react-adapter.md +262 -0
- package/core/resolvers.md +357 -0
- package/core/schema-types.md +262 -0
- package/core/system-api.md +271 -0
- package/core/testing.md +257 -0
- package/core/time-travel.md +238 -0
- package/dist/index.cjs +111 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +102 -0
- package/dist/index.js.map +1 -0
- package/examples/ab-testing.ts +385 -0
- package/examples/ai-checkpoint.ts +509 -0
- package/examples/ai-guardrails.ts +319 -0
- package/examples/ai-orchestrator.ts +589 -0
- package/examples/async-chains.ts +287 -0
- package/examples/auth-flow.ts +371 -0
- package/examples/batch-resolver.ts +341 -0
- package/examples/checkers.ts +589 -0
- package/examples/contact-form.ts +176 -0
- package/examples/counter.ts +393 -0
- package/examples/dashboard-loader.ts +512 -0
- package/examples/debounce-constraints.ts +105 -0
- package/examples/dynamic-modules.ts +293 -0
- package/examples/error-boundaries.ts +430 -0
- package/examples/feature-flags.ts +220 -0
- package/examples/form-wizard.ts +347 -0
- package/examples/fraud-analysis.ts +663 -0
- package/examples/goal-heist.ts +341 -0
- package/examples/multi-module.ts +57 -0
- package/examples/newsletter.ts +241 -0
- package/examples/notifications.ts +210 -0
- package/examples/optimistic-updates.ts +317 -0
- package/examples/pagination.ts +260 -0
- package/examples/permissions.ts +337 -0
- package/examples/provider-routing.ts +403 -0
- package/examples/server.ts +316 -0
- package/examples/shopping-cart.ts +422 -0
- package/examples/sudoku.ts +630 -0
- package/examples/theme-locale.ts +204 -0
- package/examples/time-machine.ts +225 -0
- package/examples/topic-guard.ts +306 -0
- package/examples/url-sync.ts +333 -0
- package/examples/websocket.ts +404 -0
- package/package.json +65 -0
package/core/plugins.md
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# Plugins
|
|
2
|
+
|
|
3
|
+
Plugins extend Directive systems with cross-cutting functionality like logging, persistence, devtools, and resilience patterns.
|
|
4
|
+
|
|
5
|
+
## Decision Tree: "Which plugin do I need?"
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
What do you want?
|
|
9
|
+
├── See state changes in console → loggingPlugin()
|
|
10
|
+
├── Connect to browser DevTools → devtoolsPlugin()
|
|
11
|
+
├── Persist state across reloads → persistencePlugin(config)
|
|
12
|
+
├── Protect resolvers from cascading failures → createCircuitBreaker(config)
|
|
13
|
+
├── Track metrics, traces, alerts → createObservability(config)
|
|
14
|
+
└── Custom cross-cutting behavior → Write a custom plugin
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Using Built-In Plugins
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { createSystem } from "@directive-run/core";
|
|
21
|
+
import {
|
|
22
|
+
devtoolsPlugin,
|
|
23
|
+
loggingPlugin,
|
|
24
|
+
persistencePlugin,
|
|
25
|
+
} from "@directive-run/core/plugins";
|
|
26
|
+
|
|
27
|
+
const system = createSystem({
|
|
28
|
+
module: myModule,
|
|
29
|
+
plugins: [
|
|
30
|
+
devtoolsPlugin(),
|
|
31
|
+
loggingPlugin({ verbose: true }),
|
|
32
|
+
persistencePlugin({
|
|
33
|
+
key: "my-app-state",
|
|
34
|
+
storage: localStorage,
|
|
35
|
+
}),
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Plugins are applied in order. Place logging first to capture all events including those from other plugins.
|
|
41
|
+
|
|
42
|
+
## loggingPlugin
|
|
43
|
+
|
|
44
|
+
Logs state changes, requirements, and resolutions to the console.
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { loggingPlugin } from "@directive-run/core/plugins";
|
|
48
|
+
|
|
49
|
+
// Default — logs facts changes and resolver start/complete
|
|
50
|
+
loggingPlugin()
|
|
51
|
+
|
|
52
|
+
// Verbose — logs everything including derivation recomputation and constraint evaluation
|
|
53
|
+
loggingPlugin({ verbose: true })
|
|
54
|
+
|
|
55
|
+
// Custom filter — only log specific events
|
|
56
|
+
loggingPlugin({
|
|
57
|
+
filter: (event) => {
|
|
58
|
+
// Only log resolver events
|
|
59
|
+
if (event.type === "resolution:start" || event.type === "resolution:complete") {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return false;
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## devtoolsPlugin
|
|
69
|
+
|
|
70
|
+
Connects to the Directive DevTools browser extension for visual state inspection.
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { devtoolsPlugin } from "@directive-run/core/plugins";
|
|
74
|
+
|
|
75
|
+
// Default
|
|
76
|
+
devtoolsPlugin()
|
|
77
|
+
|
|
78
|
+
// With options
|
|
79
|
+
devtoolsPlugin({
|
|
80
|
+
name: "My App", // Name shown in DevTools
|
|
81
|
+
maxAge: 50, // Max actions to keep in history
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Only enable in development. The plugin is a no-op if the DevTools extension is not installed.
|
|
86
|
+
|
|
87
|
+
## persistencePlugin
|
|
88
|
+
|
|
89
|
+
Persists fact state to storage. Restores on system creation.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { persistencePlugin } from "@directive-run/core/plugins";
|
|
93
|
+
|
|
94
|
+
// localStorage (default)
|
|
95
|
+
persistencePlugin({
|
|
96
|
+
key: "my-app-state",
|
|
97
|
+
storage: localStorage,
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// sessionStorage — cleared when tab closes
|
|
101
|
+
persistencePlugin({
|
|
102
|
+
key: "session-state",
|
|
103
|
+
storage: sessionStorage,
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// Custom storage adapter
|
|
107
|
+
persistencePlugin({
|
|
108
|
+
key: "my-app",
|
|
109
|
+
storage: {
|
|
110
|
+
getItem: (key) => myCustomStore.get(key),
|
|
111
|
+
setItem: (key, value) => myCustomStore.set(key, value),
|
|
112
|
+
removeItem: (key) => myCustomStore.delete(key),
|
|
113
|
+
},
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// Selective persistence — only persist certain facts
|
|
117
|
+
persistencePlugin({
|
|
118
|
+
key: "my-app",
|
|
119
|
+
storage: localStorage,
|
|
120
|
+
include: ["user", "preferences"], // Only these facts
|
|
121
|
+
// OR
|
|
122
|
+
exclude: ["tempData", "sessionId"], // Everything except these
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// Versioning — handle schema changes
|
|
126
|
+
persistencePlugin({
|
|
127
|
+
key: "my-app",
|
|
128
|
+
storage: localStorage,
|
|
129
|
+
version: 2,
|
|
130
|
+
migrate: (oldState, oldVersion) => {
|
|
131
|
+
if (oldVersion === 1) {
|
|
132
|
+
return {
|
|
133
|
+
...oldState,
|
|
134
|
+
newField: "default",
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return oldState;
|
|
139
|
+
},
|
|
140
|
+
})
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## createCircuitBreaker
|
|
144
|
+
|
|
145
|
+
Wraps resolvers with circuit breaker pattern for resilience.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { createCircuitBreaker } from "@directive-run/core/plugins";
|
|
149
|
+
|
|
150
|
+
const system = createSystem({
|
|
151
|
+
module: myModule,
|
|
152
|
+
plugins: [
|
|
153
|
+
createCircuitBreaker({
|
|
154
|
+
// How many failures before opening the circuit
|
|
155
|
+
failureThreshold: 5,
|
|
156
|
+
|
|
157
|
+
// How long to wait before trying again (ms)
|
|
158
|
+
resetTimeout: 30000,
|
|
159
|
+
|
|
160
|
+
// Optional: only apply to specific resolver types
|
|
161
|
+
include: ["FETCH_DATA", "SYNC_REMOTE"],
|
|
162
|
+
|
|
163
|
+
// Optional: callback when circuit opens
|
|
164
|
+
onOpen: (resolverType) => {
|
|
165
|
+
console.warn(`Circuit opened for ${resolverType}`);
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
// Optional: callback when circuit closes
|
|
169
|
+
onClose: (resolverType) => {
|
|
170
|
+
console.log(`Circuit closed for ${resolverType}`);
|
|
171
|
+
},
|
|
172
|
+
}),
|
|
173
|
+
],
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Circuit breaker states: **Closed** (normal) -> **Open** (failing, rejects immediately) -> **Half-Open** (testing one request).
|
|
178
|
+
|
|
179
|
+
## createObservability
|
|
180
|
+
|
|
181
|
+
Metrics, tracing, and alerting for production systems.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import { createObservability } from "@directive-run/core/plugins";
|
|
185
|
+
|
|
186
|
+
const system = createSystem({
|
|
187
|
+
module: myModule,
|
|
188
|
+
plugins: [
|
|
189
|
+
createObservability({
|
|
190
|
+
metrics: {
|
|
191
|
+
enabled: true,
|
|
192
|
+
// Track resolver duration, constraint evaluation count, etc.
|
|
193
|
+
},
|
|
194
|
+
tracing: {
|
|
195
|
+
enabled: true,
|
|
196
|
+
// Trace resolver execution with spans
|
|
197
|
+
},
|
|
198
|
+
alerts: {
|
|
199
|
+
enabled: true,
|
|
200
|
+
onAlert: (alert) => {
|
|
201
|
+
// Send to monitoring service
|
|
202
|
+
sendToDatadog(alert);
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
}),
|
|
206
|
+
],
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Plugin Lifecycle Hooks
|
|
211
|
+
|
|
212
|
+
Plugins hook into the system lifecycle. Use these to build custom plugins.
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import type { DirectivePlugin } from "@directive-run/core";
|
|
216
|
+
|
|
217
|
+
const myPlugin: DirectivePlugin = {
|
|
218
|
+
name: "my-custom-plugin",
|
|
219
|
+
|
|
220
|
+
// System lifecycle
|
|
221
|
+
onInit: (system) => {
|
|
222
|
+
// Called when system is created
|
|
223
|
+
},
|
|
224
|
+
onStart: (system) => {
|
|
225
|
+
// Called when system.start() is invoked
|
|
226
|
+
},
|
|
227
|
+
onStop: (system) => {
|
|
228
|
+
// Called when system.stop() is invoked
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
// State tracking
|
|
232
|
+
onSnapshot: (snapshot) => {
|
|
233
|
+
// Called after every fact mutation
|
|
234
|
+
// snapshot.facts contains current state
|
|
235
|
+
// snapshot.changedKeys lists what changed
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
// Requirement pipeline
|
|
239
|
+
onRequirementEmitted: (requirement) => {
|
|
240
|
+
// Called when a constraint emits a requirement
|
|
241
|
+
// requirement.type, requirement.id, payload
|
|
242
|
+
},
|
|
243
|
+
onResolutionStart: (resolution) => {
|
|
244
|
+
// Called when a resolver begins executing
|
|
245
|
+
// resolution.resolverId, resolution.requirement
|
|
246
|
+
},
|
|
247
|
+
onResolutionComplete: (resolution) => {
|
|
248
|
+
// Called when a resolver finishes (success or failure)
|
|
249
|
+
// resolution.resolverId, resolution.duration, resolution.error?
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
// Error handling
|
|
253
|
+
onError: (error, context) => {
|
|
254
|
+
// Called on any error in the system
|
|
255
|
+
// context.source: "resolver" | "constraint" | "effect" | "derivation"
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const system = createSystem({
|
|
260
|
+
module: myModule,
|
|
261
|
+
plugins: [myPlugin],
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Common Mistakes
|
|
266
|
+
|
|
267
|
+
### Enabling devtools in production
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
// WRONG — devtools overhead in production
|
|
271
|
+
const system = createSystem({
|
|
272
|
+
module: myModule,
|
|
273
|
+
plugins: [devtoolsPlugin()],
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// CORRECT — conditional on environment
|
|
277
|
+
const plugins = [];
|
|
278
|
+
if (process.env.NODE_ENV === "development") {
|
|
279
|
+
plugins.push(devtoolsPlugin());
|
|
280
|
+
plugins.push(loggingPlugin({ verbose: true }));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const system = createSystem({
|
|
284
|
+
module: myModule,
|
|
285
|
+
plugins,
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Persistence without versioning
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
// WRONG — schema changes break existing users
|
|
293
|
+
persistencePlugin({
|
|
294
|
+
key: "app-state",
|
|
295
|
+
storage: localStorage,
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
// CORRECT — version and migrate
|
|
299
|
+
persistencePlugin({
|
|
300
|
+
key: "app-state",
|
|
301
|
+
storage: localStorage,
|
|
302
|
+
version: 1,
|
|
303
|
+
migrate: (old, version) => old,
|
|
304
|
+
})
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Plugin order matters
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
// WRONG — logging misses events from persistence restore
|
|
311
|
+
const system = createSystem({
|
|
312
|
+
module: myModule,
|
|
313
|
+
plugins: [
|
|
314
|
+
persistencePlugin({ key: "app", storage: localStorage }),
|
|
315
|
+
loggingPlugin(), // Misses restore events
|
|
316
|
+
],
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// CORRECT — logging first to capture everything
|
|
320
|
+
const system = createSystem({
|
|
321
|
+
module: myModule,
|
|
322
|
+
plugins: [
|
|
323
|
+
loggingPlugin(),
|
|
324
|
+
persistencePlugin({ key: "app", storage: localStorage }),
|
|
325
|
+
],
|
|
326
|
+
});
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Persisting sensitive or transient data
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
// WRONG — persists auth tokens and loading state
|
|
333
|
+
persistencePlugin({
|
|
334
|
+
key: "app",
|
|
335
|
+
storage: localStorage,
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
// CORRECT — exclude sensitive and transient facts
|
|
339
|
+
persistencePlugin({
|
|
340
|
+
key: "app",
|
|
341
|
+
storage: localStorage,
|
|
342
|
+
exclude: ["authToken", "isLoading", "error"],
|
|
343
|
+
})
|
|
344
|
+
```
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# React Adapter
|
|
2
|
+
|
|
3
|
+
The React adapter connects Directive systems to React components. Import from `@directive-run/react`.
|
|
4
|
+
|
|
5
|
+
## Decision Tree: "How do I use Directive in React?"
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
What are you building?
|
|
9
|
+
├── Read system state in a component → useSelector(system, selector)
|
|
10
|
+
├── Dispatch events from a component → useEvent(system)
|
|
11
|
+
├── Create a system scoped to a component → useSystem(config)
|
|
12
|
+
├── Share a system across components → DirectiveProvider + useDirectiveContext()
|
|
13
|
+
└── Global system shared by entire app → Create outside React, use useSelector
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Setup: System Outside React (Recommended)
|
|
17
|
+
|
|
18
|
+
Create the system outside of React. Components subscribe to it.
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// system.ts — created once, imported anywhere
|
|
22
|
+
import { createSystem } from "@directive-run/core";
|
|
23
|
+
import { counterModule } from "./counter-module";
|
|
24
|
+
|
|
25
|
+
export const system = createSystem({ module: counterModule });
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// Counter.tsx
|
|
30
|
+
import { useSelector, useEvent } from "@directive-run/react";
|
|
31
|
+
import { system } from "./system";
|
|
32
|
+
|
|
33
|
+
function Counter() {
|
|
34
|
+
// Subscribe to derived state — re-renders only when value changes
|
|
35
|
+
const count = useSelector(system, (s) => s.facts.count);
|
|
36
|
+
const doubled = useSelector(system, (s) => s.derive.doubled);
|
|
37
|
+
|
|
38
|
+
// Get event dispatcher
|
|
39
|
+
const events = useEvent(system);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div>
|
|
43
|
+
<p>Count: {count}</p>
|
|
44
|
+
<p>Doubled: {doubled}</p>
|
|
45
|
+
<button onClick={() => events.increment()}>+1</button>
|
|
46
|
+
<button onClick={() => events.reset()}>Reset</button>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## useSelector
|
|
53
|
+
|
|
54
|
+
Subscribes to system state. Re-renders the component only when the selected value changes (shallow comparison).
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { useSelector } from "@directive-run/react";
|
|
58
|
+
|
|
59
|
+
function UserProfile() {
|
|
60
|
+
// Select a single fact
|
|
61
|
+
const name = useSelector(system, (s) => s.facts.userName);
|
|
62
|
+
|
|
63
|
+
// Select a derivation
|
|
64
|
+
const isAdmin = useSelector(system, (s) => s.derive.isAdmin);
|
|
65
|
+
|
|
66
|
+
// Select a computed value from multiple facts
|
|
67
|
+
const summary = useSelector(system, (s) => ({
|
|
68
|
+
name: s.facts.userName,
|
|
69
|
+
role: s.facts.role,
|
|
70
|
+
isAdmin: s.derive.isAdmin,
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
return <div>{summary.name} ({summary.role})</div>;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Multi-Module System
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
const system = createSystem({
|
|
81
|
+
modules: { auth: authModule, cart: cartModule },
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
function Header() {
|
|
85
|
+
const token = useSelector(system, (s) => s.facts.auth.token);
|
|
86
|
+
const itemCount = useSelector(system, (s) => s.derive.cart.itemCount);
|
|
87
|
+
|
|
88
|
+
return <header>Items: {itemCount}</header>;
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## useEvent
|
|
93
|
+
|
|
94
|
+
Returns the system's event dispatcher. Stable reference (does not cause re-renders).
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { useEvent } from "@directive-run/react";
|
|
98
|
+
|
|
99
|
+
function LoginForm() {
|
|
100
|
+
const events = useEvent(system);
|
|
101
|
+
|
|
102
|
+
const handleSubmit = (email: string, password: string) => {
|
|
103
|
+
events.login({ email, password });
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return <form onSubmit={() => handleSubmit("a@b.com", "pass")}>...</form>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Multi-module
|
|
110
|
+
function CartActions() {
|
|
111
|
+
const events = useEvent(system);
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<button onClick={() => events.cart.addItem({ productId: "p1" })}>
|
|
115
|
+
Add to Cart
|
|
116
|
+
</button>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## useSystem
|
|
122
|
+
|
|
123
|
+
Creates and manages a system's lifecycle within a React component. The system is created on mount and destroyed on unmount.
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { useSystem, useSelector, useEvent } from "@directive-run/react";
|
|
127
|
+
|
|
128
|
+
function GameBoard() {
|
|
129
|
+
// System created on mount, destroyed on unmount
|
|
130
|
+
const gameSystem = useSystem({
|
|
131
|
+
module: gameModule,
|
|
132
|
+
debug: { timeTravel: true },
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const score = useSelector(gameSystem, (s) => s.facts.score);
|
|
136
|
+
const events = useEvent(gameSystem);
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<div>
|
|
140
|
+
<p>Score: {score}</p>
|
|
141
|
+
<button onClick={() => events.move({ direction: "up" })}>Up</button>
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Use `useSystem` when the system's lifecycle matches a component's lifecycle (e.g., a game board, a wizard, a modal form). For app-wide state, create the system outside React.
|
|
148
|
+
|
|
149
|
+
## DirectiveProvider and useDirectiveContext
|
|
150
|
+
|
|
151
|
+
Share a system through React context.
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { DirectiveProvider, useDirectiveContext, useSelector, useEvent } from "@directive-run/react";
|
|
155
|
+
|
|
156
|
+
// Provide the system at the top of your tree
|
|
157
|
+
function App() {
|
|
158
|
+
return (
|
|
159
|
+
<DirectiveProvider system={system}>
|
|
160
|
+
<Dashboard />
|
|
161
|
+
</DirectiveProvider>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Consume the system anywhere below
|
|
166
|
+
function Dashboard() {
|
|
167
|
+
const system = useDirectiveContext();
|
|
168
|
+
const stats = useSelector(system, (s) => s.derive.dashboardStats);
|
|
169
|
+
|
|
170
|
+
return <div>{stats.totalUsers} users</div>;
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## CRITICAL: Hooks That DO NOT Exist
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
// WRONG — useDirective() does not exist. This is a common hallucination.
|
|
178
|
+
const { facts, derive, events } = useDirective(system);
|
|
179
|
+
|
|
180
|
+
// CORRECT — use useSelector for state, useEvent for actions
|
|
181
|
+
const count = useSelector(system, (s) => s.facts.count);
|
|
182
|
+
const events = useEvent(system);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Common Mistakes
|
|
186
|
+
|
|
187
|
+
### Creating the system inside a component without useSystem
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
// WRONG — creates a new system on every render
|
|
191
|
+
function Counter() {
|
|
192
|
+
const system = createSystem({ module: counterModule }); // New system each render!
|
|
193
|
+
const count = useSelector(system, (s) => s.facts.count);
|
|
194
|
+
|
|
195
|
+
return <div>{count}</div>;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// CORRECT — create outside the component
|
|
199
|
+
const system = createSystem({ module: counterModule });
|
|
200
|
+
|
|
201
|
+
function Counter() {
|
|
202
|
+
const count = useSelector(system, (s) => s.facts.count);
|
|
203
|
+
|
|
204
|
+
return <div>{count}</div>;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ALSO CORRECT — useSystem manages lifecycle
|
|
208
|
+
function Counter() {
|
|
209
|
+
const system = useSystem({ module: counterModule });
|
|
210
|
+
const count = useSelector(system, (s) => s.facts.count);
|
|
211
|
+
|
|
212
|
+
return <div>{count}</div>;
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Selecting too much state (causes unnecessary re-renders)
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
// WRONG — re-renders on ANY fact change
|
|
220
|
+
const allFacts = useSelector(system, (s) => s.facts);
|
|
221
|
+
|
|
222
|
+
// CORRECT — select only what you need
|
|
223
|
+
const name = useSelector(system, (s) => s.facts.userName);
|
|
224
|
+
const count = useSelector(system, (s) => s.facts.count);
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Mutating facts directly in event handlers
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
// WRONG — bypass the event system
|
|
231
|
+
function Counter() {
|
|
232
|
+
const count = useSelector(system, (s) => s.facts.count);
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<button onClick={() => { system.facts.count += 1; }}>
|
|
236
|
+
{count}
|
|
237
|
+
</button>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// CORRECT — use events for intent-driven mutations
|
|
242
|
+
function Counter() {
|
|
243
|
+
const count = useSelector(system, (s) => s.facts.count);
|
|
244
|
+
const events = useEvent(system);
|
|
245
|
+
|
|
246
|
+
return (
|
|
247
|
+
<button onClick={() => events.increment()}>
|
|
248
|
+
{count}
|
|
249
|
+
</button>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Casting values from useSelector
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
// WRONG — unnecessary type casting
|
|
258
|
+
const profile = useSelector(system, (s) => s.facts.profile as UserProfile);
|
|
259
|
+
|
|
260
|
+
// CORRECT — types are inferred from the module schema
|
|
261
|
+
const profile = useSelector(system, (s) => s.facts.profile);
|
|
262
|
+
```
|