@directive-run/knowledge 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +63 -0
  3. package/ai/ai-adapters.md +250 -0
  4. package/ai/ai-agents-streaming.md +269 -0
  5. package/ai/ai-budget-resilience.md +235 -0
  6. package/ai/ai-communication.md +281 -0
  7. package/ai/ai-debug-observability.md +243 -0
  8. package/ai/ai-guardrails-memory.md +332 -0
  9. package/ai/ai-mcp-rag.md +288 -0
  10. package/ai/ai-multi-agent.md +274 -0
  11. package/ai/ai-orchestrator.md +227 -0
  12. package/ai/ai-security.md +293 -0
  13. package/ai/ai-tasks.md +261 -0
  14. package/ai/ai-testing-evals.md +378 -0
  15. package/api-skeleton.md +5 -0
  16. package/core/anti-patterns.md +382 -0
  17. package/core/constraints.md +263 -0
  18. package/core/core-patterns.md +228 -0
  19. package/core/error-boundaries.md +322 -0
  20. package/core/multi-module.md +315 -0
  21. package/core/naming.md +283 -0
  22. package/core/plugins.md +344 -0
  23. package/core/react-adapter.md +262 -0
  24. package/core/resolvers.md +357 -0
  25. package/core/schema-types.md +262 -0
  26. package/core/system-api.md +271 -0
  27. package/core/testing.md +257 -0
  28. package/core/time-travel.md +238 -0
  29. package/dist/index.cjs +111 -0
  30. package/dist/index.cjs.map +1 -0
  31. package/dist/index.d.cts +10 -0
  32. package/dist/index.d.ts +10 -0
  33. package/dist/index.js +102 -0
  34. package/dist/index.js.map +1 -0
  35. package/examples/ab-testing.ts +385 -0
  36. package/examples/ai-checkpoint.ts +509 -0
  37. package/examples/ai-guardrails.ts +319 -0
  38. package/examples/ai-orchestrator.ts +589 -0
  39. package/examples/async-chains.ts +287 -0
  40. package/examples/auth-flow.ts +371 -0
  41. package/examples/batch-resolver.ts +341 -0
  42. package/examples/checkers.ts +589 -0
  43. package/examples/contact-form.ts +176 -0
  44. package/examples/counter.ts +393 -0
  45. package/examples/dashboard-loader.ts +512 -0
  46. package/examples/debounce-constraints.ts +105 -0
  47. package/examples/dynamic-modules.ts +293 -0
  48. package/examples/error-boundaries.ts +430 -0
  49. package/examples/feature-flags.ts +220 -0
  50. package/examples/form-wizard.ts +347 -0
  51. package/examples/fraud-analysis.ts +663 -0
  52. package/examples/goal-heist.ts +341 -0
  53. package/examples/multi-module.ts +57 -0
  54. package/examples/newsletter.ts +241 -0
  55. package/examples/notifications.ts +210 -0
  56. package/examples/optimistic-updates.ts +317 -0
  57. package/examples/pagination.ts +260 -0
  58. package/examples/permissions.ts +337 -0
  59. package/examples/provider-routing.ts +403 -0
  60. package/examples/server.ts +316 -0
  61. package/examples/shopping-cart.ts +422 -0
  62. package/examples/sudoku.ts +630 -0
  63. package/examples/theme-locale.ts +204 -0
  64. package/examples/time-machine.ts +225 -0
  65. package/examples/topic-guard.ts +306 -0
  66. package/examples/url-sync.ts +333 -0
  67. package/examples/websocket.ts +404 -0
  68. package/package.json +65 -0
@@ -0,0 +1,228 @@
1
+ # Core Patterns
2
+
3
+ How to think about building with Directive: modules, systems, and the constraint-resolver pattern.
4
+
5
+ ## Decision Tree: "Where does this logic go?"
6
+
7
+ ```
8
+ User wants to...
9
+ ├── Store state → schema.facts + init()
10
+ ├── Compute derived values → schema.derivations + derive
11
+ ├── React to state changes → effects
12
+ ├── Trigger side effects when conditions are met → constraints + resolvers
13
+ ├── Handle user actions → schema.events + events handlers
14
+ └── Coordinate multiple modules → createSystem({ modules: {} })
15
+ ```
16
+
17
+ ## Module Shape (Canonical Object Syntax)
18
+
19
+ ```typescript
20
+ // CORRECT — full module definition
21
+ import { createModule, t } from "@directive-run/core";
22
+
23
+ const myModule = createModule("name", {
24
+ schema: {
25
+ facts: {
26
+ count: t.number(),
27
+ phase: t.string<"idle" | "loading" | "done">(),
28
+ user: t.object<{ id: string; name: string } | null>(),
29
+ },
30
+ derivations: {
31
+ isLoading: t.boolean(),
32
+ displayName: t.string(),
33
+ },
34
+ events: {
35
+ increment: {},
36
+ setUser: { user: t.object<{ id: string; name: string }>() },
37
+ },
38
+ requirements: {
39
+ FETCH_USER: { userId: t.string() },
40
+ },
41
+ },
42
+
43
+ init: (facts) => {
44
+ facts.count = 0;
45
+ facts.phase = "idle";
46
+ facts.user = null;
47
+ },
48
+
49
+ derive: {
50
+ isLoading: (facts) => facts.phase === "loading",
51
+ displayName: (facts) => {
52
+ if (!facts.user) {
53
+ return "Guest";
54
+ }
55
+
56
+ return facts.user.name;
57
+ },
58
+ },
59
+
60
+ effects: {
61
+ logPhase: {
62
+ run: (facts, prev) => {
63
+ if (prev?.phase !== facts.phase) {
64
+ console.log(`Phase: ${facts.phase}`);
65
+ }
66
+ },
67
+ },
68
+ },
69
+
70
+ constraints: {
71
+ fetchWhenReady: {
72
+ when: (facts) => facts.phase === "idle" && facts.count > 0,
73
+ require: (facts) => ({ type: "FETCH_USER", userId: "user-1" }),
74
+ },
75
+ },
76
+
77
+ resolvers: {
78
+ fetchUser: {
79
+ requirement: "FETCH_USER",
80
+ resolve: async (req, context) => {
81
+ context.facts.phase = "loading";
82
+ const res = await fetch(`/api/users/${req.userId}`);
83
+ const data = await res.json();
84
+ context.facts.user = data;
85
+ context.facts.phase = "done";
86
+ },
87
+ },
88
+ },
89
+
90
+ events: {
91
+ increment: (facts) => {
92
+ facts.count += 1;
93
+ },
94
+ setUser: (facts, payload) => {
95
+ facts.user = payload.user;
96
+ },
97
+ },
98
+ });
99
+ ```
100
+
101
+ ## System Creation
102
+
103
+ ```typescript
104
+ import { createSystem } from "@directive-run/core";
105
+ import { loggingPlugin, devtoolsPlugin } from "@directive-run/core/plugins";
106
+
107
+ // Single module — direct access: system.facts.count
108
+ const system = createSystem({
109
+ module: myModule,
110
+ plugins: [loggingPlugin(), devtoolsPlugin()],
111
+ debug: { timeTravel: true, maxSnapshots: 100 },
112
+ });
113
+
114
+ // Multi-module — namespaced access: system.facts.auth.token
115
+ const system = createSystem({
116
+ modules: { auth: authModule, cart: cartModule },
117
+ plugins: [devtoolsPlugin()],
118
+ });
119
+
120
+ // Lifecycle
121
+ system.start();
122
+ await system.settle(); // Wait for all resolvers to complete
123
+ // ... use the system ...
124
+ system.stop();
125
+ system.destroy();
126
+ ```
127
+
128
+ ## Decision Tree: "User says 'fetch data when authenticated'"
129
+
130
+ WRONG thinking: "I'll put the fetch call in a resolver that checks auth."
131
+
132
+ ```typescript
133
+ // WRONG — resolver doing condition checking + data fetching
134
+ resolvers: {
135
+ fetchData: {
136
+ requirement: "FETCH_DATA",
137
+ resolve: async (req, context) => {
138
+ if (!context.facts.isAuthenticated) {
139
+ return; // Resolver should not check conditions
140
+ }
141
+ const data = await fetch("/api/data");
142
+ context.facts.data = await data.json();
143
+ },
144
+ },
145
+ },
146
+ ```
147
+
148
+ CORRECT thinking: "Constraint declares WHEN, resolver declares HOW."
149
+
150
+ ```typescript
151
+ // CORRECT — constraint declares the need, resolver fulfills it
152
+ constraints: {
153
+ fetchWhenAuthenticated: {
154
+ when: (facts) => facts.isAuthenticated && !facts.data,
155
+ require: { type: "FETCH_DATA" },
156
+ },
157
+ },
158
+
159
+ resolvers: {
160
+ fetchData: {
161
+ requirement: "FETCH_DATA",
162
+ resolve: async (req, context) => {
163
+ const data = await fetch("/api/data");
164
+ context.facts.data = await data.json();
165
+ },
166
+ },
167
+ },
168
+ ```
169
+
170
+ ## Reading System State
171
+
172
+ ```typescript
173
+ // Facts — mutable state
174
+ system.facts.count = 5;
175
+ const val = system.facts.count;
176
+
177
+ // Derivations — read-only computed values
178
+ const loading = system.derive.isLoading;
179
+
180
+ // Events — dispatch user actions
181
+ system.events.increment();
182
+ system.events.setUser({ user: { id: "1", name: "Alice" } });
183
+
184
+ // Subscribe to changes
185
+ const unsub = system.subscribe(["count", "isLoading"], () => {
186
+ console.log(system.facts.count, system.derive.isLoading);
187
+ });
188
+
189
+ // Watch individual values
190
+ system.watch("count", (newVal, oldVal) => {
191
+ console.log(`Count: ${oldVal} -> ${newVal}`);
192
+ });
193
+
194
+ // Wait for a condition
195
+ await system.when((facts) => facts.phase === "done");
196
+ await system.when((facts) => facts.phase === "done", { timeout: 5000 });
197
+ ```
198
+
199
+ ## Schema Patterns
200
+
201
+ Only `facts` is required in the schema. Other sections are optional:
202
+
203
+ ```typescript
204
+ // Minimal module — facts only
205
+ const minimal = createModule("minimal", {
206
+ schema: {
207
+ facts: { count: t.number() },
208
+ },
209
+ init: (facts) => {
210
+ facts.count = 0;
211
+ },
212
+ });
213
+
214
+ // Type assertion pattern (alternative to t.* builders)
215
+ const typed = createModule("typed", {
216
+ schema: {
217
+ facts: {} as { count: number; name: string },
218
+ derivations: {} as { doubled: number },
219
+ },
220
+ init: (facts) => {
221
+ facts.count = 0;
222
+ facts.name = "";
223
+ },
224
+ derive: {
225
+ doubled: (facts) => facts.count * 2,
226
+ },
227
+ });
228
+ ```
@@ -0,0 +1,322 @@
1
+ # Error Boundaries
2
+
3
+ How to handle errors in Directive: recovery strategies, error boundaries, lifecycle hooks, and the circuit breaker pattern.
4
+
5
+ ## Decision Tree: "How should errors be handled?"
6
+
7
+ ```
8
+ Where did the error occur?
9
+ ├── Resolver (API call, async work)
10
+ │ ├── Transient failure (network, timeout) → retry / retry-later
11
+ │ ├── Permanent failure (404, auth) → skip or throw
12
+ │ └── Unknown → retry-later with maxRetries
13
+
14
+ ├── Constraint (evaluation error)
15
+ │ ├── Bug in when() logic → throw (fix the code)
16
+ │ └── Unexpected data shape → skip (disable constraint)
17
+
18
+ ├── Effect (side effect failed)
19
+ │ ├── Non-critical (logging, analytics) → skip
20
+ │ └── Critical (sync to external system) → retry-later
21
+
22
+ ├── Derivation (computation error)
23
+ │ └── Usually a bug → throw (fix the code)
24
+
25
+ └── External service (repeated failures)
26
+ └── Circuit breaker pattern
27
+ ```
28
+
29
+ ## Recovery Strategies
30
+
31
+ Directive supports five recovery strategies:
32
+
33
+ | Strategy | Behavior |
34
+ |---|---|
35
+ | `"skip"` | Swallow the error, continue processing |
36
+ | `"retry"` | Retry immediately (respects resolver retry policy) |
37
+ | `"retry-later"` | Retry after a delay with exponential backoff |
38
+ | `"disable"` | Disable the failing constraint/effect permanently |
39
+ | `"throw"` | Re-throw the error, halting the system |
40
+
41
+ ## System-Level Error Boundary
42
+
43
+ Configure error handling for the entire system:
44
+
45
+ ```typescript
46
+ const system = createSystem({
47
+ module: myModule,
48
+
49
+ errorBoundary: {
50
+ // Per-subsystem strategies (string or function)
51
+ onConstraintError: "skip",
52
+ onResolverError: "retry-later",
53
+ onEffectError: "skip",
54
+ onDerivationError: "throw",
55
+
56
+ // Global error callback — fires for all errors
57
+ onError: (error) => {
58
+ // error is a DirectiveError with source tracking
59
+ console.error(`[${error.source}] ${error.sourceId}: ${error.message}`);
60
+ console.error("Recoverable:", error.recoverable);
61
+ console.error("Context:", error.context);
62
+ },
63
+
64
+ // Configuration for retry-later strategy
65
+ retryLater: {
66
+ delayMs: 1000, // Initial delay (default: 1000)
67
+ maxRetries: 3, // Max retry attempts (default: 3)
68
+ backoffMultiplier: 2, // Multiply delay each retry (default: 2)
69
+ maxDelayMs: 30000, // Cap on delay growth (default: 30000)
70
+ },
71
+ },
72
+ });
73
+ ```
74
+
75
+ ## Dynamic Error Handling with Functions
76
+
77
+ Use functions instead of strings for conditional recovery:
78
+
79
+ ```typescript
80
+ errorBoundary: {
81
+ onResolverError: (error, resolverId) => {
82
+ // Network errors — retry later
83
+ if (error.message.includes("NetworkError")) {
84
+ return "retry-later";
85
+ }
86
+
87
+ // Auth errors — skip, don't retry
88
+ if (error.message.includes("401")) {
89
+ return "skip";
90
+ }
91
+
92
+ // Everything else — throw
93
+ return "throw";
94
+ },
95
+
96
+ onConstraintError: (error, constraintId) => {
97
+ // Disable constraints that repeatedly fail
98
+ if (constraintId === "experimentalFeature") {
99
+ return "disable";
100
+ }
101
+
102
+ return "skip";
103
+ },
104
+
105
+ onEffectError: (error, effectId) => {
106
+ // Analytics can fail silently
107
+ if (effectId.startsWith("analytics")) {
108
+ return "skip";
109
+ }
110
+
111
+ return "throw";
112
+ },
113
+ },
114
+ ```
115
+
116
+ ## DirectiveError
117
+
118
+ All errors passed to error boundary callbacks are `DirectiveError` instances with source tracking:
119
+
120
+ ```typescript
121
+ import { DirectiveError } from "@directive-run/core";
122
+
123
+ try {
124
+ await system.settle();
125
+ } catch (err) {
126
+ if (err instanceof DirectiveError) {
127
+ err.source; // "constraint" | "resolver" | "effect" | "derivation" | "system"
128
+ err.sourceId; // e.g., "fetchUser" — the specific item that failed
129
+ err.recoverable; // boolean — whether recovery strategies apply
130
+ err.context; // arbitrary debug data (e.g., the requirement object)
131
+ err.message; // human-readable description
132
+ }
133
+ }
134
+ ```
135
+
136
+ ## Resolver-Level Error Handling
137
+
138
+ Resolvers have their own retry policy independent of the error boundary:
139
+
140
+ ```typescript
141
+ resolvers: {
142
+ fetchData: {
143
+ requirement: "FETCH_DATA",
144
+
145
+ // Resolver-level retry policy
146
+ retry: {
147
+ attempts: 3,
148
+ backoff: "exponential",
149
+ initialDelay: 200,
150
+ maxDelay: 5000,
151
+ shouldRetry: (error, attempt) => {
152
+ // Only retry server errors, not client errors
153
+ if (error.message.includes("4")) {
154
+ return false;
155
+ }
156
+
157
+ return true;
158
+ },
159
+ },
160
+
161
+ resolve: async (req, context) => {
162
+ const res = await fetch("/api/data");
163
+ if (!res.ok) {
164
+ throw new Error(`HTTP ${res.status}`);
165
+ }
166
+ context.facts.data = await res.json();
167
+ },
168
+ },
169
+ },
170
+ ```
171
+
172
+ When both resolver retry and error boundary are configured, the resolver retries first. If all retries fail, the error boundary strategy is applied.
173
+
174
+ ## Lifecycle Hooks
175
+
176
+ Module-level hooks for lifecycle events:
177
+
178
+ ```typescript
179
+ const myModule = createModule("app", {
180
+ schema: { facts: { status: t.string() } },
181
+
182
+ hooks: {
183
+ onInit: (system) => {
184
+ console.log("Module initialized");
185
+ },
186
+ onStart: (system) => {
187
+ console.log("System started");
188
+ },
189
+ onStop: (system) => {
190
+ console.log("System stopped");
191
+ },
192
+ onError: (error, hookContext) => {
193
+ console.error("Module error:", error.message);
194
+ // error is a DirectiveError
195
+ // hookContext provides additional details
196
+ },
197
+ },
198
+
199
+ init: (facts) => {
200
+ facts.status = "ready";
201
+ },
202
+ });
203
+ ```
204
+
205
+ ## Circuit Breaker
206
+
207
+ For protecting against cascading failures from external services. The circuit breaker tracks failure rates and short-circuits requests when a threshold is exceeded.
208
+
209
+ ```typescript
210
+ import { createCircuitBreaker } from "@directive-run/core/plugins";
211
+
212
+ const apiBreaker = createCircuitBreaker({
213
+ name: "external-api",
214
+ failureThreshold: 5, // Open after 5 failures (default: 5)
215
+ recoveryTimeMs: 30000, // Wait 30s before trying again (default: 30000)
216
+ halfOpenMaxRequests: 3, // Allow 3 trial requests in half-open (default: 3)
217
+ failureWindowMs: 60000, // Count failures within 60s window (default: 60000)
218
+
219
+ // Optional: classify which errors count as failures
220
+ isFailure: (error) => {
221
+ // Don't count 404s as circuit-breaking failures
222
+ if (error.message.includes("404")) {
223
+ return false;
224
+ }
225
+
226
+ return true;
227
+ },
228
+
229
+ // Optional: react to state changes
230
+ onStateChange: (from, to) => {
231
+ console.log(`Circuit: ${from} -> ${to}`);
232
+ },
233
+ });
234
+ ```
235
+
236
+ ### Using Circuit Breaker in Resolvers
237
+
238
+ ```typescript
239
+ resolvers: {
240
+ fetchData: {
241
+ requirement: "FETCH_DATA",
242
+ resolve: async (req, context) => {
243
+ const data = await apiBreaker.execute(async () => {
244
+ const res = await fetch("/api/data");
245
+
246
+ return res.json();
247
+ });
248
+
249
+ context.facts.data = data;
250
+ },
251
+ },
252
+ },
253
+ ```
254
+
255
+ ### Circuit Breaker + Constraints
256
+
257
+ Wire the circuit breaker state into constraints for automatic fallback:
258
+
259
+ ```typescript
260
+ constraints: {
261
+ apiDown: {
262
+ when: () => apiBreaker.getState() === "OPEN",
263
+ require: { type: "USE_FALLBACK" },
264
+ },
265
+
266
+ fetchNormally: {
267
+ when: (facts) => apiBreaker.getState() !== "OPEN" && !facts.data,
268
+ require: { type: "FETCH_DATA" },
269
+ },
270
+ },
271
+ ```
272
+
273
+ ### Circuit Breaker States
274
+
275
+ ```
276
+ CLOSED → Normal operation, all requests pass through
277
+ ↓ (failures >= threshold)
278
+ OPEN → All requests rejected immediately
279
+ ↓ (after recoveryTimeMs)
280
+ HALF_OPEN → Limited trial requests allowed
281
+ ↓ (trial succeeds) → CLOSED
282
+ ↓ (trial fails) → OPEN
283
+ ```
284
+
285
+ ### Circuit Breaker API
286
+
287
+ ```typescript
288
+ apiBreaker.getState(); // "CLOSED" | "OPEN" | "HALF_OPEN"
289
+ apiBreaker.isAllowed(); // boolean — would a request be allowed?
290
+ apiBreaker.getStats(); // { totalRequests, totalFailures, recentFailures, ... }
291
+ apiBreaker.forceState("CLOSED"); // Force state (useful in tests)
292
+ apiBreaker.reset(); // Reset to CLOSED with cleared stats
293
+ ```
294
+
295
+ ### CircuitBreakerOpenError
296
+
297
+ When the circuit is open, `execute()` throws a `CircuitBreakerOpenError`:
298
+
299
+ ```typescript
300
+ import { CircuitBreakerOpenError } from "@directive-run/core/plugins";
301
+
302
+ try {
303
+ await apiBreaker.execute(() => fetch("/api"));
304
+ } catch (error) {
305
+ if (error instanceof CircuitBreakerOpenError) {
306
+ error.code; // "CIRCUIT_OPEN"
307
+ error.retryAfterMs; // ms until circuit transitions to HALF_OPEN
308
+ error.state; // "OPEN" | "HALF_OPEN"
309
+ }
310
+ }
311
+ ```
312
+
313
+ ## Error Handling Checklist
314
+
315
+ 1. Set system-level `errorBoundary` with strategies for each subsystem
316
+ 2. Add `retry` policy on resolvers that call external services
317
+ 3. Use `shouldRetry` to avoid retrying permanent failures (4xx errors)
318
+ 4. Use circuit breaker for services with known reliability issues
319
+ 5. Wire circuit breaker state into constraints for automatic fallback
320
+ 6. Add `onError` callback for logging/monitoring
321
+ 7. Use `"throw"` for derivation errors (they indicate bugs)
322
+ 8. Use `"skip"` for non-critical effects (logging, analytics)