@directive-run/knowledge 1.14.0 → 1.16.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/README.md +50 -15
- package/ai/ai-adapters.md +7 -0
- package/ai/ai-agents-streaming.md +187 -149
- package/ai/ai-budget-resilience.md +305 -132
- package/ai/ai-communication.md +220 -197
- package/ai/ai-debug-observability.md +259 -173
- package/ai/ai-guardrails-memory.md +191 -153
- package/ai/ai-mcp-rag.md +204 -199
- package/ai/ai-multi-agent.md +254 -153
- package/ai/ai-orchestrator.md +353 -114
- package/ai/ai-security.md +287 -180
- package/ai/ai-tasks.md +8 -1
- package/ai/ai-testing-evals.md +363 -256
- package/core/anti-patterns.md +18 -2
- package/core/constraints.md +13 -2
- package/core/core-patterns.md +12 -0
- package/core/error-boundaries.md +8 -0
- package/core/history.md +7 -0
- package/core/multi-module.md +15 -3
- package/core/naming.md +128 -90
- package/core/plugins.md +7 -0
- package/core/react-adapter.md +256 -174
- package/core/resolvers.md +10 -0
- package/core/schema-types.md +8 -0
- package/core/system-api.md +10 -0
- package/core/testing.md +257 -143
- package/examples/checkers.ts +15 -16
- package/examples/contact-form.ts +2 -2
- package/examples/counter-react.ts +1 -1
- package/examples/counter-svelte.ts +1 -1
- package/examples/counter-vue.ts +1 -1
- package/examples/counter.ts +1 -1
- package/examples/data-triggers.ts +4 -4
- package/examples/feature-flags.ts +2 -2
- package/examples/form-wizard.ts +2 -2
- package/examples/newsletter.ts +2 -2
- package/examples/server.ts +2 -2
- package/examples/shopping-cart.ts +1 -1
- package/examples/topic-guard.ts +1 -1
- package/package.json +3 -3
- package/sitemap.md +17 -11
- package/examples/debounce-constraints.ts +0 -95
- package/examples/multi-module.ts +0 -58
package/core/react-adapter.md
CHANGED
|
@@ -1,294 +1,376 @@
|
|
|
1
1
|
# React Adapter
|
|
2
2
|
|
|
3
|
+
> Covers `@directive-run/react` — `useFact`, `useDerived`, `useEvents`, `useSelector`, `useDirective`, `createDirectiveContext`.
|
|
4
|
+
|
|
3
5
|
The React adapter connects Directive systems to React components. Import from `@directive-run/react`.
|
|
4
6
|
|
|
5
|
-
##
|
|
7
|
+
## Canonical pattern: `createDirectiveContext`
|
|
8
|
+
|
|
9
|
+
This is the recommended way to use Directive in React. One call returns a typed `Provider` plus bound hooks — no system arg needed at every call site, no `useContext` boilerplate, full type inference from the schema.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
// counter-context.ts — create once, import everywhere
|
|
13
|
+
import { createSystem } from "@directive-run/core";
|
|
14
|
+
import { createDirectiveContext } from "@directive-run/react";
|
|
15
|
+
import { counterModule } from "./counter-module";
|
|
6
16
|
|
|
17
|
+
export const counterSystem = createSystem({ module: counterModule });
|
|
18
|
+
export const Counter = createDirectiveContext(counterSystem);
|
|
7
19
|
```
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
// App.tsx
|
|
23
|
+
import { Counter } from "./counter-context";
|
|
24
|
+
import { Display } from "./Display";
|
|
25
|
+
|
|
26
|
+
export function App() {
|
|
27
|
+
return (
|
|
28
|
+
<Counter.Provider>
|
|
29
|
+
<Display />
|
|
30
|
+
</Counter.Provider>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
14
33
|
```
|
|
15
34
|
|
|
16
|
-
|
|
35
|
+
```tsx
|
|
36
|
+
// Display.tsx
|
|
37
|
+
import { Counter } from "./counter-context";
|
|
17
38
|
|
|
18
|
-
|
|
39
|
+
export function Display() {
|
|
40
|
+
const count = Counter.useFact("count");
|
|
41
|
+
const doubled = Counter.useDerived("doubled");
|
|
42
|
+
const events = Counter.useEvents();
|
|
19
43
|
|
|
20
|
-
|
|
21
|
-
|
|
44
|
+
return (
|
|
45
|
+
<div>
|
|
46
|
+
<p>Count: {count} (doubled: {doubled})</p>
|
|
47
|
+
<button onClick={() => events.increment()}>+1</button>
|
|
48
|
+
<button onClick={() => events.reset()}>Reset</button>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`createDirectiveContext(system)` returns: `{ Provider, useSystem, useFact, useDerived, useEvents, useDispatch, useSelector, useWatch, useInspect, useExplain, useHistory }`. All bound to the typed system — no system argument needed when calling them.
|
|
55
|
+
|
|
56
|
+
The Provider accepts an optional `system` prop override for testing:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
<Counter.Provider system={testSystem}>
|
|
60
|
+
<ComponentUnderTest />
|
|
61
|
+
</Counter.Provider>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Standalone hooks (without a context)
|
|
65
|
+
|
|
66
|
+
When you don't want a context (e.g. one-off components, prototypes), import the hooks directly and pass the system to each call:
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
// system.ts
|
|
22
70
|
import { createSystem } from "@directive-run/core";
|
|
23
71
|
import { counterModule } from "./counter-module";
|
|
24
72
|
|
|
25
73
|
export const system = createSystem({ module: counterModule });
|
|
26
74
|
```
|
|
27
75
|
|
|
28
|
-
```
|
|
76
|
+
```tsx
|
|
29
77
|
// Counter.tsx
|
|
30
|
-
import {
|
|
78
|
+
import { useFact, useDerived, useEvents } from "@directive-run/react";
|
|
31
79
|
import { system } from "./system";
|
|
32
80
|
|
|
33
|
-
function Counter() {
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
// Get event dispatcher
|
|
39
|
-
const events = useEvent(system);
|
|
81
|
+
export function Counter() {
|
|
82
|
+
const count = useFact(system, "count");
|
|
83
|
+
const doubled = useDerived(system, "doubled");
|
|
84
|
+
const events = useEvents(system);
|
|
40
85
|
|
|
41
86
|
return (
|
|
42
87
|
<div>
|
|
43
|
-
<p>Count: {count}</p>
|
|
44
|
-
<p>Doubled: {doubled}</p>
|
|
88
|
+
<p>Count: {count} (doubled: {doubled})</p>
|
|
45
89
|
<button onClick={() => events.increment()}>+1</button>
|
|
46
|
-
<button onClick={() => events.reset()}>Reset</button>
|
|
47
90
|
</div>
|
|
48
91
|
);
|
|
49
92
|
}
|
|
50
93
|
```
|
|
51
94
|
|
|
52
|
-
##
|
|
95
|
+
## Hook reference
|
|
53
96
|
|
|
54
|
-
|
|
97
|
+
### `useFact(system, key)` or `useFact(system, [keys])`
|
|
55
98
|
|
|
56
|
-
|
|
57
|
-
import { useSelector } from "@directive-run/react";
|
|
99
|
+
Reads one fact (single key) or many facts (array of keys). Re-renders only when the selected fact(s) change. Return type is narrowed by the schema.
|
|
58
100
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const name = useSelector(system, (s) => s.facts.userName);
|
|
101
|
+
```tsx
|
|
102
|
+
import { useFact } from "@directive-run/react";
|
|
62
103
|
|
|
63
|
-
|
|
64
|
-
|
|
104
|
+
function Profile() {
|
|
105
|
+
// Single key — typed: name is string | undefined
|
|
106
|
+
const name = useFact(system, "name");
|
|
65
107
|
|
|
66
|
-
//
|
|
67
|
-
const
|
|
68
|
-
name: s.facts.userName,
|
|
69
|
-
role: s.facts.role,
|
|
70
|
-
isAdmin: s.derive.isAdmin,
|
|
71
|
-
}));
|
|
108
|
+
// Multi-key — typed: { name: string; age: number }
|
|
109
|
+
const { name, age } = useFact(system, ["name", "age"]);
|
|
72
110
|
|
|
73
|
-
return <div>{
|
|
111
|
+
return <div>{name} ({age})</div>;
|
|
74
112
|
}
|
|
75
113
|
```
|
|
76
114
|
|
|
77
|
-
###
|
|
115
|
+
### `useDerived(system, id)` or `useDerived(system, [ids])`
|
|
78
116
|
|
|
79
|
-
|
|
80
|
-
const system = createSystem({
|
|
81
|
-
modules: { auth: authModule, cart: cartModule },
|
|
82
|
-
});
|
|
117
|
+
Same shape as `useFact` but for derivations. Re-renders only when the derivation's value changes.
|
|
83
118
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const itemCount = useSelector(system, (s) => s.derive.cart.itemCount);
|
|
119
|
+
```tsx
|
|
120
|
+
import { useDerived } from "@directive-run/react";
|
|
87
121
|
|
|
88
|
-
|
|
122
|
+
function Status() {
|
|
123
|
+
const isReady = useDerived(system, "isReady");
|
|
124
|
+
const { isReady, isLoading } = useDerived(system, ["isReady", "isLoading"]);
|
|
125
|
+
|
|
126
|
+
return <div>{isReady ? "ready" : "loading"}</div>;
|
|
89
127
|
}
|
|
90
128
|
```
|
|
91
129
|
|
|
92
|
-
|
|
130
|
+
### `useEvents(system)`
|
|
93
131
|
|
|
94
|
-
Returns the system's
|
|
132
|
+
Returns the system's typed events accessor. Stable identity — does not cause re-renders. **This is the primary way to dispatch events from a component** — it carries autocomplete for every event name and payload shape.
|
|
95
133
|
|
|
96
|
-
```
|
|
97
|
-
import {
|
|
134
|
+
```tsx
|
|
135
|
+
import { useEvents } from "@directive-run/react";
|
|
98
136
|
|
|
99
|
-
function
|
|
100
|
-
const events =
|
|
137
|
+
function CartActions() {
|
|
138
|
+
const events = useEvents(system);
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<>
|
|
142
|
+
<button onClick={() => events.addItem({ productId: "p1" })}>Add</button>
|
|
143
|
+
<button onClick={() => events.checkout()}>Checkout</button>
|
|
144
|
+
</>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
101
147
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
148
|
+
// Namespaced (multi-module) system — events are nested by module name
|
|
149
|
+
function Header() {
|
|
150
|
+
const events = useEvents(namespacedSystem);
|
|
105
151
|
|
|
106
|
-
return <
|
|
152
|
+
return <button onClick={() => events.cart.addItem({ productId: "p1" })}>Add</button>;
|
|
107
153
|
}
|
|
154
|
+
```
|
|
108
155
|
|
|
109
|
-
|
|
110
|
-
function CartActions() {
|
|
111
|
-
const events = useEvent(system);
|
|
156
|
+
### `useSelector(system, selector, equalityFn?)`
|
|
112
157
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
158
|
+
For computed values across multiple facts and derivations. Auto-tracks accessed keys; re-renders only when the selected result changes (by `Object.is` equality, override with `equalityFn`).
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
import { useSelector, shallowEqual } from "@directive-run/react";
|
|
162
|
+
|
|
163
|
+
function CheckoutSummary() {
|
|
164
|
+
const summary = useSelector(system, (state) => ({
|
|
165
|
+
itemCount: state.items.length,
|
|
166
|
+
total: state.cartTotal,
|
|
167
|
+
isReady: state.canCheckout,
|
|
168
|
+
}), shallowEqual);
|
|
169
|
+
|
|
170
|
+
return <div>{summary.itemCount} items · {summary.total}</div>;
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`state` carries both facts and derivations in a flat namespace — that's why it's typed `InferSelectorState<S>`, not `InferFacts<S>`.
|
|
175
|
+
|
|
176
|
+
### `useDispatch(system)`
|
|
177
|
+
|
|
178
|
+
Returns a raw `dispatch(event)` function. Useful when you need to forward events programmatically or when the event name is computed at runtime. Prefer `useEvents` for normal dispatch — it carries typing.
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
import { useDispatch } from "@directive-run/react";
|
|
182
|
+
|
|
183
|
+
function ToolBar({ actions }: { actions: SystemEvent[] }) {
|
|
184
|
+
const dispatch = useDispatch(system);
|
|
185
|
+
|
|
186
|
+
return actions.map((a) => (
|
|
187
|
+
<button key={a.type} onClick={() => dispatch(a)}>{a.type}</button>
|
|
188
|
+
));
|
|
118
189
|
}
|
|
119
190
|
```
|
|
120
191
|
|
|
121
|
-
|
|
192
|
+
### `useDirective(moduleOrOptions, selections?)`
|
|
122
193
|
|
|
123
|
-
|
|
194
|
+
Convenience hook that **creates a scoped system** AND selects facts/derivations in one call. The system lives for the lifetime of the component (created on mount, destroyed on unmount). Use this when the system's lifecycle should match a component's lifecycle — game boards, wizard flows, modal forms.
|
|
124
195
|
|
|
125
|
-
```
|
|
126
|
-
import {
|
|
196
|
+
```tsx
|
|
197
|
+
import { useDirective } from "@directive-run/react";
|
|
198
|
+
import { gameModule } from "./game-module";
|
|
127
199
|
|
|
128
200
|
function GameBoard() {
|
|
129
|
-
//
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
201
|
+
// Selective subscription — only re-renders when score or isOver change
|
|
202
|
+
const { facts: { score }, derived: { isOver }, events } = useDirective(gameModule, {
|
|
203
|
+
facts: ["score"],
|
|
204
|
+
derived: ["isOver"],
|
|
133
205
|
});
|
|
134
206
|
|
|
135
|
-
const score = useSelector(gameSystem, (s) => s.facts.score);
|
|
136
|
-
const events = useEvent(gameSystem);
|
|
137
|
-
|
|
138
207
|
return (
|
|
139
208
|
<div>
|
|
140
|
-
<p>Score: {score}</p>
|
|
209
|
+
<p>Score: {score}{isOver && " — game over!"}</p>
|
|
141
210
|
<button onClick={() => events.move({ direction: "up" })}>Up</button>
|
|
142
211
|
</div>
|
|
143
212
|
);
|
|
144
213
|
}
|
|
214
|
+
|
|
215
|
+
// Subscribe to everything (omit keys)
|
|
216
|
+
function Debug() {
|
|
217
|
+
const { facts, derived, events, dispatch } = useDirective(gameModule);
|
|
218
|
+
|
|
219
|
+
return <pre>{JSON.stringify({ facts, derived }, null, 2)}</pre>;
|
|
220
|
+
}
|
|
145
221
|
```
|
|
146
222
|
|
|
147
|
-
|
|
223
|
+
For app-wide state, prefer `createDirectiveContext` over `useDirective` — the context pattern creates the system once instead of per-component.
|
|
148
224
|
|
|
149
|
-
|
|
225
|
+
### `useDirectiveRef(moduleOrOptions, config?)`
|
|
150
226
|
|
|
151
|
-
|
|
227
|
+
Just the system-lifecycle half of `useDirective`. Returns the `SingleModuleSystem<S>` instance. Use this when you need a scoped system without consuming its state in this component (e.g., you're going to thread it through props or a context).
|
|
152
228
|
|
|
153
|
-
|
|
154
|
-
import { DirectiveProvider, useDirectiveContext, useSelector, useEvent } from "@directive-run/react";
|
|
229
|
+
### `useWatch(system, factOrDerivationId, callback)`
|
|
155
230
|
|
|
156
|
-
|
|
157
|
-
function App() {
|
|
158
|
-
return (
|
|
159
|
-
<DirectiveProvider system={system}>
|
|
160
|
-
<Dashboard />
|
|
161
|
-
</DirectiveProvider>
|
|
162
|
-
);
|
|
163
|
-
}
|
|
231
|
+
Imperative side-effect when a fact or derivation changes. Does NOT cause re-renders — use this for analytics, logging, focus management. For state you want to render, use `useFact` / `useDerived` / `useSelector`.
|
|
164
232
|
|
|
165
|
-
|
|
166
|
-
function Dashboard() {
|
|
167
|
-
const system = useDirectiveContext();
|
|
168
|
-
const stats = useSelector(system, (s) => s.derive.dashboardStats);
|
|
233
|
+
### Status + introspection hooks
|
|
169
234
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
235
|
+
| Hook | Purpose |
|
|
236
|
+
|---|---|
|
|
237
|
+
| `useRequirementStatus(system, type)` | Reactive `idle / running / success / error` status for a requirement type |
|
|
238
|
+
| `useConstraintStatus(system, id)` | Reactive constraint enabled/disabled state |
|
|
239
|
+
| `useSuspenseRequirement(system, type)` | Same as `useRequirementStatus` but suspends until resolved (Suspense boundary required) |
|
|
240
|
+
| `useExplain(system, requirementId)` | Reactive English explanation of a requirement's `when` predicate |
|
|
241
|
+
| `useInspect(system, options?)` | Full system inspection state — constraints, requirements, resolvers, derivations |
|
|
242
|
+
| `useHistory(system)` | Time-travel UI helpers: `goBack`, `goForward`, `canUndo`, `canRedo`, snapshot list |
|
|
243
|
+
| `useAuditLedger(system)` | Subscribe to a configured `createAuditLedger` plugin's entries |
|
|
173
244
|
|
|
174
|
-
|
|
245
|
+
### SSR + hydration
|
|
175
246
|
|
|
176
|
-
|
|
177
|
-
// WRONG – useDirective() does not exist. This is a common hallucination.
|
|
178
|
-
const { facts, derive, events } = useDirective(system);
|
|
247
|
+
`DirectiveHydrator` and `useHydratedSystem` close the SSR loop. See `system-api.md` → "Hydration" for the full pattern.
|
|
179
248
|
|
|
180
|
-
|
|
181
|
-
const count = useSelector(system, (s) => s.facts.count);
|
|
182
|
-
const events = useEvent(system);
|
|
183
|
-
```
|
|
249
|
+
### Suspense for data fetching
|
|
184
250
|
|
|
185
|
-
|
|
251
|
+
`useQuerySystem` + `useSuspenseQuery` integrate `@directive-run/query`'s subscription/mutation primitives with React's Suspense and Error Boundaries. See the `@directive-run/query` docs.
|
|
186
252
|
|
|
187
|
-
|
|
253
|
+
## CRITICAL: hooks that DO NOT exist
|
|
188
254
|
|
|
189
|
-
|
|
190
|
-
import { createDirectiveContext } from "@directive-run/react";
|
|
255
|
+
LLMs frequently hallucinate these names because they sound plausible. None of them are exported from `@directive-run/react`.
|
|
191
256
|
|
|
192
|
-
|
|
257
|
+
| Hallucination | Use instead |
|
|
258
|
+
|---|---|
|
|
259
|
+
| `useEvent(system)` (singular) | `useEvents(system)` (plural) |
|
|
260
|
+
| `useSystem(config)` as a top-level import | `useDirective(module)` or `createDirectiveContext().useSystem` |
|
|
261
|
+
| `DirectiveProvider` as a top-level import | `createDirectiveContext(system).Provider` |
|
|
262
|
+
| `useDirectiveContext()` | `createDirectiveContext(system).useSystem()` |
|
|
263
|
+
| `useState`-style writes like `setCount(5)` | Dispatch events: `events.setCount(5)` (or whatever your module defines) |
|
|
193
264
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
</Counter.Provider>
|
|
199
|
-
);
|
|
200
|
-
}
|
|
265
|
+
```tsx
|
|
266
|
+
// WRONG — useEvent does not exist
|
|
267
|
+
import { useEvent } from "@directive-run/react";
|
|
268
|
+
const events = useEvent(system);
|
|
201
269
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const events = Counter.useEvents();
|
|
206
|
-
return <button onClick={() => events.increment()}>{count}</button>;
|
|
207
|
-
}
|
|
270
|
+
// CORRECT — useEvents (plural)
|
|
271
|
+
import { useEvents } from "@directive-run/react";
|
|
272
|
+
const events = useEvents(system);
|
|
208
273
|
```
|
|
209
274
|
|
|
210
|
-
Returns: `{ Provider, useSystem, useFact, useDerived, useEvents, useDispatch, useSelector, useWatch, useInspect, useExplain, useHistory }`.
|
|
211
|
-
|
|
212
|
-
Provider accepts `system` prop override for testing:
|
|
213
275
|
```tsx
|
|
214
|
-
|
|
276
|
+
// WRONG — DirectiveProvider is not a top-level export
|
|
277
|
+
import { DirectiveProvider, useDirectiveContext } from "@directive-run/react";
|
|
278
|
+
<DirectiveProvider system={system}>...</DirectiveProvider>
|
|
279
|
+
|
|
280
|
+
// CORRECT — Provider comes from createDirectiveContext()
|
|
281
|
+
import { createDirectiveContext } from "@directive-run/react";
|
|
282
|
+
export const Counter = createDirectiveContext(counterSystem);
|
|
283
|
+
<Counter.Provider>...</Counter.Provider>
|
|
215
284
|
```
|
|
216
285
|
|
|
217
|
-
## Common
|
|
286
|
+
## Common mistakes
|
|
218
287
|
|
|
219
|
-
### Creating the system inside a component
|
|
288
|
+
### Creating the system inside a component body
|
|
220
289
|
|
|
221
|
-
```
|
|
222
|
-
// WRONG
|
|
290
|
+
```tsx
|
|
291
|
+
// WRONG — creates a new system on every render
|
|
223
292
|
function Counter() {
|
|
224
|
-
const system = createSystem({ module: counterModule });
|
|
225
|
-
const count =
|
|
226
|
-
|
|
293
|
+
const system = createSystem({ module: counterModule });
|
|
294
|
+
const count = useFact(system, "count");
|
|
227
295
|
return <div>{count}</div>;
|
|
228
296
|
}
|
|
229
297
|
|
|
230
|
-
// CORRECT
|
|
298
|
+
// CORRECT — create outside the component
|
|
231
299
|
const system = createSystem({ module: counterModule });
|
|
232
300
|
|
|
233
301
|
function Counter() {
|
|
234
|
-
const count =
|
|
235
|
-
|
|
302
|
+
const count = useFact(system, "count");
|
|
236
303
|
return <div>{count}</div>;
|
|
237
304
|
}
|
|
238
305
|
|
|
239
|
-
// ALSO CORRECT
|
|
306
|
+
// ALSO CORRECT — useDirective manages lifecycle
|
|
240
307
|
function Counter() {
|
|
241
|
-
const
|
|
242
|
-
const count = useSelector(system, (s) => s.facts.count);
|
|
243
|
-
|
|
308
|
+
const { facts: { count } } = useDirective(counterModule, { facts: ["count"] });
|
|
244
309
|
return <div>{count}</div>;
|
|
245
310
|
}
|
|
246
311
|
```
|
|
247
312
|
|
|
248
|
-
### Selecting too much state (
|
|
313
|
+
### Selecting too much state (excess re-renders)
|
|
314
|
+
|
|
315
|
+
```tsx
|
|
316
|
+
// WRONG — re-renders on ANY fact change
|
|
317
|
+
const allFacts = useSelector(system, (s) => s);
|
|
249
318
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const allFacts = useSelector(system, (s) => s.facts);
|
|
319
|
+
// WRONG — re-renders even when other facts change (new object identity every time)
|
|
320
|
+
const profile = useSelector(system, (s) => ({ name: s.name, age: s.age }));
|
|
253
321
|
|
|
254
|
-
// CORRECT
|
|
255
|
-
const name =
|
|
256
|
-
|
|
322
|
+
// CORRECT — use the targeted hook
|
|
323
|
+
const name = useFact(system, "name");
|
|
324
|
+
|
|
325
|
+
// CORRECT — multi-key reads a stable subset
|
|
326
|
+
const { name, age } = useFact(system, ["name", "age"]);
|
|
327
|
+
|
|
328
|
+
// CORRECT — useSelector with shallowEqual for shaped output
|
|
329
|
+
import { shallowEqual } from "@directive-run/react";
|
|
330
|
+
const profile = useSelector(system, (s) => ({ name: s.name, age: s.age }), shallowEqual);
|
|
257
331
|
```
|
|
258
332
|
|
|
259
|
-
### Mutating facts directly
|
|
333
|
+
### Mutating facts directly from an event handler
|
|
260
334
|
|
|
261
|
-
```
|
|
262
|
-
// WRONG
|
|
335
|
+
```tsx
|
|
336
|
+
// WRONG — bypasses the event system, no audit trail, no constraint reaction
|
|
263
337
|
function Counter() {
|
|
264
|
-
const count =
|
|
265
|
-
|
|
266
|
-
return (
|
|
267
|
-
<button onClick={() => { system.facts.count += 1; }}>
|
|
268
|
-
{count}
|
|
269
|
-
</button>
|
|
270
|
-
);
|
|
338
|
+
const count = useFact(system, "count");
|
|
339
|
+
return <button onClick={() => { system.facts.count += 1; }}>{count}</button>;
|
|
271
340
|
}
|
|
272
341
|
|
|
273
|
-
// CORRECT
|
|
342
|
+
// CORRECT — dispatch an event
|
|
274
343
|
function Counter() {
|
|
275
|
-
const count =
|
|
276
|
-
const events =
|
|
277
|
-
|
|
278
|
-
return (
|
|
279
|
-
<button onClick={() => events.increment()}>
|
|
280
|
-
{count}
|
|
281
|
-
</button>
|
|
282
|
-
);
|
|
344
|
+
const count = useFact(system, "count");
|
|
345
|
+
const events = useEvents(system);
|
|
346
|
+
return <button onClick={() => events.increment()}>{count}</button>;
|
|
283
347
|
}
|
|
284
348
|
```
|
|
285
349
|
|
|
286
|
-
### Casting values from
|
|
350
|
+
### Casting values from hooks
|
|
287
351
|
|
|
288
|
-
```
|
|
289
|
-
// WRONG
|
|
290
|
-
const profile =
|
|
352
|
+
```tsx
|
|
353
|
+
// WRONG — types come from the schema
|
|
354
|
+
const profile = useFact(system, "profile") as UserProfile;
|
|
291
355
|
|
|
292
|
-
// CORRECT
|
|
293
|
-
const profile =
|
|
356
|
+
// CORRECT — the schema types it for you
|
|
357
|
+
const profile = useFact(system, "profile");
|
|
294
358
|
```
|
|
359
|
+
|
|
360
|
+
### Forgetting `useEvents` is stable
|
|
361
|
+
|
|
362
|
+
```tsx
|
|
363
|
+
// UNNECESSARY — useEvents returns a stable reference, no useCallback needed
|
|
364
|
+
const events = useEvents(system);
|
|
365
|
+
const handleClick = useCallback(() => events.increment(), [events]);
|
|
366
|
+
|
|
367
|
+
// FINE — events.increment is stable across renders too
|
|
368
|
+
const events = useEvents(system);
|
|
369
|
+
<button onClick={() => events.increment()}>+</button>
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## See also
|
|
373
|
+
|
|
374
|
+
- [`system-api.md`](./system-api.md) — the system instance the hooks here read from + the SSR/hydration story
|
|
375
|
+
- [`core-patterns.md`](./core-patterns.md) — the module shape every React example here assumes you've built
|
|
376
|
+
- [`anti-patterns.md`](./anti-patterns.md) — the hallucinated React hooks (`useEvent`, `useSystem`, `DirectiveProvider`) that AI assistants reach for
|
package/core/resolvers.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Resolvers
|
|
2
2
|
|
|
3
|
+
> Covers `@directive-run/core` — resolver definition: retry policies, batching, cancellation, custom dedup keys, `owns` binding.
|
|
4
|
+
|
|
3
5
|
Resolvers fulfill requirements emitted by constraints. They are the supply side of the constraint-resolver pattern. Resolvers handle async work and mutate state through `context.facts`.
|
|
4
6
|
|
|
5
7
|
## Decision Tree: "How should this resolver work?"
|
|
@@ -355,3 +357,11 @@ system.start();
|
|
|
355
357
|
await system.settle();
|
|
356
358
|
console.log(system.facts.data); // Resolved value
|
|
357
359
|
```
|
|
360
|
+
|
|
361
|
+
## See also
|
|
362
|
+
|
|
363
|
+
- [`constraints.md`](./constraints.md) — the demand side of the loop; what emits the requirements this resolver fulfills
|
|
364
|
+
- [`core-patterns.md`](./core-patterns.md) — how resolvers fit into the module shape
|
|
365
|
+
- [`error-boundaries.md`](./error-boundaries.md) — recovery strategies when a resolver throws (retry, fallback, circuit breaker)
|
|
366
|
+
- [`testing.md`](./testing.md) — mock resolvers via `mocks.resolvers` and the programmatic `mockResolver(type)` helper
|
|
367
|
+
- [`anti-patterns.md`](./anti-patterns.md) — the resolver-shaped mistakes (returning data from `resolve`, abbreviating `context` to `ctx`)
|
package/core/schema-types.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Schema Types
|
|
2
2
|
|
|
3
|
+
> Covers `@directive-run/core` — schema builders (`t.string` / `t.number` / `t.array` / etc.) and inference helpers.
|
|
4
|
+
|
|
3
5
|
The `t.*()` builders define fact types in module schemas. They provide runtime validation in dev mode and full TypeScript inference.
|
|
4
6
|
|
|
5
7
|
## Decision Tree: "Which type builder do I use?"
|
|
@@ -263,3 +265,9 @@ const myModule = createModule("simple", {
|
|
|
263
265
|
```
|
|
264
266
|
|
|
265
267
|
This gives full TypeScript inference but skips runtime validation. Use `t.*()` when you want dev-mode validation, transforms, or self-documenting schemas.
|
|
268
|
+
|
|
269
|
+
## See also
|
|
270
|
+
|
|
271
|
+
- [`core-patterns.md`](./core-patterns.md) — where the schemas these builders define get attached (modules)
|
|
272
|
+
- [`system-api.md`](./system-api.md) — schema flows through `createSystem` and shows up on `system.facts.*`
|
|
273
|
+
- [`anti-patterns.md`](./anti-patterns.md) — hallucinated schema types (`t.map`, `t.set`, `t.date`, `t.promise`, `t.tuple`, `t.record`, `t.any`) that don't exist
|
package/core/system-api.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# System API
|
|
2
2
|
|
|
3
|
+
> Covers `@directive-run/core` — `createSystem`, the system instance surface, hydration, inspection, distributable snapshots.
|
|
4
|
+
|
|
3
5
|
The system is created with `createSystem()` and is the runtime that orchestrates modules, constraints, resolvers, and plugins.
|
|
4
6
|
|
|
5
7
|
## Decision Tree: "How do I interact with the system?"
|
|
@@ -451,3 +453,11 @@ system.start();
|
|
|
451
453
|
system.stop();
|
|
452
454
|
system.destroy();
|
|
453
455
|
```
|
|
456
|
+
|
|
457
|
+
## See also
|
|
458
|
+
|
|
459
|
+
- [`core-patterns.md`](./core-patterns.md) — the modules that go into `createSystem({ module: ... })`
|
|
460
|
+
- [`multi-module.md`](./multi-module.md) — `createSystem({ modules: { ... } })` for namespaced systems
|
|
461
|
+
- [`history.md`](./history.md) — the `history:` config and the `system.history.*` time-travel surface
|
|
462
|
+
- [`plugins.md`](./plugins.md) — the `plugins:` config and the lifecycle hooks plugins observe
|
|
463
|
+
- [`react-adapter.md`](./react-adapter.md) — consuming a system from React components
|