@directive-run/knowledge 0.2.0 → 0.5.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 +1 -1
- package/README.md +3 -3
- package/ai/ai-adapters.md +7 -7
- package/ai/ai-agents-streaming.md +8 -8
- package/ai/ai-budget-resilience.md +5 -5
- package/ai/ai-communication.md +1 -1
- package/ai/ai-guardrails-memory.md +7 -7
- package/ai/ai-mcp-rag.md +5 -5
- package/ai/ai-multi-agent.md +14 -14
- package/ai/ai-orchestrator.md +8 -8
- package/ai/ai-security.md +2 -2
- package/ai/ai-tasks.md +9 -9
- package/ai/ai-testing-evals.md +2 -2
- package/core/anti-patterns.md +39 -39
- package/core/constraints.md +15 -15
- package/core/core-patterns.md +9 -9
- package/core/error-boundaries.md +7 -7
- package/core/multi-module.md +16 -16
- package/core/naming.md +21 -21
- package/core/plugins.md +14 -14
- package/core/react-adapter.md +13 -13
- package/core/resolvers.md +14 -14
- package/core/schema-types.md +22 -22
- package/core/system-api.md +16 -16
- package/core/testing.md +5 -5
- package/core/time-travel.md +20 -20
- package/dist/index.cjs +6 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7 -97
- package/dist/index.js.map +1 -1
- package/examples/ab-testing.ts +18 -90
- package/examples/ai-checkpoint.ts +68 -87
- package/examples/ai-guardrails.ts +20 -70
- package/examples/auth-flow.ts +2 -2
- package/examples/batch-resolver.ts +19 -59
- package/examples/contact-form.ts +220 -69
- package/examples/counter.ts +77 -95
- package/examples/dashboard-loader.ts +38 -56
- package/examples/debounce-constraints.ts +0 -2
- package/examples/dynamic-modules.ts +17 -20
- package/examples/error-boundaries.ts +30 -81
- package/examples/form-wizard.ts +6 -6
- package/examples/newsletter.ts +24 -51
- package/examples/notifications.ts +24 -23
- package/examples/optimistic-updates.ts +36 -41
- package/examples/pagination.ts +2 -2
- package/examples/permissions.ts +22 -32
- package/examples/provider-routing.ts +26 -83
- package/examples/shopping-cart.ts +12 -12
- package/examples/sudoku.ts +60 -67
- package/examples/theme-locale.ts +4 -7
- package/examples/time-machine.ts +12 -90
- package/examples/topic-guard.ts +31 -39
- package/examples/url-sync.ts +8 -8
- package/examples/websocket.ts +5 -5
- package/package.json +3 -3
package/core/anti-patterns.md
CHANGED
|
@@ -5,17 +5,17 @@
|
|
|
5
5
|
## 1. Unnecessary Type Casting on Facts/Derivations
|
|
6
6
|
|
|
7
7
|
```typescript
|
|
8
|
-
// WRONG
|
|
8
|
+
// WRONG – schema already provides the type
|
|
9
9
|
const profile = system.facts.profile as ResourceState<Profile>;
|
|
10
10
|
|
|
11
|
-
// CORRECT
|
|
11
|
+
// CORRECT – trust the schema
|
|
12
12
|
const profile = system.facts.profile;
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
## 2. Flat Schema (Missing facts Wrapper)
|
|
16
16
|
|
|
17
17
|
```typescript
|
|
18
|
-
// WRONG
|
|
18
|
+
// WRONG – facts must be nested under schema.facts
|
|
19
19
|
createModule("counter", {
|
|
20
20
|
schema: {
|
|
21
21
|
phase: t.string(),
|
|
@@ -37,7 +37,7 @@ createModule("counter", {
|
|
|
37
37
|
## 3. Bare `facts.*` in Multi-Module Constraints
|
|
38
38
|
|
|
39
39
|
```typescript
|
|
40
|
-
// WRONG
|
|
40
|
+
// WRONG – multi-module constraints use facts.self for own module
|
|
41
41
|
constraints: {
|
|
42
42
|
checkItems: {
|
|
43
43
|
when: (facts) => facts.items.length > 0,
|
|
@@ -45,7 +45,7 @@ constraints: {
|
|
|
45
45
|
},
|
|
46
46
|
},
|
|
47
47
|
|
|
48
|
-
// CORRECT
|
|
48
|
+
// CORRECT – use facts.self.* for own module facts
|
|
49
49
|
constraints: {
|
|
50
50
|
checkItems: {
|
|
51
51
|
when: (facts) => facts.self.items.length > 0,
|
|
@@ -57,7 +57,7 @@ constraints: {
|
|
|
57
57
|
## 4. Nonexistent Schema Builders
|
|
58
58
|
|
|
59
59
|
```typescript
|
|
60
|
-
// WRONG
|
|
60
|
+
// WRONG – t.map(), t.set(), t.promise() do not exist
|
|
61
61
|
schema: {
|
|
62
62
|
facts: {
|
|
63
63
|
cache: t.map<string, User>(),
|
|
@@ -66,7 +66,7 @@ schema: {
|
|
|
66
66
|
},
|
|
67
67
|
},
|
|
68
68
|
|
|
69
|
-
// CORRECT
|
|
69
|
+
// CORRECT – use t.object() with type parameter
|
|
70
70
|
schema: {
|
|
71
71
|
facts: {
|
|
72
72
|
cache: t.object<Map<string, User>>(),
|
|
@@ -79,12 +79,12 @@ schema: {
|
|
|
79
79
|
## 5. Abbreviating `context` to `ctx`
|
|
80
80
|
|
|
81
81
|
```typescript
|
|
82
|
-
// WRONG
|
|
82
|
+
// WRONG – never abbreviate context
|
|
83
83
|
resolve: async (req, ctx) => {
|
|
84
84
|
ctx.facts.status = "done";
|
|
85
85
|
},
|
|
86
86
|
|
|
87
|
-
// CORRECT
|
|
87
|
+
// CORRECT – always spell out context
|
|
88
88
|
resolve: async (req, context) => {
|
|
89
89
|
context.facts.status = "done";
|
|
90
90
|
},
|
|
@@ -93,13 +93,13 @@ resolve: async (req, context) => {
|
|
|
93
93
|
## 6. Flat Module Config (No schema Wrapper)
|
|
94
94
|
|
|
95
95
|
```typescript
|
|
96
|
-
// WRONG
|
|
96
|
+
// WRONG – properties must be inside schema.facts
|
|
97
97
|
createModule("timer", {
|
|
98
98
|
phase: t.string(),
|
|
99
99
|
elapsed: t.number(),
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
-
// CORRECT
|
|
102
|
+
// CORRECT – wrap in schema: { facts: {} }
|
|
103
103
|
createModule("timer", {
|
|
104
104
|
schema: {
|
|
105
105
|
facts: {
|
|
@@ -113,21 +113,21 @@ createModule("timer", {
|
|
|
113
113
|
## 7. String-Based Event Dispatch
|
|
114
114
|
|
|
115
115
|
```typescript
|
|
116
|
-
// WRONG
|
|
116
|
+
// WRONG – events are not dispatched by string
|
|
117
117
|
system.dispatch("login", { token: "abc" });
|
|
118
118
|
|
|
119
|
-
// CORRECT
|
|
119
|
+
// CORRECT – use the events accessor
|
|
120
120
|
system.events.login({ token: "abc" });
|
|
121
121
|
```
|
|
122
122
|
|
|
123
123
|
## 8. Direct Array/Object Mutation
|
|
124
124
|
|
|
125
125
|
```typescript
|
|
126
|
-
// WRONG
|
|
126
|
+
// WRONG – proxy cannot detect in-place mutations
|
|
127
127
|
facts.items.push(item);
|
|
128
128
|
facts.config.theme = "dark";
|
|
129
129
|
|
|
130
|
-
// CORRECT
|
|
130
|
+
// CORRECT – replace the entire value
|
|
131
131
|
facts.items = [...facts.items, item];
|
|
132
132
|
facts.config = { ...facts.config, theme: "dark" };
|
|
133
133
|
```
|
|
@@ -135,10 +135,10 @@ facts.config = { ...facts.config, theme: "dark" };
|
|
|
135
135
|
## 9. Nonexistent `useDirective` Hook
|
|
136
136
|
|
|
137
137
|
```typescript
|
|
138
|
-
// WRONG
|
|
138
|
+
// WRONG – there is no useDirective hook
|
|
139
139
|
const state = useDirective(system);
|
|
140
140
|
|
|
141
|
-
// CORRECT
|
|
141
|
+
// CORRECT – use useSelector with a selector function
|
|
142
142
|
const count = useSelector(system, (s) => s.facts.count);
|
|
143
143
|
const isLoading = useSelector(system, (s) => s.derive.isLoading);
|
|
144
144
|
```
|
|
@@ -146,11 +146,11 @@ const isLoading = useSelector(system, (s) => s.derive.isLoading);
|
|
|
146
146
|
## 10. Bracket Notation for Namespaced Facts
|
|
147
147
|
|
|
148
148
|
```typescript
|
|
149
|
-
// WRONG
|
|
149
|
+
// WRONG – internal separator is not part of the public API
|
|
150
150
|
const status = facts["auth::status"];
|
|
151
151
|
const token = facts["auth_token"];
|
|
152
152
|
|
|
153
|
-
// CORRECT
|
|
153
|
+
// CORRECT – use dot notation through the namespace proxy
|
|
154
154
|
const status = facts.auth.status;
|
|
155
155
|
const token = facts.auth.token;
|
|
156
156
|
```
|
|
@@ -158,12 +158,12 @@ const token = facts.auth.token;
|
|
|
158
158
|
## 11. Builder/Chaining API
|
|
159
159
|
|
|
160
160
|
```typescript
|
|
161
|
-
// WRONG
|
|
161
|
+
// WRONG – there is no builder pattern
|
|
162
162
|
const mod = module("counter")
|
|
163
163
|
.schema({ count: t.number() })
|
|
164
164
|
.build();
|
|
165
165
|
|
|
166
|
-
// CORRECT
|
|
166
|
+
// CORRECT – use createModule with object syntax
|
|
167
167
|
const mod = createModule("counter", {
|
|
168
168
|
schema: {
|
|
169
169
|
facts: { count: t.number() },
|
|
@@ -174,14 +174,14 @@ const mod = createModule("counter", {
|
|
|
174
174
|
## 12. Returning Data from Resolvers
|
|
175
175
|
|
|
176
176
|
```typescript
|
|
177
|
-
// WRONG
|
|
177
|
+
// WRONG – resolvers return void, not data
|
|
178
178
|
resolve: async (req, context) => {
|
|
179
179
|
const user = await fetchUser(req.userId);
|
|
180
180
|
|
|
181
181
|
return user; // Return value is ignored
|
|
182
182
|
},
|
|
183
183
|
|
|
184
|
-
// CORRECT
|
|
184
|
+
// CORRECT – mutate context.facts to store results
|
|
185
185
|
resolve: async (req, context) => {
|
|
186
186
|
const user = await fetchUser(req.userId);
|
|
187
187
|
context.facts.user = user;
|
|
@@ -191,13 +191,13 @@ resolve: async (req, context) => {
|
|
|
191
191
|
## 13. Async Logic in `init`
|
|
192
192
|
|
|
193
193
|
```typescript
|
|
194
|
-
// WRONG
|
|
194
|
+
// WRONG – init is synchronous, facts assignment only
|
|
195
195
|
init: async (facts) => {
|
|
196
196
|
const data = await fetch("/api/config");
|
|
197
197
|
facts.config = await data.json();
|
|
198
198
|
},
|
|
199
199
|
|
|
200
|
-
// CORRECT
|
|
200
|
+
// CORRECT – init sets defaults; use constraints/resolvers for async work
|
|
201
201
|
init: (facts) => {
|
|
202
202
|
facts.config = null;
|
|
203
203
|
facts.phase = "loading";
|
|
@@ -214,11 +214,11 @@ constraints: {
|
|
|
214
214
|
## 14. Missing `settle()` After `start()`
|
|
215
215
|
|
|
216
216
|
```typescript
|
|
217
|
-
// WRONG
|
|
217
|
+
// WRONG – constraints fire on start, resolvers are async
|
|
218
218
|
system.start();
|
|
219
219
|
console.log(system.facts.data); // May still be null
|
|
220
220
|
|
|
221
|
-
// CORRECT
|
|
221
|
+
// CORRECT – wait for resolvers to complete
|
|
222
222
|
system.start();
|
|
223
223
|
await system.settle();
|
|
224
224
|
console.log(system.facts.data); // Resolved
|
|
@@ -227,7 +227,7 @@ console.log(system.facts.data); // Resolved
|
|
|
227
227
|
## 15. Missing `crossModuleDeps` Declaration
|
|
228
228
|
|
|
229
229
|
```typescript
|
|
230
|
-
// WRONG
|
|
230
|
+
// WRONG – accessing auth facts without declaring dependency
|
|
231
231
|
const dataModule = createModule("data", {
|
|
232
232
|
schema: { facts: { items: t.array(t.string()) } },
|
|
233
233
|
constraints: {
|
|
@@ -238,7 +238,7 @@ const dataModule = createModule("data", {
|
|
|
238
238
|
},
|
|
239
239
|
});
|
|
240
240
|
|
|
241
|
-
// CORRECT
|
|
241
|
+
// CORRECT – declare crossModuleDeps for type-safe cross-module access
|
|
242
242
|
const dataModule = createModule("data", {
|
|
243
243
|
schema: { facts: { items: t.array(t.string()) } },
|
|
244
244
|
crossModuleDeps: { auth: authSchema },
|
|
@@ -254,7 +254,7 @@ const dataModule = createModule("data", {
|
|
|
254
254
|
## 16. String Literal for `require`
|
|
255
255
|
|
|
256
256
|
```typescript
|
|
257
|
-
// WRONG
|
|
257
|
+
// WRONG – require must be an object with type property
|
|
258
258
|
constraints: {
|
|
259
259
|
check: {
|
|
260
260
|
when: (facts) => facts.ready,
|
|
@@ -262,7 +262,7 @@ constraints: {
|
|
|
262
262
|
},
|
|
263
263
|
},
|
|
264
264
|
|
|
265
|
-
// CORRECT
|
|
265
|
+
// CORRECT – use object form with type
|
|
266
266
|
constraints: {
|
|
267
267
|
check: {
|
|
268
268
|
when: (facts) => facts.ready,
|
|
@@ -274,23 +274,23 @@ constraints: {
|
|
|
274
274
|
## 17. Passthrough Derivations
|
|
275
275
|
|
|
276
276
|
```typescript
|
|
277
|
-
// WRONG
|
|
277
|
+
// WRONG – derivation just returns a fact value unchanged
|
|
278
278
|
derive: {
|
|
279
279
|
count: (facts) => facts.count,
|
|
280
280
|
},
|
|
281
281
|
|
|
282
|
-
// CORRECT
|
|
282
|
+
// CORRECT – remove it, read the fact directly instead
|
|
283
283
|
// system.facts.count instead of system.derive.count
|
|
284
284
|
```
|
|
285
285
|
|
|
286
286
|
## 18. Deep Import Paths
|
|
287
287
|
|
|
288
288
|
```typescript
|
|
289
|
-
// WRONG
|
|
289
|
+
// WRONG – internal module paths are not public API
|
|
290
290
|
import { createModule } from "@directive-run/core/module";
|
|
291
291
|
import { createSystem } from "@directive-run/core/system";
|
|
292
292
|
|
|
293
|
-
// CORRECT
|
|
293
|
+
// CORRECT – import from package root
|
|
294
294
|
import { createModule, createSystem } from "@directive-run/core";
|
|
295
295
|
|
|
296
296
|
// Exception: plugins have their own entry point
|
|
@@ -300,7 +300,7 @@ import { loggingPlugin } from "@directive-run/core/plugins";
|
|
|
300
300
|
## 19. Async `when()` Without `deps`
|
|
301
301
|
|
|
302
302
|
```typescript
|
|
303
|
-
// WRONG
|
|
303
|
+
// WRONG – async constraints need explicit deps for tracking
|
|
304
304
|
constraints: {
|
|
305
305
|
validate: {
|
|
306
306
|
async: true,
|
|
@@ -313,7 +313,7 @@ constraints: {
|
|
|
313
313
|
},
|
|
314
314
|
},
|
|
315
315
|
|
|
316
|
-
// CORRECT
|
|
316
|
+
// CORRECT – add deps array for async constraints
|
|
317
317
|
constraints: {
|
|
318
318
|
validate: {
|
|
319
319
|
async: true,
|
|
@@ -331,7 +331,7 @@ constraints: {
|
|
|
331
331
|
## 20. No Error Handling on Failing Resolvers
|
|
332
332
|
|
|
333
333
|
```typescript
|
|
334
|
-
// WRONG
|
|
334
|
+
// WRONG – unhandled errors crash the system
|
|
335
335
|
resolvers: {
|
|
336
336
|
fetchData: {
|
|
337
337
|
requirement: "FETCH",
|
|
@@ -342,7 +342,7 @@ resolvers: {
|
|
|
342
342
|
},
|
|
343
343
|
},
|
|
344
344
|
|
|
345
|
-
// CORRECT
|
|
345
|
+
// CORRECT – use retry policy and/or module error boundary
|
|
346
346
|
resolvers: {
|
|
347
347
|
fetchData: {
|
|
348
348
|
requirement: "FETCH",
|
package/core/constraints.md
CHANGED
|
@@ -19,10 +19,10 @@ A constraint has two parts: `when` (condition) and `require` (what's needed).
|
|
|
19
19
|
```typescript
|
|
20
20
|
constraints: {
|
|
21
21
|
fetchUserWhenReady: {
|
|
22
|
-
// when() returns boolean
|
|
22
|
+
// when() returns boolean – evaluated on every fact change
|
|
23
23
|
when: (facts) => facts.isAuthenticated && !facts.user,
|
|
24
24
|
|
|
25
|
-
// require
|
|
25
|
+
// require – the requirement to emit when condition is true
|
|
26
26
|
require: { type: "FETCH_USER", userId: facts.userId },
|
|
27
27
|
},
|
|
28
28
|
},
|
|
@@ -31,7 +31,7 @@ constraints: {
|
|
|
31
31
|
### Static vs Dynamic Requirements
|
|
32
32
|
|
|
33
33
|
```typescript
|
|
34
|
-
// Static requirement
|
|
34
|
+
// Static requirement – same object every time
|
|
35
35
|
constraints: {
|
|
36
36
|
loadConfig: {
|
|
37
37
|
when: (facts) => facts.config === null,
|
|
@@ -39,7 +39,7 @@ constraints: {
|
|
|
39
39
|
},
|
|
40
40
|
},
|
|
41
41
|
|
|
42
|
-
// Dynamic requirement
|
|
42
|
+
// Dynamic requirement – function that reads facts
|
|
43
43
|
constraints: {
|
|
44
44
|
fetchUser: {
|
|
45
45
|
when: (facts) => facts.isAuthenticated && !facts.profile,
|
|
@@ -47,7 +47,7 @@ constraints: {
|
|
|
47
47
|
},
|
|
48
48
|
},
|
|
49
49
|
|
|
50
|
-
// Multiple requirements
|
|
50
|
+
// Multiple requirements – return an array
|
|
51
51
|
constraints: {
|
|
52
52
|
loadAll: {
|
|
53
53
|
when: (facts) => facts.phase === "init",
|
|
@@ -137,7 +137,7 @@ constraints: {
|
|
|
137
137
|
Synchronous constraints use auto-tracking (proxy-based). Async constraints cannot be auto-tracked because the function is suspended across await boundaries. The `deps` array tells the engine which facts to watch.
|
|
138
138
|
|
|
139
139
|
```typescript
|
|
140
|
-
// WRONG
|
|
140
|
+
// WRONG – async without deps, engine cannot track dependencies
|
|
141
141
|
constraints: {
|
|
142
142
|
check: {
|
|
143
143
|
async: true,
|
|
@@ -146,7 +146,7 @@ constraints: {
|
|
|
146
146
|
},
|
|
147
147
|
},
|
|
148
148
|
|
|
149
|
-
// CORRECT
|
|
149
|
+
// CORRECT – deps tells the engine to re-evaluate when token changes
|
|
150
150
|
constraints: {
|
|
151
151
|
check: {
|
|
152
152
|
async: true,
|
|
@@ -163,13 +163,13 @@ constraints: {
|
|
|
163
163
|
const system = createSystem({ module: myModule });
|
|
164
164
|
system.start();
|
|
165
165
|
|
|
166
|
-
// Disable a constraint
|
|
166
|
+
// Disable a constraint – it won't be evaluated
|
|
167
167
|
system.constraints.disable("fetchUserWhenReady");
|
|
168
168
|
|
|
169
169
|
// Check if disabled
|
|
170
170
|
system.constraints.isDisabled("fetchUserWhenReady"); // true
|
|
171
171
|
|
|
172
|
-
// Re-enable
|
|
172
|
+
// Re-enable – triggers re-evaluation on next cycle
|
|
173
173
|
system.constraints.enable("fetchUserWhenReady");
|
|
174
174
|
```
|
|
175
175
|
|
|
@@ -178,7 +178,7 @@ system.constraints.enable("fetchUserWhenReady");
|
|
|
178
178
|
### Putting async logic in resolvers instead of constraints
|
|
179
179
|
|
|
180
180
|
```typescript
|
|
181
|
-
// WRONG
|
|
181
|
+
// WRONG – resolver checks conditions (constraint's job)
|
|
182
182
|
resolvers: {
|
|
183
183
|
fetchData: {
|
|
184
184
|
requirement: "FETCH",
|
|
@@ -191,7 +191,7 @@ resolvers: {
|
|
|
191
191
|
},
|
|
192
192
|
},
|
|
193
193
|
|
|
194
|
-
// CORRECT
|
|
194
|
+
// CORRECT – constraint declares when, resolver just does the work
|
|
195
195
|
constraints: {
|
|
196
196
|
fetchWhenAuth: {
|
|
197
197
|
when: (facts) => facts.isAuthenticated && !facts.data,
|
|
@@ -213,16 +213,16 @@ resolvers: {
|
|
|
213
213
|
### String literal for require
|
|
214
214
|
|
|
215
215
|
```typescript
|
|
216
|
-
// WRONG
|
|
216
|
+
// WRONG – require must be an object
|
|
217
217
|
require: "FETCH_DATA",
|
|
218
218
|
|
|
219
|
-
// CORRECT
|
|
219
|
+
// CORRECT – object with type property
|
|
220
220
|
require: { type: "FETCH_DATA" },
|
|
221
221
|
|
|
222
|
-
// CORRECT
|
|
222
|
+
// CORRECT – with payload
|
|
223
223
|
require: { type: "FETCH_DATA", endpoint: "/api/users" },
|
|
224
224
|
|
|
225
|
-
// CORRECT
|
|
225
|
+
// CORRECT – dynamic from facts
|
|
226
226
|
require: (facts) => ({ type: "FETCH_DATA", userId: facts.currentUserId }),
|
|
227
227
|
```
|
|
228
228
|
|
package/core/core-patterns.md
CHANGED
|
@@ -17,7 +17,7 @@ User wants to...
|
|
|
17
17
|
## Module Shape (Canonical Object Syntax)
|
|
18
18
|
|
|
19
19
|
```typescript
|
|
20
|
-
// CORRECT
|
|
20
|
+
// CORRECT – full module definition
|
|
21
21
|
import { createModule, t } from "@directive-run/core";
|
|
22
22
|
|
|
23
23
|
const myModule = createModule("name", {
|
|
@@ -104,14 +104,14 @@ const myModule = createModule("name", {
|
|
|
104
104
|
import { createSystem } from "@directive-run/core";
|
|
105
105
|
import { loggingPlugin, devtoolsPlugin } from "@directive-run/core/plugins";
|
|
106
106
|
|
|
107
|
-
// Single module
|
|
107
|
+
// Single module – direct access: system.facts.count
|
|
108
108
|
const system = createSystem({
|
|
109
109
|
module: myModule,
|
|
110
110
|
plugins: [loggingPlugin(), devtoolsPlugin()],
|
|
111
111
|
debug: { timeTravel: true, maxSnapshots: 100 },
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
-
// Multi-module
|
|
114
|
+
// Multi-module – namespaced access: system.facts.auth.token
|
|
115
115
|
const system = createSystem({
|
|
116
116
|
modules: { auth: authModule, cart: cartModule },
|
|
117
117
|
plugins: [devtoolsPlugin()],
|
|
@@ -130,7 +130,7 @@ system.destroy();
|
|
|
130
130
|
WRONG thinking: "I'll put the fetch call in a resolver that checks auth."
|
|
131
131
|
|
|
132
132
|
```typescript
|
|
133
|
-
// WRONG
|
|
133
|
+
// WRONG – resolver doing condition checking + data fetching
|
|
134
134
|
resolvers: {
|
|
135
135
|
fetchData: {
|
|
136
136
|
requirement: "FETCH_DATA",
|
|
@@ -148,7 +148,7 @@ resolvers: {
|
|
|
148
148
|
CORRECT thinking: "Constraint declares WHEN, resolver declares HOW."
|
|
149
149
|
|
|
150
150
|
```typescript
|
|
151
|
-
// CORRECT
|
|
151
|
+
// CORRECT – constraint declares the need, resolver fulfills it
|
|
152
152
|
constraints: {
|
|
153
153
|
fetchWhenAuthenticated: {
|
|
154
154
|
when: (facts) => facts.isAuthenticated && !facts.data,
|
|
@@ -170,14 +170,14 @@ resolvers: {
|
|
|
170
170
|
## Reading System State
|
|
171
171
|
|
|
172
172
|
```typescript
|
|
173
|
-
// Facts
|
|
173
|
+
// Facts – mutable state
|
|
174
174
|
system.facts.count = 5;
|
|
175
175
|
const val = system.facts.count;
|
|
176
176
|
|
|
177
|
-
// Derivations
|
|
177
|
+
// Derivations – read-only computed values
|
|
178
178
|
const loading = system.derive.isLoading;
|
|
179
179
|
|
|
180
|
-
// Events
|
|
180
|
+
// Events – dispatch user actions
|
|
181
181
|
system.events.increment();
|
|
182
182
|
system.events.setUser({ user: { id: "1", name: "Alice" } });
|
|
183
183
|
|
|
@@ -201,7 +201,7 @@ await system.when((facts) => facts.phase === "done", { timeout: 5000 });
|
|
|
201
201
|
Only `facts` is required in the schema. Other sections are optional:
|
|
202
202
|
|
|
203
203
|
```typescript
|
|
204
|
-
// Minimal module
|
|
204
|
+
// Minimal module – facts only
|
|
205
205
|
const minimal = createModule("minimal", {
|
|
206
206
|
schema: {
|
|
207
207
|
facts: { count: t.number() },
|
package/core/error-boundaries.md
CHANGED
|
@@ -53,7 +53,7 @@ const system = createSystem({
|
|
|
53
53
|
onEffectError: "skip",
|
|
54
54
|
onDerivationError: "throw",
|
|
55
55
|
|
|
56
|
-
// Global error callback
|
|
56
|
+
// Global error callback – fires for all errors
|
|
57
57
|
onError: (error) => {
|
|
58
58
|
// error is a DirectiveError with source tracking
|
|
59
59
|
console.error(`[${error.source}] ${error.sourceId}: ${error.message}`);
|
|
@@ -79,17 +79,17 @@ Use functions instead of strings for conditional recovery:
|
|
|
79
79
|
```typescript
|
|
80
80
|
errorBoundary: {
|
|
81
81
|
onResolverError: (error, resolverId) => {
|
|
82
|
-
// Network errors
|
|
82
|
+
// Network errors – retry later
|
|
83
83
|
if (error.message.includes("NetworkError")) {
|
|
84
84
|
return "retry-later";
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
// Auth errors
|
|
87
|
+
// Auth errors – skip, don't retry
|
|
88
88
|
if (error.message.includes("401")) {
|
|
89
89
|
return "skip";
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
// Everything else
|
|
92
|
+
// Everything else – throw
|
|
93
93
|
return "throw";
|
|
94
94
|
},
|
|
95
95
|
|
|
@@ -125,8 +125,8 @@ try {
|
|
|
125
125
|
} catch (err) {
|
|
126
126
|
if (err instanceof DirectiveError) {
|
|
127
127
|
err.source; // "constraint" | "resolver" | "effect" | "derivation" | "system"
|
|
128
|
-
err.sourceId; // e.g., "fetchUser"
|
|
129
|
-
err.recoverable; // boolean
|
|
128
|
+
err.sourceId; // e.g., "fetchUser" – the specific item that failed
|
|
129
|
+
err.recoverable; // boolean – whether recovery strategies apply
|
|
130
130
|
err.context; // arbitrary debug data (e.g., the requirement object)
|
|
131
131
|
err.message; // human-readable description
|
|
132
132
|
}
|
|
@@ -286,7 +286,7 @@ HALF_OPEN → Limited trial requests allowed
|
|
|
286
286
|
|
|
287
287
|
```typescript
|
|
288
288
|
apiBreaker.getState(); // "CLOSED" | "OPEN" | "HALF_OPEN"
|
|
289
|
-
apiBreaker.isAllowed(); // boolean
|
|
289
|
+
apiBreaker.isAllowed(); // boolean – would a request be allowed?
|
|
290
290
|
apiBreaker.getStats(); // { totalRequests, totalFailures, recentFailures, ... }
|
|
291
291
|
apiBreaker.forceState("CLOSED"); // Force state (useful in tests)
|
|
292
292
|
apiBreaker.reset(); // Reset to CLOSED with cleared stats
|
package/core/multi-module.md
CHANGED
|
@@ -68,7 +68,7 @@ const cartModule = createModule("cart", {
|
|
|
68
68
|
},
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
-
// Multi-module system
|
|
71
|
+
// Multi-module system – namespaced access
|
|
72
72
|
const system = createSystem({
|
|
73
73
|
modules: { auth: authModule, cart: cartModule },
|
|
74
74
|
});
|
|
@@ -79,24 +79,24 @@ system.start();
|
|
|
79
79
|
## Accessing Namespaced State
|
|
80
80
|
|
|
81
81
|
```typescript
|
|
82
|
-
// Facts
|
|
82
|
+
// Facts – namespaced under module name
|
|
83
83
|
system.facts.auth.token;
|
|
84
84
|
system.facts.auth.isAuthenticated;
|
|
85
85
|
system.facts.cart.items;
|
|
86
86
|
|
|
87
|
-
// Derivations
|
|
87
|
+
// Derivations – namespaced under module name
|
|
88
88
|
system.derive.cart.itemCount;
|
|
89
89
|
|
|
90
|
-
// Events
|
|
90
|
+
// Events – namespaced under module name
|
|
91
91
|
system.events.auth.login({ token: "abc123" });
|
|
92
92
|
system.events.auth.logout();
|
|
93
93
|
|
|
94
|
-
// Subscribe
|
|
94
|
+
// Subscribe – use "namespace.key" format
|
|
95
95
|
system.subscribe(["auth.token", "cart.items"], () => {
|
|
96
96
|
console.log("auth or cart changed");
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
-
// Watch
|
|
99
|
+
// Watch – use "namespace.key" format
|
|
100
100
|
system.watch("auth.isAuthenticated", (newVal, oldVal) => {
|
|
101
101
|
console.log(`Auth: ${oldVal} -> ${newVal}`);
|
|
102
102
|
});
|
|
@@ -106,7 +106,7 @@ system.subscribeModule("cart", () => {
|
|
|
106
106
|
console.log("anything in cart changed");
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
// Wait for condition
|
|
109
|
+
// Wait for condition – facts are namespaced
|
|
110
110
|
await system.when((facts) => facts.auth.isAuthenticated);
|
|
111
111
|
```
|
|
112
112
|
|
|
@@ -166,7 +166,7 @@ const dataModule = createModule("data", {
|
|
|
166
166
|
facts.loaded = false;
|
|
167
167
|
},
|
|
168
168
|
|
|
169
|
-
// CORRECT
|
|
169
|
+
// CORRECT – facts.self for own module, facts.auth for cross-module
|
|
170
170
|
constraints: {
|
|
171
171
|
fetchWhenAuth: {
|
|
172
172
|
when: (facts) => facts.auth.isAuthenticated && !facts.self.loaded,
|
|
@@ -203,7 +203,7 @@ const dataModule = createModule("data", {
|
|
|
203
203
|
### Using bare `facts.*` instead of `facts.self.*`
|
|
204
204
|
|
|
205
205
|
```typescript
|
|
206
|
-
// WRONG
|
|
206
|
+
// WRONG – in cross-module context, bare facts has no self-module properties
|
|
207
207
|
constraints: {
|
|
208
208
|
check: {
|
|
209
209
|
when: (facts) => facts.loaded, // TypeScript error
|
|
@@ -211,7 +211,7 @@ constraints: {
|
|
|
211
211
|
},
|
|
212
212
|
},
|
|
213
213
|
|
|
214
|
-
// CORRECT
|
|
214
|
+
// CORRECT – use facts.self for own module
|
|
215
215
|
constraints: {
|
|
216
216
|
check: {
|
|
217
217
|
when: (facts) => facts.self.loaded,
|
|
@@ -223,11 +223,11 @@ constraints: {
|
|
|
223
223
|
### Bracket notation for internal keys
|
|
224
224
|
|
|
225
225
|
```typescript
|
|
226
|
-
// WRONG
|
|
226
|
+
// WRONG – the :: separator is internal, never use it directly
|
|
227
227
|
system.facts["auth::token"];
|
|
228
228
|
system.read("auth::status");
|
|
229
229
|
|
|
230
|
-
// CORRECT
|
|
230
|
+
// CORRECT – dot notation through namespace proxy
|
|
231
231
|
system.facts.auth.token;
|
|
232
232
|
system.read("auth.status");
|
|
233
233
|
```
|
|
@@ -235,7 +235,7 @@ system.read("auth.status");
|
|
|
235
235
|
### Forgetting crossModuleDeps
|
|
236
236
|
|
|
237
237
|
```typescript
|
|
238
|
-
// WRONG
|
|
238
|
+
// WRONG – facts.auth is untyped without crossModuleDeps
|
|
239
239
|
const dataModule = createModule("data", {
|
|
240
240
|
schema: { facts: { items: t.array(t.string()) } },
|
|
241
241
|
constraints: {
|
|
@@ -246,7 +246,7 @@ const dataModule = createModule("data", {
|
|
|
246
246
|
},
|
|
247
247
|
});
|
|
248
248
|
|
|
249
|
-
// CORRECT
|
|
249
|
+
// CORRECT – declare the dependency
|
|
250
250
|
const dataModule = createModule("data", {
|
|
251
251
|
schema: { facts: { items: t.array(t.string()) } },
|
|
252
252
|
crossModuleDeps: { auth: authSchema },
|
|
@@ -265,13 +265,13 @@ const dataModule = createModule("data", {
|
|
|
265
265
|
const system = createSystem({
|
|
266
266
|
modules: { auth: authModule, cart: cartModule, data: dataModule },
|
|
267
267
|
|
|
268
|
-
// Initial facts
|
|
268
|
+
// Initial facts – applied after init(), before first reconciliation
|
|
269
269
|
initialFacts: {
|
|
270
270
|
auth: { token: "restored-token" },
|
|
271
271
|
cart: { items: cachedItems },
|
|
272
272
|
},
|
|
273
273
|
|
|
274
|
-
// Init order
|
|
274
|
+
// Init order – control module initialization sequence
|
|
275
275
|
initOrder: "auto", // Sort by crossModuleDeps topology (default)
|
|
276
276
|
// initOrder: "declaration", // Use object key order
|
|
277
277
|
// initOrder: ["auth", "data", "cart"], // Explicit order
|