@directive-run/knowledge 0.2.0 → 0.4.2
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 +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 +37 -55
- package/examples/debounce-constraints.ts +0 -2
- package/examples/dynamic-modules.ts +17 -20
- package/examples/error-boundaries.ts +30 -81
- package/examples/newsletter.ts +22 -49
- 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 +8 -8
- package/examples/sudoku.ts +55 -62
- package/examples/theme-locale.ts +4 -7
- package/examples/time-machine.ts +12 -90
- package/examples/topic-guard.ts +30 -38
- package/examples/url-sync.ts +8 -8
- package/examples/websocket.ts +5 -5
- package/package.json +3 -3
package/core/naming.md
CHANGED
|
@@ -33,7 +33,7 @@ Fulfillment logic?
|
|
|
33
33
|
The `req` parameter in resolvers and constraint `key()` functions is short for **requirement** -- the object emitted by a constraint's `require` property.
|
|
34
34
|
|
|
35
35
|
```typescript
|
|
36
|
-
// CORRECT
|
|
36
|
+
// CORRECT – req is a requirement
|
|
37
37
|
resolvers: {
|
|
38
38
|
fetchUser: {
|
|
39
39
|
requirement: "FETCH_USER",
|
|
@@ -47,7 +47,7 @@ resolvers: {
|
|
|
47
47
|
},
|
|
48
48
|
},
|
|
49
49
|
|
|
50
|
-
// WRONG
|
|
50
|
+
// WRONG – never use "request" or "r"
|
|
51
51
|
resolve: async (request, context) => { /* ... */ },
|
|
52
52
|
resolve: async (r, context) => { /* ... */ },
|
|
53
53
|
```
|
|
@@ -62,7 +62,7 @@ resolve: async (req, context) => {
|
|
|
62
62
|
context.snapshot(); // facts snapshot
|
|
63
63
|
},
|
|
64
64
|
|
|
65
|
-
// WRONG
|
|
65
|
+
// WRONG – never abbreviate to ctx
|
|
66
66
|
resolve: async (req, ctx) => { /* ... */ },
|
|
67
67
|
```
|
|
68
68
|
|
|
@@ -88,10 +88,10 @@ constraints: {
|
|
|
88
88
|
// Wait -- the above IS correct for one-line arrow expressions.
|
|
89
89
|
// The brace rule applies to if/return blocks:
|
|
90
90
|
|
|
91
|
-
// WRONG
|
|
91
|
+
// WRONG – single-line if return
|
|
92
92
|
if (facts.user) return "ready";
|
|
93
93
|
|
|
94
|
-
// CORRECT
|
|
94
|
+
// CORRECT – always use braces
|
|
95
95
|
if (facts.user) {
|
|
96
96
|
return "ready";
|
|
97
97
|
}
|
|
@@ -102,7 +102,7 @@ if (facts.user) {
|
|
|
102
102
|
Add a blank line before `return` when there is code above it. Skip the blank line when `return` is the first statement in a block.
|
|
103
103
|
|
|
104
104
|
```typescript
|
|
105
|
-
// CORRECT
|
|
105
|
+
// CORRECT – blank line before return when code precedes it
|
|
106
106
|
function getStatus(facts) {
|
|
107
107
|
const phase = facts.phase;
|
|
108
108
|
const hasUser = facts.user !== null;
|
|
@@ -110,12 +110,12 @@ function getStatus(facts) {
|
|
|
110
110
|
return phase === "ready" && hasUser;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
// CORRECT
|
|
113
|
+
// CORRECT – no blank line when return is first statement
|
|
114
114
|
function isReady(facts) {
|
|
115
115
|
return facts.phase === "ready";
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
// CORRECT
|
|
118
|
+
// CORRECT – blank line after brace-style return block
|
|
119
119
|
function process(facts) {
|
|
120
120
|
if (!facts.ready) {
|
|
121
121
|
return null;
|
|
@@ -126,7 +126,7 @@ function process(facts) {
|
|
|
126
126
|
return result;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
// WRONG
|
|
129
|
+
// WRONG – no blank line before return after code
|
|
130
130
|
function getStatus(facts) {
|
|
131
131
|
const phase = facts.phase;
|
|
132
132
|
return phase === "ready"; // Missing blank line
|
|
@@ -138,13 +138,13 @@ function getStatus(facts) {
|
|
|
138
138
|
Never put properties or statements on a single line inside braces. Always expand to one item per line with proper indentation. This applies everywhere: schema definitions, init functions, events, effects, requirement types, and any other object or block.
|
|
139
139
|
|
|
140
140
|
```typescript
|
|
141
|
-
// WRONG
|
|
141
|
+
// WRONG – properties crammed on one line
|
|
142
142
|
schema: {
|
|
143
143
|
facts: { phase: t.string(), count: t.number() },
|
|
144
144
|
requirements: { FETCH_USER: { id: t.string() }, RESET: {} },
|
|
145
145
|
},
|
|
146
146
|
|
|
147
|
-
// CORRECT
|
|
147
|
+
// CORRECT – one property per line, always expanded
|
|
148
148
|
schema: {
|
|
149
149
|
facts: {
|
|
150
150
|
phase: t.string(),
|
|
@@ -158,10 +158,10 @@ schema: {
|
|
|
158
158
|
},
|
|
159
159
|
},
|
|
160
160
|
|
|
161
|
-
// WRONG
|
|
161
|
+
// WRONG – statements crammed on one line
|
|
162
162
|
init: (facts) => { facts.phase = "idle"; facts.count = 0; },
|
|
163
163
|
|
|
164
|
-
// CORRECT
|
|
164
|
+
// CORRECT – one statement per line
|
|
165
165
|
init: (facts) => {
|
|
166
166
|
facts.phase = "idle";
|
|
167
167
|
facts.count = 0;
|
|
@@ -181,12 +181,12 @@ events: {
|
|
|
181
181
|
|
|
182
182
|
Single-expression arrows (no braces) are fine on one line. Empty objects `{}` are fine inline.
|
|
183
183
|
```typescript
|
|
184
|
-
// OK
|
|
184
|
+
// OK – single expression, no braces
|
|
185
185
|
derive: {
|
|
186
186
|
isReady: (facts) => facts.phase === "ready",
|
|
187
187
|
},
|
|
188
188
|
|
|
189
|
-
// OK
|
|
189
|
+
// OK – empty object
|
|
190
190
|
RESET: {},
|
|
191
191
|
```
|
|
192
192
|
|
|
@@ -205,7 +205,7 @@ constraints: {
|
|
|
205
205
|
},
|
|
206
206
|
},
|
|
207
207
|
|
|
208
|
-
// WRONG
|
|
208
|
+
// WRONG – bare facts.* in multi-module context
|
|
209
209
|
constraints: {
|
|
210
210
|
loadWhenAuth: {
|
|
211
211
|
when: (facts) => facts.isAuthenticated && !facts.loaded,
|
|
@@ -217,13 +217,13 @@ constraints: {
|
|
|
217
217
|
### System-Level Access Uses Dot Notation
|
|
218
218
|
|
|
219
219
|
```typescript
|
|
220
|
-
// CORRECT
|
|
220
|
+
// CORRECT – dot notation through namespace proxy
|
|
221
221
|
system.facts.auth.token;
|
|
222
222
|
system.facts.cart.items;
|
|
223
223
|
system.derive.auth.isLoggedIn;
|
|
224
224
|
system.events.auth.login({ token: "..." });
|
|
225
225
|
|
|
226
|
-
// WRONG
|
|
226
|
+
// WRONG – bracket notation with internal separator
|
|
227
227
|
system.facts["auth::token"];
|
|
228
228
|
system.facts["auth_token"];
|
|
229
229
|
```
|
|
@@ -235,11 +235,11 @@ system.facts["auth_token"];
|
|
|
235
235
|
The schema provides all types. Do not add `as` casts when reading facts or derivations from the system.
|
|
236
236
|
|
|
237
237
|
```typescript
|
|
238
|
-
// CORRECT
|
|
238
|
+
// CORRECT – schema provides the type
|
|
239
239
|
const profile = system.facts.profile;
|
|
240
240
|
const isReady = system.derive.isReady;
|
|
241
241
|
|
|
242
|
-
// WRONG
|
|
242
|
+
// WRONG – unnecessary cast
|
|
243
243
|
const profile = system.facts.profile as UserProfile;
|
|
244
244
|
const isReady = system.derive.isReady as boolean;
|
|
245
245
|
```
|
|
@@ -249,7 +249,7 @@ const isReady = system.derive.isReady as boolean;
|
|
|
249
249
|
Type assertions are only valid in schema definition using the `{} as {}` pattern:
|
|
250
250
|
|
|
251
251
|
```typescript
|
|
252
|
-
// CORRECT
|
|
252
|
+
// CORRECT – cast in schema definition
|
|
253
253
|
schema: {
|
|
254
254
|
facts: {} as { profile: UserProfile; settings: AppSettings },
|
|
255
255
|
derivations: {} as { displayName: string },
|
package/core/plugins.md
CHANGED
|
@@ -46,13 +46,13 @@ Logs state changes, requirements, and resolutions to the console.
|
|
|
46
46
|
```typescript
|
|
47
47
|
import { loggingPlugin } from "@directive-run/core/plugins";
|
|
48
48
|
|
|
49
|
-
// Default
|
|
49
|
+
// Default – logs facts changes and resolver start/complete
|
|
50
50
|
loggingPlugin()
|
|
51
51
|
|
|
52
|
-
// Verbose
|
|
52
|
+
// Verbose – logs everything including derivation recomputation and constraint evaluation
|
|
53
53
|
loggingPlugin({ verbose: true })
|
|
54
54
|
|
|
55
|
-
// Custom filter
|
|
55
|
+
// Custom filter – only log specific events
|
|
56
56
|
loggingPlugin({
|
|
57
57
|
filter: (event) => {
|
|
58
58
|
// Only log resolver events
|
|
@@ -97,7 +97,7 @@ persistencePlugin({
|
|
|
97
97
|
storage: localStorage,
|
|
98
98
|
})
|
|
99
99
|
|
|
100
|
-
// sessionStorage
|
|
100
|
+
// sessionStorage – cleared when tab closes
|
|
101
101
|
persistencePlugin({
|
|
102
102
|
key: "session-state",
|
|
103
103
|
storage: sessionStorage,
|
|
@@ -113,7 +113,7 @@ persistencePlugin({
|
|
|
113
113
|
},
|
|
114
114
|
})
|
|
115
115
|
|
|
116
|
-
// Selective persistence
|
|
116
|
+
// Selective persistence – only persist certain facts
|
|
117
117
|
persistencePlugin({
|
|
118
118
|
key: "my-app",
|
|
119
119
|
storage: localStorage,
|
|
@@ -122,7 +122,7 @@ persistencePlugin({
|
|
|
122
122
|
exclude: ["tempData", "sessionId"], // Everything except these
|
|
123
123
|
})
|
|
124
124
|
|
|
125
|
-
// Versioning
|
|
125
|
+
// Versioning – handle schema changes
|
|
126
126
|
persistencePlugin({
|
|
127
127
|
key: "my-app",
|
|
128
128
|
storage: localStorage,
|
|
@@ -267,13 +267,13 @@ const system = createSystem({
|
|
|
267
267
|
### Enabling devtools in production
|
|
268
268
|
|
|
269
269
|
```typescript
|
|
270
|
-
// WRONG
|
|
270
|
+
// WRONG – devtools overhead in production
|
|
271
271
|
const system = createSystem({
|
|
272
272
|
module: myModule,
|
|
273
273
|
plugins: [devtoolsPlugin()],
|
|
274
274
|
});
|
|
275
275
|
|
|
276
|
-
// CORRECT
|
|
276
|
+
// CORRECT – conditional on environment
|
|
277
277
|
const plugins = [];
|
|
278
278
|
if (process.env.NODE_ENV === "development") {
|
|
279
279
|
plugins.push(devtoolsPlugin());
|
|
@@ -289,13 +289,13 @@ const system = createSystem({
|
|
|
289
289
|
### Persistence without versioning
|
|
290
290
|
|
|
291
291
|
```typescript
|
|
292
|
-
// WRONG
|
|
292
|
+
// WRONG – schema changes break existing users
|
|
293
293
|
persistencePlugin({
|
|
294
294
|
key: "app-state",
|
|
295
295
|
storage: localStorage,
|
|
296
296
|
})
|
|
297
297
|
|
|
298
|
-
// CORRECT
|
|
298
|
+
// CORRECT – version and migrate
|
|
299
299
|
persistencePlugin({
|
|
300
300
|
key: "app-state",
|
|
301
301
|
storage: localStorage,
|
|
@@ -307,7 +307,7 @@ persistencePlugin({
|
|
|
307
307
|
### Plugin order matters
|
|
308
308
|
|
|
309
309
|
```typescript
|
|
310
|
-
// WRONG
|
|
310
|
+
// WRONG – logging misses events from persistence restore
|
|
311
311
|
const system = createSystem({
|
|
312
312
|
module: myModule,
|
|
313
313
|
plugins: [
|
|
@@ -316,7 +316,7 @@ const system = createSystem({
|
|
|
316
316
|
],
|
|
317
317
|
});
|
|
318
318
|
|
|
319
|
-
// CORRECT
|
|
319
|
+
// CORRECT – logging first to capture everything
|
|
320
320
|
const system = createSystem({
|
|
321
321
|
module: myModule,
|
|
322
322
|
plugins: [
|
|
@@ -329,13 +329,13 @@ const system = createSystem({
|
|
|
329
329
|
### Persisting sensitive or transient data
|
|
330
330
|
|
|
331
331
|
```typescript
|
|
332
|
-
// WRONG
|
|
332
|
+
// WRONG – persists auth tokens and loading state
|
|
333
333
|
persistencePlugin({
|
|
334
334
|
key: "app",
|
|
335
335
|
storage: localStorage,
|
|
336
336
|
})
|
|
337
337
|
|
|
338
|
-
// CORRECT
|
|
338
|
+
// CORRECT – exclude sensitive and transient facts
|
|
339
339
|
persistencePlugin({
|
|
340
340
|
key: "app",
|
|
341
341
|
storage: localStorage,
|
package/core/react-adapter.md
CHANGED
|
@@ -18,7 +18,7 @@ What are you building?
|
|
|
18
18
|
Create the system outside of React. Components subscribe to it.
|
|
19
19
|
|
|
20
20
|
```typescript
|
|
21
|
-
// system.ts
|
|
21
|
+
// system.ts – created once, imported anywhere
|
|
22
22
|
import { createSystem } from "@directive-run/core";
|
|
23
23
|
import { counterModule } from "./counter-module";
|
|
24
24
|
|
|
@@ -31,7 +31,7 @@ import { useSelector, useEvent } from "@directive-run/react";
|
|
|
31
31
|
import { system } from "./system";
|
|
32
32
|
|
|
33
33
|
function Counter() {
|
|
34
|
-
// Subscribe to derived state
|
|
34
|
+
// Subscribe to derived state – re-renders only when value changes
|
|
35
35
|
const count = useSelector(system, (s) => s.facts.count);
|
|
36
36
|
const doubled = useSelector(system, (s) => s.derive.doubled);
|
|
37
37
|
|
|
@@ -174,10 +174,10 @@ function Dashboard() {
|
|
|
174
174
|
## CRITICAL: Hooks That DO NOT Exist
|
|
175
175
|
|
|
176
176
|
```typescript
|
|
177
|
-
// WRONG
|
|
177
|
+
// WRONG – useDirective() does not exist. This is a common hallucination.
|
|
178
178
|
const { facts, derive, events } = useDirective(system);
|
|
179
179
|
|
|
180
|
-
// CORRECT
|
|
180
|
+
// CORRECT – use useSelector for state, useEvent for actions
|
|
181
181
|
const count = useSelector(system, (s) => s.facts.count);
|
|
182
182
|
const events = useEvent(system);
|
|
183
183
|
```
|
|
@@ -187,7 +187,7 @@ const events = useEvent(system);
|
|
|
187
187
|
### Creating the system inside a component without useSystem
|
|
188
188
|
|
|
189
189
|
```typescript
|
|
190
|
-
// WRONG
|
|
190
|
+
// WRONG – creates a new system on every render
|
|
191
191
|
function Counter() {
|
|
192
192
|
const system = createSystem({ module: counterModule }); // New system each render!
|
|
193
193
|
const count = useSelector(system, (s) => s.facts.count);
|
|
@@ -195,7 +195,7 @@ function Counter() {
|
|
|
195
195
|
return <div>{count}</div>;
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
// CORRECT
|
|
198
|
+
// CORRECT – create outside the component
|
|
199
199
|
const system = createSystem({ module: counterModule });
|
|
200
200
|
|
|
201
201
|
function Counter() {
|
|
@@ -204,7 +204,7 @@ function Counter() {
|
|
|
204
204
|
return <div>{count}</div>;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
// ALSO CORRECT
|
|
207
|
+
// ALSO CORRECT – useSystem manages lifecycle
|
|
208
208
|
function Counter() {
|
|
209
209
|
const system = useSystem({ module: counterModule });
|
|
210
210
|
const count = useSelector(system, (s) => s.facts.count);
|
|
@@ -216,10 +216,10 @@ function Counter() {
|
|
|
216
216
|
### Selecting too much state (causes unnecessary re-renders)
|
|
217
217
|
|
|
218
218
|
```typescript
|
|
219
|
-
// WRONG
|
|
219
|
+
// WRONG – re-renders on ANY fact change
|
|
220
220
|
const allFacts = useSelector(system, (s) => s.facts);
|
|
221
221
|
|
|
222
|
-
// CORRECT
|
|
222
|
+
// CORRECT – select only what you need
|
|
223
223
|
const name = useSelector(system, (s) => s.facts.userName);
|
|
224
224
|
const count = useSelector(system, (s) => s.facts.count);
|
|
225
225
|
```
|
|
@@ -227,7 +227,7 @@ const count = useSelector(system, (s) => s.facts.count);
|
|
|
227
227
|
### Mutating facts directly in event handlers
|
|
228
228
|
|
|
229
229
|
```typescript
|
|
230
|
-
// WRONG
|
|
230
|
+
// WRONG – bypass the event system
|
|
231
231
|
function Counter() {
|
|
232
232
|
const count = useSelector(system, (s) => s.facts.count);
|
|
233
233
|
|
|
@@ -238,7 +238,7 @@ function Counter() {
|
|
|
238
238
|
);
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
// CORRECT
|
|
241
|
+
// CORRECT – use events for intent-driven mutations
|
|
242
242
|
function Counter() {
|
|
243
243
|
const count = useSelector(system, (s) => s.facts.count);
|
|
244
244
|
const events = useEvent(system);
|
|
@@ -254,9 +254,9 @@ function Counter() {
|
|
|
254
254
|
### Casting values from useSelector
|
|
255
255
|
|
|
256
256
|
```typescript
|
|
257
|
-
// WRONG
|
|
257
|
+
// WRONG – unnecessary type casting
|
|
258
258
|
const profile = useSelector(system, (s) => s.facts.profile as UserProfile);
|
|
259
259
|
|
|
260
|
-
// CORRECT
|
|
260
|
+
// CORRECT – types are inferred from the module schema
|
|
261
261
|
const profile = useSelector(system, (s) => s.facts.profile);
|
|
262
262
|
```
|
package/core/resolvers.md
CHANGED
|
@@ -16,7 +16,7 @@ Is the work async (API calls, timers)?
|
|
|
16
16
|
│ ├── Yes → Add batch config to group them
|
|
17
17
|
│ └── No → Single resolve is fine
|
|
18
18
|
│
|
|
19
|
-
└── No → Reconsider
|
|
19
|
+
└── No → Reconsider – maybe this is an event handler or derivation
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
## Basic Resolver
|
|
@@ -27,12 +27,12 @@ resolvers: {
|
|
|
27
27
|
// Which requirement type this resolver handles
|
|
28
28
|
requirement: "FETCH_USER",
|
|
29
29
|
|
|
30
|
-
// Async function
|
|
30
|
+
// Async function – req is the requirement, context has facts + signal
|
|
31
31
|
resolve: async (req, context) => {
|
|
32
32
|
const res = await fetch(`/api/users/${req.userId}`);
|
|
33
33
|
const user = await res.json();
|
|
34
34
|
|
|
35
|
-
// Mutate facts to store results
|
|
35
|
+
// Mutate facts to store results – resolvers return void
|
|
36
36
|
context.facts.user = user;
|
|
37
37
|
context.facts.phase = "loaded";
|
|
38
38
|
},
|
|
@@ -46,13 +46,13 @@ The `context` object provides:
|
|
|
46
46
|
|
|
47
47
|
```typescript
|
|
48
48
|
resolve: async (req, context) => {
|
|
49
|
-
// context.facts
|
|
49
|
+
// context.facts – mutable proxy to the module's facts
|
|
50
50
|
context.facts.status = "loading";
|
|
51
51
|
|
|
52
|
-
// context.signal
|
|
52
|
+
// context.signal – AbortSignal, cancelled when system stops or requirement removed
|
|
53
53
|
const res = await fetch("/api/data", { signal: context.signal });
|
|
54
54
|
|
|
55
|
-
// context.snapshot()
|
|
55
|
+
// context.snapshot() – read-only snapshot for before/after comparisons
|
|
56
56
|
const before = context.snapshot();
|
|
57
57
|
context.facts.count += 1;
|
|
58
58
|
const after = context.snapshot();
|
|
@@ -69,7 +69,7 @@ resolvers: {
|
|
|
69
69
|
fetchUser: {
|
|
70
70
|
requirement: "FETCH_USER",
|
|
71
71
|
|
|
72
|
-
// Custom key
|
|
72
|
+
// Custom key – only one inflight resolver per userId
|
|
73
73
|
key: (req) => `fetch-user-${req.userId}`,
|
|
74
74
|
|
|
75
75
|
resolve: async (req, context) => {
|
|
@@ -270,14 +270,14 @@ const explanation = system.explain("req-123");
|
|
|
270
270
|
### Returning data from resolve
|
|
271
271
|
|
|
272
272
|
```typescript
|
|
273
|
-
// WRONG
|
|
273
|
+
// WRONG – return value is ignored
|
|
274
274
|
resolve: async (req, context) => {
|
|
275
275
|
const user = await fetchUser(req.userId);
|
|
276
276
|
|
|
277
277
|
return user; // Ignored!
|
|
278
278
|
},
|
|
279
279
|
|
|
280
|
-
// CORRECT
|
|
280
|
+
// CORRECT – mutate context.facts
|
|
281
281
|
resolve: async (req, context) => {
|
|
282
282
|
const user = await fetchUser(req.userId);
|
|
283
283
|
context.facts.user = user;
|
|
@@ -297,7 +297,7 @@ resolve: async (req, context) => { /* ... */ },
|
|
|
297
297
|
### Checking conditions in resolve (constraint's job)
|
|
298
298
|
|
|
299
299
|
```typescript
|
|
300
|
-
// WRONG
|
|
300
|
+
// WRONG – condition checking belongs in constraint's when()
|
|
301
301
|
resolve: async (req, context) => {
|
|
302
302
|
if (!context.facts.isAuthenticated) {
|
|
303
303
|
return;
|
|
@@ -305,7 +305,7 @@ resolve: async (req, context) => {
|
|
|
305
305
|
// ...
|
|
306
306
|
},
|
|
307
307
|
|
|
308
|
-
// CORRECT
|
|
308
|
+
// CORRECT – let constraints handle conditions
|
|
309
309
|
// The resolver only runs when a requirement is emitted
|
|
310
310
|
resolve: async (req, context) => {
|
|
311
311
|
const data = await fetch("/api/data");
|
|
@@ -316,7 +316,7 @@ resolve: async (req, context) => {
|
|
|
316
316
|
### Forgetting error handling
|
|
317
317
|
|
|
318
318
|
```typescript
|
|
319
|
-
// WRONG
|
|
319
|
+
// WRONG – unhandled errors with no recovery
|
|
320
320
|
resolvers: {
|
|
321
321
|
fetch: {
|
|
322
322
|
requirement: "FETCH",
|
|
@@ -327,7 +327,7 @@ resolvers: {
|
|
|
327
327
|
},
|
|
328
328
|
},
|
|
329
329
|
|
|
330
|
-
// CORRECT
|
|
330
|
+
// CORRECT – retry policy + error handling
|
|
331
331
|
resolvers: {
|
|
332
332
|
fetch: {
|
|
333
333
|
requirement: "FETCH",
|
|
@@ -346,7 +346,7 @@ resolvers: {
|
|
|
346
346
|
### Missing settle() after start()
|
|
347
347
|
|
|
348
348
|
```typescript
|
|
349
|
-
// WRONG
|
|
349
|
+
// WRONG – reading facts before resolvers finish
|
|
350
350
|
system.start();
|
|
351
351
|
console.log(system.facts.data); // Likely null
|
|
352
352
|
|
package/core/schema-types.md
CHANGED
|
@@ -32,7 +32,7 @@ const myModule = createModule("example", {
|
|
|
32
32
|
// Basic string
|
|
33
33
|
name: t.string(),
|
|
34
34
|
|
|
35
|
-
// String literal union
|
|
35
|
+
// String literal union – full type safety
|
|
36
36
|
phase: t.string<"idle" | "loading" | "done">(),
|
|
37
37
|
|
|
38
38
|
// Number with validation
|
|
@@ -85,11 +85,11 @@ schema: {
|
|
|
85
85
|
```typescript
|
|
86
86
|
schema: {
|
|
87
87
|
facts: {
|
|
88
|
-
// Enum
|
|
88
|
+
// Enum – string literal union from values
|
|
89
89
|
status: t.enum("pending", "active", "archived"),
|
|
90
90
|
// TypeScript type: "pending" | "active" | "archived"
|
|
91
91
|
|
|
92
|
-
// Literal
|
|
92
|
+
// Literal – exact match
|
|
93
93
|
version: t.literal(2),
|
|
94
94
|
mode: t.literal("strict"),
|
|
95
95
|
enabled: t.literal(true),
|
|
@@ -102,13 +102,13 @@ schema: {
|
|
|
102
102
|
```typescript
|
|
103
103
|
schema: {
|
|
104
104
|
facts: {
|
|
105
|
-
// Nullable
|
|
105
|
+
// Nullable – T | null
|
|
106
106
|
selectedItem: t.nullable(t.string()),
|
|
107
107
|
|
|
108
|
-
// Optional
|
|
108
|
+
// Optional – T | undefined
|
|
109
109
|
nickname: t.optional(t.string()),
|
|
110
110
|
|
|
111
|
-
// Union
|
|
111
|
+
// Union – combine types
|
|
112
112
|
result: t.union(t.string(), t.number()),
|
|
113
113
|
|
|
114
114
|
// Nullable via object generic (also valid)
|
|
@@ -122,28 +122,28 @@ schema: {
|
|
|
122
122
|
```typescript
|
|
123
123
|
schema: {
|
|
124
124
|
facts: {
|
|
125
|
-
// Default value
|
|
125
|
+
// Default value – used if init doesn't set it
|
|
126
126
|
theme: t.string<"light" | "dark">().default("light"),
|
|
127
127
|
|
|
128
|
-
// Custom validation
|
|
128
|
+
// Custom validation – runs in dev mode
|
|
129
129
|
email: t.string().validate((val) => val.includes("@")),
|
|
130
130
|
|
|
131
|
-
// Transform on set
|
|
131
|
+
// Transform on set – runs when fact is mutated
|
|
132
132
|
slug: t.string().transform((val) => val.toLowerCase().replace(/\s+/g, "-")),
|
|
133
133
|
|
|
134
|
-
// Branded type
|
|
134
|
+
// Branded type – nominal typing
|
|
135
135
|
userId: t.string().brand<"UserId">(),
|
|
136
136
|
|
|
137
|
-
// Description
|
|
137
|
+
// Description – for docs and devtools
|
|
138
138
|
retryCount: t.number().describe("Number of failed attempts"),
|
|
139
139
|
|
|
140
|
-
// Refinement
|
|
140
|
+
// Refinement – predicate with error message
|
|
141
141
|
port: t.number().refine(
|
|
142
142
|
(n) => n >= 1 && n <= 65535,
|
|
143
143
|
"Port must be between 1 and 65535",
|
|
144
144
|
),
|
|
145
145
|
|
|
146
|
-
// Chaining
|
|
146
|
+
// Chaining – combine multiple modifiers
|
|
147
147
|
score: t.number()
|
|
148
148
|
.min(0)
|
|
149
149
|
.max(100)
|
|
@@ -193,43 +193,43 @@ schema: {
|
|
|
193
193
|
These are common AI hallucinations. Do not use them.
|
|
194
194
|
|
|
195
195
|
```typescript
|
|
196
|
-
// WRONG
|
|
196
|
+
// WRONG – t.map() does not exist
|
|
197
197
|
items: t.map<string, number>(),
|
|
198
198
|
// CORRECT
|
|
199
199
|
items: t.object<Map<string, number>>(),
|
|
200
200
|
|
|
201
|
-
// WRONG
|
|
201
|
+
// WRONG – t.set() does not exist
|
|
202
202
|
tags: t.set<string>(),
|
|
203
203
|
// CORRECT
|
|
204
204
|
tags: t.object<Set<string>>(),
|
|
205
205
|
|
|
206
|
-
// WRONG
|
|
206
|
+
// WRONG – t.date() does not exist
|
|
207
207
|
createdAt: t.date(),
|
|
208
208
|
// CORRECT
|
|
209
209
|
createdAt: t.object<Date>(),
|
|
210
210
|
// OR use timestamps
|
|
211
211
|
createdAt: t.number(), // Unix ms
|
|
212
212
|
|
|
213
|
-
// WRONG
|
|
213
|
+
// WRONG – t.tuple() does not exist
|
|
214
214
|
coords: t.tuple<[number, number]>(),
|
|
215
215
|
// CORRECT
|
|
216
216
|
coords: t.array<[number, number]>(),
|
|
217
217
|
|
|
218
|
-
// WRONG
|
|
218
|
+
// WRONG – t.record() does not exist
|
|
219
219
|
scores: t.record<string, number>(),
|
|
220
220
|
// CORRECT
|
|
221
221
|
scores: t.object<Record<string, number>>(),
|
|
222
222
|
|
|
223
|
-
// WRONG
|
|
223
|
+
// WRONG – t.promise() does not exist (facts are synchronous)
|
|
224
224
|
result: t.promise<string>(),
|
|
225
225
|
|
|
226
|
-
// WRONG
|
|
226
|
+
// WRONG – t.any() does not exist
|
|
227
227
|
data: t.any(),
|
|
228
228
|
// CORRECT
|
|
229
229
|
data: t.object<unknown>(),
|
|
230
230
|
|
|
231
|
-
// WRONG
|
|
232
|
-
// WRONG
|
|
231
|
+
// WRONG – t.void() does not exist (not a fact type)
|
|
232
|
+
// WRONG – t.function() does not exist (functions are not serializable)
|
|
233
233
|
```
|
|
234
234
|
|
|
235
235
|
## Type Assertion Alternative
|