@directive-run/claude-plugin 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/.claude-plugin/plugin.json +6 -0
- package/LICENSE +26 -0
- package/README.md +113 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +66 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
- package/skills/README.md +9 -0
- package/skills/building-ai-agents/SKILL.md +414 -0
- package/skills/building-ai-agents/ai-adapters.md +257 -0
- package/skills/building-ai-agents/ai-agents-streaming.md +307 -0
- package/skills/building-ai-agents/ai-communication.md +304 -0
- package/skills/building-ai-agents/api-skeleton.md +5 -0
- package/skills/building-ai-agents/examples.md +849 -0
- package/skills/building-ai-orchestrators/SKILL.md +401 -0
- package/skills/building-ai-orchestrators/ai-multi-agent.md +375 -0
- package/skills/building-ai-orchestrators/ai-orchestrator.md +466 -0
- package/skills/building-ai-orchestrators/ai-tasks.md +268 -0
- package/skills/building-ai-orchestrators/api-skeleton.md +5 -0
- package/skills/building-ai-orchestrators/examples.md +1609 -0
- package/skills/building-directive-systems/SKILL.md +499 -0
- package/skills/building-directive-systems/api-skeleton.md +5 -0
- package/skills/building-directive-systems/examples.md +4149 -0
- package/skills/building-directive-systems/multi-module.md +327 -0
- package/skills/building-directive-systems/plugins.md +361 -0
- package/skills/building-directive-systems/react-adapter.md +376 -0
- package/skills/building-directive-systems/system-api.md +463 -0
- package/skills/getting-started-with-directive/SKILL.md +198 -0
- package/skills/getting-started-with-directive/api-skeleton.md +5 -0
- package/skills/getting-started-with-directive/core-patterns.md +240 -0
- package/skills/getting-started-with-directive/examples.md +87 -0
- package/skills/getting-started-with-directive/sitemap.md +202 -0
- package/skills/hardening-ai-systems/SKILL.md +377 -0
- package/skills/hardening-ai-systems/ai-budget-resilience.md +408 -0
- package/skills/hardening-ai-systems/ai-guardrails-memory.md +370 -0
- package/skills/hardening-ai-systems/ai-security.md +400 -0
- package/skills/hardening-ai-systems/api-skeleton.md +5 -0
- package/skills/hardening-ai-systems/examples.md +628 -0
- package/skills/migrating-to-directive/SKILL.md +249 -0
- package/skills/migrating-to-directive/anti-patterns.md +382 -0
- package/skills/migrating-to-directive/api-skeleton.md +5 -0
- package/skills/migrating-to-directive/core-patterns.md +240 -0
- package/skills/migrating-to-directive/examples.md +511 -0
- package/skills/migrating-to-directive/schema-types.md +273 -0
- package/skills/reviewing-directive-code/SKILL.md +232 -0
- package/skills/reviewing-directive-code/anti-patterns.md +382 -0
- package/skills/reviewing-directive-code/api-skeleton.md +5 -0
- package/skills/reviewing-directive-code/core-patterns.md +240 -0
- package/skills/reviewing-directive-code/examples.md +463 -0
- package/skills/reviewing-directive-code/naming.md +321 -0
- package/skills/scaffolding-directive-modules/SKILL.md +218 -0
- package/skills/scaffolding-directive-modules/api-skeleton.md +5 -0
- package/skills/scaffolding-directive-modules/core-patterns.md +240 -0
- package/skills/scaffolding-directive-modules/examples.md +1386 -0
- package/skills/scaffolding-directive-modules/naming.md +321 -0
- package/skills/scaffolding-directive-modules/schema-types.md +273 -0
- package/skills/testing-ai-systems/SKILL.md +398 -0
- package/skills/testing-ai-systems/ai-debug-observability.md +329 -0
- package/skills/testing-ai-systems/ai-mcp-rag.md +293 -0
- package/skills/testing-ai-systems/ai-testing-evals.md +485 -0
- package/skills/testing-ai-systems/api-skeleton.md +5 -0
- package/skills/testing-ai-systems/examples.md +1261 -0
- package/skills/testing-directive-code/SKILL.md +479 -0
- package/skills/testing-directive-code/api-skeleton.md +5 -0
- package/skills/testing-directive-code/examples.md +156 -0
- package/skills/testing-directive-code/history.md +351 -0
- package/skills/testing-directive-code/testing.md +412 -0
- package/skills/writing-directive-constraints/SKILL.md +486 -0
- package/skills/writing-directive-constraints/api-skeleton.md +5 -0
- package/skills/writing-directive-constraints/constraints.md +274 -0
- package/skills/writing-directive-constraints/error-boundaries.md +330 -0
- package/skills/writing-directive-constraints/examples.md +1361 -0
- package/skills/writing-directive-constraints/resolvers.md +367 -0
- package/skills/writing-directive-modules/SKILL.md +342 -0
- package/skills/writing-directive-modules/anti-patterns.md +382 -0
- package/skills/writing-directive-modules/api-skeleton.md +5 -0
- package/skills/writing-directive-modules/core-patterns.md +240 -0
- package/skills/writing-directive-modules/examples.md +1639 -0
- package/skills/writing-directive-modules/naming.md +321 -0
- package/skills/writing-directive-modules/schema-types.md +273 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
# Anti-Patterns
|
|
2
|
+
|
|
3
|
+
> Covers `@directive-run/core` and `@directive-run/react` — hallucination-prone API patterns to avoid.
|
|
4
|
+
|
|
5
|
+
19 most common mistakes when generating Directive code, ranked by AI hallucination frequency. Every code generation MUST be checked against this list.
|
|
6
|
+
|
|
7
|
+
## 1. Unnecessary Type Casting on Facts/Derivations
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// WRONG – schema already provides the type
|
|
11
|
+
const profile = system.facts.profile as ResourceState<Profile>;
|
|
12
|
+
|
|
13
|
+
// CORRECT – trust the schema
|
|
14
|
+
const profile = system.facts.profile;
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 2. Flat Schema (Missing facts Wrapper)
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// WRONG – facts must be nested under schema.facts
|
|
21
|
+
createModule("counter", {
|
|
22
|
+
schema: {
|
|
23
|
+
phase: t.string(),
|
|
24
|
+
count: t.number(),
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// CORRECT
|
|
29
|
+
createModule("counter", {
|
|
30
|
+
schema: {
|
|
31
|
+
facts: {
|
|
32
|
+
phase: t.string(),
|
|
33
|
+
count: t.number(),
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 3. Bare `facts.*` in Multi-Module Constraints
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// WRONG – multi-module constraints use facts.self for own module
|
|
43
|
+
constraints: {
|
|
44
|
+
checkItems: {
|
|
45
|
+
when: (facts) => facts.items.length > 0,
|
|
46
|
+
require: { type: "PROCESS" },
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
// CORRECT – use facts.self.* for own module facts
|
|
51
|
+
constraints: {
|
|
52
|
+
checkItems: {
|
|
53
|
+
when: (facts) => facts.self.items.length > 0,
|
|
54
|
+
require: { type: "PROCESS" },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 4. Nonexistent Schema Builders
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// WRONG – t.map(), t.set(), t.promise() do not exist
|
|
63
|
+
schema: {
|
|
64
|
+
facts: {
|
|
65
|
+
cache: t.map<string, User>(),
|
|
66
|
+
tags: t.set<string>(),
|
|
67
|
+
pending: t.promise<Data>(),
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
// CORRECT – use t.object() with type parameter
|
|
72
|
+
schema: {
|
|
73
|
+
facts: {
|
|
74
|
+
cache: t.object<Map<string, User>>(),
|
|
75
|
+
tags: t.object<Set<string>>(),
|
|
76
|
+
pending: t.object<Promise<Data>>(),
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 5. Abbreviating `context` to `ctx`
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// WRONG – never abbreviate context
|
|
85
|
+
resolve: async (req, ctx) => {
|
|
86
|
+
ctx.facts.status = "done";
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
// CORRECT – always spell out context
|
|
90
|
+
resolve: async (req, context) => {
|
|
91
|
+
context.facts.status = "done";
|
|
92
|
+
},
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## 6. Flat Module Config (No schema Wrapper)
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// WRONG – properties must be inside schema.facts
|
|
99
|
+
createModule("timer", {
|
|
100
|
+
phase: t.string(),
|
|
101
|
+
elapsed: t.number(),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// CORRECT – wrap in schema: { facts: {} }
|
|
105
|
+
createModule("timer", {
|
|
106
|
+
schema: {
|
|
107
|
+
facts: {
|
|
108
|
+
phase: t.string(),
|
|
109
|
+
elapsed: t.number(),
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## 7. String-Based Event Dispatch
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// WRONG – there is no two-argument string-keyed dispatch signature
|
|
119
|
+
system.dispatch("login", { token: "abc" });
|
|
120
|
+
|
|
121
|
+
// CORRECT – use the typed events accessor (preferred — autocomplete + payload typing)
|
|
122
|
+
system.events.login({ token: "abc" });
|
|
123
|
+
|
|
124
|
+
// ALSO VALID – the single-arg object form of dispatch() is supported when you
|
|
125
|
+
// need to forward a programmatically-built event. Prefer the events accessor
|
|
126
|
+
// for normal code.
|
|
127
|
+
system.dispatch({ type: "login", token: "abc" });
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## 8. Direct Array/Object Mutation
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// WRONG – proxy cannot detect in-place mutations
|
|
134
|
+
facts.items.push(item);
|
|
135
|
+
facts.config.theme = "dark";
|
|
136
|
+
|
|
137
|
+
// CORRECT – replace the entire value
|
|
138
|
+
facts.items = [...facts.items, item];
|
|
139
|
+
facts.config = { ...facts.config, theme: "dark" };
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## 9. Nonexistent `useDirective` Hook
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// WRONG – there is no useDirective hook
|
|
146
|
+
const state = useDirective(system);
|
|
147
|
+
|
|
148
|
+
// CORRECT – use useSelector with a selector function
|
|
149
|
+
const count = useSelector(system, (s) => s.facts.count);
|
|
150
|
+
const isLoading = useSelector(system, (s) => s.derive.isLoading);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## 10. Bracket Notation for Namespaced Facts
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
// WRONG – internal separator is not part of the public API
|
|
157
|
+
const status = facts["auth::status"];
|
|
158
|
+
const token = facts["auth_token"];
|
|
159
|
+
|
|
160
|
+
// CORRECT – use dot notation through the namespace proxy
|
|
161
|
+
const status = facts.auth.status;
|
|
162
|
+
const token = facts.auth.token;
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## 11. Returning Data from Resolvers
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// WRONG – resolvers return void, not data
|
|
169
|
+
resolve: async (req, context) => {
|
|
170
|
+
const user = await fetchUser(req.userId);
|
|
171
|
+
|
|
172
|
+
return user; // Return value is ignored
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
// CORRECT – mutate context.facts to store results
|
|
176
|
+
resolve: async (req, context) => {
|
|
177
|
+
const user = await fetchUser(req.userId);
|
|
178
|
+
context.facts.user = user;
|
|
179
|
+
},
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## 12. Async Logic in `init`
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// WRONG – init is synchronous, facts assignment only
|
|
186
|
+
init: async (facts) => {
|
|
187
|
+
const data = await fetch("/api/config");
|
|
188
|
+
facts.config = await data.json();
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
// CORRECT – init sets defaults; use constraints/resolvers for async work
|
|
192
|
+
init: (facts) => {
|
|
193
|
+
facts.config = null;
|
|
194
|
+
facts.phase = "loading";
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
constraints: {
|
|
198
|
+
loadConfig: {
|
|
199
|
+
when: (facts) => facts.config === null,
|
|
200
|
+
require: { type: "LOAD_CONFIG" },
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## 13. Missing `settle()` After `start()`
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
// WRONG – constraints fire on start, resolvers are async
|
|
209
|
+
system.start();
|
|
210
|
+
console.log(system.facts.data); // May still be null
|
|
211
|
+
|
|
212
|
+
// CORRECT – wait for resolvers to complete
|
|
213
|
+
system.start();
|
|
214
|
+
await system.settle();
|
|
215
|
+
console.log(system.facts.data); // Resolved
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## 14. Missing `crossModuleDeps` Declaration
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
// WRONG – accessing auth facts without declaring dependency
|
|
222
|
+
const dataModule = createModule("data", {
|
|
223
|
+
schema: { facts: { items: t.array(t.string()) } },
|
|
224
|
+
constraints: {
|
|
225
|
+
fetchWhenAuth: {
|
|
226
|
+
when: (facts) => facts.auth.isAuthenticated, // Type error
|
|
227
|
+
require: { type: "FETCH" },
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// CORRECT – declare crossModuleDeps for type-safe cross-module access
|
|
233
|
+
const dataModule = createModule("data", {
|
|
234
|
+
schema: { facts: { items: t.array(t.string()) } },
|
|
235
|
+
crossModuleDeps: { auth: authSchema },
|
|
236
|
+
constraints: {
|
|
237
|
+
fetchWhenAuth: {
|
|
238
|
+
when: (facts) => facts.auth.isAuthenticated,
|
|
239
|
+
require: { type: "FETCH" },
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## 15. String Literal for `require`
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
// WRONG – require must be an object with type property
|
|
249
|
+
constraints: {
|
|
250
|
+
check: {
|
|
251
|
+
when: (facts) => facts.ready,
|
|
252
|
+
require: "FETCH_DATA",
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
// CORRECT – use object form with type
|
|
257
|
+
constraints: {
|
|
258
|
+
check: {
|
|
259
|
+
when: (facts) => facts.ready,
|
|
260
|
+
require: { type: "FETCH_DATA" },
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## 16. Passthrough Derivations
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
// WRONG – derivation just returns a fact value unchanged
|
|
269
|
+
derive: {
|
|
270
|
+
count: (facts) => facts.count,
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
// CORRECT – remove it, read the fact directly instead
|
|
274
|
+
// system.facts.count instead of system.derive.count
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## 17. Deep Import Paths
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
// WRONG – internal module paths are not public API
|
|
281
|
+
import { createModule } from "@directive-run/core/module";
|
|
282
|
+
import { createSystem } from "@directive-run/core/system";
|
|
283
|
+
|
|
284
|
+
// CORRECT – import from package root
|
|
285
|
+
import { createModule, createSystem } from "@directive-run/core";
|
|
286
|
+
|
|
287
|
+
// Exception: plugins have their own entry point
|
|
288
|
+
import { loggingPlugin } from "@directive-run/core/plugins";
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## 18. Async `when()` Without `deps`
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
// WRONG – async constraints need explicit deps for tracking
|
|
295
|
+
constraints: {
|
|
296
|
+
validate: {
|
|
297
|
+
async: true,
|
|
298
|
+
when: async (facts) => {
|
|
299
|
+
const valid = await checkRemote(facts.token);
|
|
300
|
+
|
|
301
|
+
return valid;
|
|
302
|
+
},
|
|
303
|
+
require: { type: "REFRESH_TOKEN" },
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
// CORRECT – add deps array for async constraints
|
|
308
|
+
constraints: {
|
|
309
|
+
validate: {
|
|
310
|
+
async: true,
|
|
311
|
+
deps: ["token"],
|
|
312
|
+
when: async (facts) => {
|
|
313
|
+
const valid = await checkRemote(facts.token);
|
|
314
|
+
|
|
315
|
+
return valid;
|
|
316
|
+
},
|
|
317
|
+
require: { type: "REFRESH_TOKEN" },
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## 19. No Error Handling on Failing Resolvers
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
// WRONG – unhandled errors crash the system
|
|
326
|
+
resolvers: {
|
|
327
|
+
fetchData: {
|
|
328
|
+
requirement: "FETCH",
|
|
329
|
+
resolve: async (req, context) => {
|
|
330
|
+
const res = await fetch("/api/data");
|
|
331
|
+
context.facts.data = await res.json();
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
// CORRECT – use retry policy and/or module error boundary
|
|
337
|
+
resolvers: {
|
|
338
|
+
fetchData: {
|
|
339
|
+
requirement: "FETCH",
|
|
340
|
+
retry: { attempts: 3, backoff: "exponential" },
|
|
341
|
+
resolve: async (req, context) => {
|
|
342
|
+
const res = await fetch("/api/data");
|
|
343
|
+
if (!res.ok) {
|
|
344
|
+
throw new Error(`HTTP ${res.status}`);
|
|
345
|
+
}
|
|
346
|
+
context.facts.data = await res.json();
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
// Also set error boundary at system level
|
|
352
|
+
const system = createSystem({
|
|
353
|
+
module: myModule,
|
|
354
|
+
errorBoundary: {
|
|
355
|
+
onResolverError: "retry-later",
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Quick Reference Checklist
|
|
361
|
+
|
|
362
|
+
Before generating any Directive code, verify:
|
|
363
|
+
|
|
364
|
+
1. Schema is nested: `schema: { facts: { ... } }` (not flat)
|
|
365
|
+
2. No `as` casts when reading facts or derivations
|
|
366
|
+
3. Resolver params are `(req, context)` not `(req, ctx)`
|
|
367
|
+
4. `require` is an object `{ type: "..." }` not a string
|
|
368
|
+
5. `init()` is synchronous
|
|
369
|
+
6. Resolvers return void and mutate `context.facts`
|
|
370
|
+
7. Arrays/objects replaced, not mutated in place
|
|
371
|
+
8. Multi-module uses `facts.self.*` for own facts
|
|
372
|
+
9. Imports from `@directive-run/core`, not deep paths
|
|
373
|
+
10. `await system.settle()` after `system.start()`
|
|
374
|
+
|
|
375
|
+
## See also
|
|
376
|
+
|
|
377
|
+
- [`naming.md`](./naming.md) — the strict canonical-term rules AND the alias map for cross-paradigm searches
|
|
378
|
+
- [`constraints.md`](./constraints.md) — the constraint shape these anti-patterns reference (`facts` not in scope inside static `require:`, etc.)
|
|
379
|
+
- [`resolvers.md`](./resolvers.md) — the resolver shape these anti-patterns reference (return `void`, mutate `context.facts`, `(req, context)` not `(req, ctx)`)
|
|
380
|
+
- [`schema-types.md`](./schema-types.md) — the `t.*()` builders that exist and the hallucinated ones (`t.map`, `t.set`, `t.promise`, `t.date`) that don't
|
|
381
|
+
- [`react-adapter.md`](./react-adapter.md) — the React hooks that exist and the hallucinated ones (`useEvent`, `useSystem`, `DirectiveProvider`) that don't
|
|
382
|
+
- [`error-boundaries.md`](./error-boundaries.md) — error-handling shapes AI assistants sometimes invent
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Core Patterns
|
|
2
|
+
|
|
3
|
+
> Covers `@directive-run/core` — modules, facts, derivations, effects, events, and the constraint-resolver loop.
|
|
4
|
+
|
|
5
|
+
How to think about building with Directive: modules, systems, and the constraint-resolver pattern.
|
|
6
|
+
|
|
7
|
+
## Decision Tree: "Where does this logic go?"
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
User wants to...
|
|
11
|
+
├── Store state → schema.facts + init()
|
|
12
|
+
├── Compute derived values → schema.derivations + derive
|
|
13
|
+
├── React to state changes → effects
|
|
14
|
+
├── Trigger side effects when conditions are met → constraints + resolvers
|
|
15
|
+
├── Handle user actions → schema.events + events handlers
|
|
16
|
+
└── Coordinate multiple modules → createSystem({ modules: {} })
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Module Shape (Canonical Object Syntax)
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// CORRECT – full module definition
|
|
23
|
+
import { createModule, t } from "@directive-run/core";
|
|
24
|
+
|
|
25
|
+
const myModule = createModule("name", {
|
|
26
|
+
schema: {
|
|
27
|
+
facts: {
|
|
28
|
+
count: t.number(),
|
|
29
|
+
phase: t.string<"idle" | "loading" | "done">(),
|
|
30
|
+
user: t.object<{ id: string; name: string } | null>(),
|
|
31
|
+
},
|
|
32
|
+
derivations: {
|
|
33
|
+
isLoading: t.boolean(),
|
|
34
|
+
displayName: t.string(),
|
|
35
|
+
},
|
|
36
|
+
events: {
|
|
37
|
+
increment: {},
|
|
38
|
+
setUser: { user: t.object<{ id: string; name: string }>() },
|
|
39
|
+
},
|
|
40
|
+
requirements: {
|
|
41
|
+
FETCH_USER: { userId: t.string() },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
init: (facts) => {
|
|
46
|
+
facts.count = 0;
|
|
47
|
+
facts.phase = "idle";
|
|
48
|
+
facts.user = null;
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
derive: {
|
|
52
|
+
isLoading: (facts) => facts.phase === "loading",
|
|
53
|
+
displayName: (facts) => {
|
|
54
|
+
if (!facts.user) {
|
|
55
|
+
return "Guest";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return facts.user.name;
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
effects: {
|
|
63
|
+
logPhase: {
|
|
64
|
+
run: (facts, prev) => {
|
|
65
|
+
if (prev?.phase !== facts.phase) {
|
|
66
|
+
console.log(`Phase: ${facts.phase}`);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
constraints: {
|
|
73
|
+
fetchWhenReady: {
|
|
74
|
+
when: (facts) => facts.phase === "idle" && facts.count > 0,
|
|
75
|
+
require: (facts) => ({ type: "FETCH_USER", userId: "user-1" }),
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
resolvers: {
|
|
80
|
+
fetchUser: {
|
|
81
|
+
requirement: "FETCH_USER",
|
|
82
|
+
resolve: async (req, context) => {
|
|
83
|
+
context.facts.phase = "loading";
|
|
84
|
+
const res = await fetch(`/api/users/${req.userId}`);
|
|
85
|
+
const data = await res.json();
|
|
86
|
+
context.facts.user = data;
|
|
87
|
+
context.facts.phase = "done";
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
events: {
|
|
93
|
+
increment: (facts) => {
|
|
94
|
+
facts.count += 1;
|
|
95
|
+
},
|
|
96
|
+
setUser: (facts, payload) => {
|
|
97
|
+
facts.user = payload.user;
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## System Creation
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { createSystem } from "@directive-run/core";
|
|
107
|
+
import { loggingPlugin, devtoolsPlugin } from "@directive-run/core/plugins";
|
|
108
|
+
|
|
109
|
+
// Single module – direct access: system.facts.count
|
|
110
|
+
const system = createSystem({
|
|
111
|
+
module: myModule,
|
|
112
|
+
plugins: [loggingPlugin(), devtoolsPlugin()],
|
|
113
|
+
history: { maxSnapshots: 100 },
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Multi-module – namespaced access: system.facts.auth.token
|
|
117
|
+
const system = createSystem({
|
|
118
|
+
modules: { auth: authModule, cart: cartModule },
|
|
119
|
+
plugins: [devtoolsPlugin()],
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Lifecycle
|
|
123
|
+
system.start();
|
|
124
|
+
await system.settle(); // Wait for all resolvers to complete
|
|
125
|
+
// ... use the system ...
|
|
126
|
+
system.stop();
|
|
127
|
+
system.destroy();
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Decision Tree: "User says 'fetch data when authenticated'"
|
|
131
|
+
|
|
132
|
+
WRONG thinking: "I'll put the fetch call in a resolver that checks auth."
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// WRONG – resolver doing condition checking + data fetching
|
|
136
|
+
resolvers: {
|
|
137
|
+
fetchData: {
|
|
138
|
+
requirement: "FETCH_DATA",
|
|
139
|
+
resolve: async (req, context) => {
|
|
140
|
+
if (!context.facts.isAuthenticated) {
|
|
141
|
+
return; // Resolver should not check conditions
|
|
142
|
+
}
|
|
143
|
+
const data = await fetch("/api/data");
|
|
144
|
+
context.facts.data = await data.json();
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
CORRECT thinking: "Constraint declares WHEN, resolver declares HOW."
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// CORRECT – constraint declares the need, resolver fulfills it
|
|
154
|
+
constraints: {
|
|
155
|
+
fetchWhenAuthenticated: {
|
|
156
|
+
when: (facts) => facts.isAuthenticated && !facts.data,
|
|
157
|
+
require: { type: "FETCH_DATA" },
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
resolvers: {
|
|
162
|
+
fetchData: {
|
|
163
|
+
requirement: "FETCH_DATA",
|
|
164
|
+
resolve: async (req, context) => {
|
|
165
|
+
const data = await fetch("/api/data");
|
|
166
|
+
context.facts.data = await data.json();
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Reading System State
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// Facts – mutable state
|
|
176
|
+
system.facts.count = 5;
|
|
177
|
+
const val = system.facts.count;
|
|
178
|
+
|
|
179
|
+
// Derivations – read-only computed values
|
|
180
|
+
const loading = system.derive.isLoading;
|
|
181
|
+
|
|
182
|
+
// Events – dispatch user actions
|
|
183
|
+
system.events.increment();
|
|
184
|
+
system.events.setUser({ user: { id: "1", name: "Alice" } });
|
|
185
|
+
|
|
186
|
+
// Subscribe to changes
|
|
187
|
+
const unsub = system.subscribe(["count", "isLoading"], () => {
|
|
188
|
+
console.log(system.facts.count, system.derive.isLoading);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Watch individual values
|
|
192
|
+
system.watch("count", (newVal, oldVal) => {
|
|
193
|
+
console.log(`Count: ${oldVal} -> ${newVal}`);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Wait for a condition
|
|
197
|
+
await system.when((facts) => facts.phase === "done");
|
|
198
|
+
await system.when((facts) => facts.phase === "done", { timeout: 5000 });
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Schema Patterns
|
|
202
|
+
|
|
203
|
+
Only `facts` is required in the schema. Other sections are optional:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// Minimal module – facts only
|
|
207
|
+
const minimal = createModule("minimal", {
|
|
208
|
+
schema: {
|
|
209
|
+
facts: { count: t.number() },
|
|
210
|
+
},
|
|
211
|
+
init: (facts) => {
|
|
212
|
+
facts.count = 0;
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Type assertion pattern (alternative to t.* builders)
|
|
217
|
+
const typed = createModule("typed", {
|
|
218
|
+
schema: {
|
|
219
|
+
facts: {} as { count: number; name: string },
|
|
220
|
+
derivations: {} as { doubled: number },
|
|
221
|
+
},
|
|
222
|
+
init: (facts) => {
|
|
223
|
+
facts.count = 0;
|
|
224
|
+
facts.name = "";
|
|
225
|
+
},
|
|
226
|
+
derive: {
|
|
227
|
+
doubled: (facts) => facts.count * 2,
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## See also
|
|
233
|
+
|
|
234
|
+
- [`constraints.md`](./constraints.md) — the demand side of the constraint-resolver loop
|
|
235
|
+
- [`resolvers.md`](./resolvers.md) — the supply side that fulfills requirements
|
|
236
|
+
- [`schema-types.md`](./schema-types.md) — the `t.*()` builders that define fact and derivation types
|
|
237
|
+
- [`multi-module.md`](./multi-module.md) — when a single module isn't enough
|
|
238
|
+
- [`system-api.md`](./system-api.md) — `createSystem` and the runtime instance once your module is defined
|
|
239
|
+
- [`naming.md`](./naming.md) — terminology conventions every example here follows
|
|
240
|
+
- [`testing.md`](./testing.md) — `createTestSystem` for the modules you're learning to write
|