@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,271 @@
1
+ # System API
2
+
3
+ The system is created with `createSystem()` and is the runtime that orchestrates modules, constraints, resolvers, and plugins.
4
+
5
+ ## Decision Tree: "How do I interact with the system?"
6
+
7
+ ```
8
+ What do you want to do?
9
+ ├── Read/write state → system.facts.fieldName
10
+ ├── Read computed values → system.derive.derivationName
11
+ ├── Dispatch user actions → system.events.eventName(payload)
12
+ ├── React to changes → system.subscribe() or system.watch()
13
+ ├── Wait for a condition → system.when()
14
+ ├── Wait for all async to finish → system.settle()
15
+ ├── Debug/inspect current state → system.inspect()
16
+ ├── Control lifecycle → system.start() / system.stop() / system.destroy()
17
+ └── Multi-module access → system.facts.moduleName.fieldName
18
+ ```
19
+
20
+ ## Creating a System
21
+
22
+ ```typescript
23
+ import { createSystem } from "@directive-run/core";
24
+ import { loggingPlugin, devtoolsPlugin } from "@directive-run/core/plugins";
25
+
26
+ // Single module — direct access to facts/derive/events
27
+ const system = createSystem({
28
+ module: myModule,
29
+ plugins: [loggingPlugin(), devtoolsPlugin()],
30
+ debug: { timeTravel: true, maxSnapshots: 100 },
31
+ });
32
+
33
+ // Multi-module — namespaced access
34
+ const system = createSystem({
35
+ modules: {
36
+ auth: authModule,
37
+ cart: cartModule,
38
+ ui: uiModule,
39
+ },
40
+ plugins: [devtoolsPlugin()],
41
+ });
42
+ ```
43
+
44
+ ## Facts: Reading and Writing State
45
+
46
+ ```typescript
47
+ // Single module
48
+ system.facts.count = 5;
49
+ const val = system.facts.count;
50
+
51
+ // Multi-module — access through module namespace
52
+ system.facts.auth.token = "abc123";
53
+ system.facts.cart.items = [];
54
+ const token = system.facts.auth.token;
55
+ ```
56
+
57
+ Facts are proxy-based. Mutations are tracked automatically and trigger derivation recomputation, constraint evaluation, and effect execution.
58
+
59
+ ## Derivations: Reading Computed Values
60
+
61
+ ```typescript
62
+ // Single module
63
+ const loading = system.derive.isLoading;
64
+ const display = system.derive.displayName;
65
+
66
+ // Multi-module
67
+ const isAdmin = system.derive.auth.isAdmin;
68
+ const total = system.derive.cart.totalPrice;
69
+ ```
70
+
71
+ Derivations are read-only. They recompute lazily when their tracked facts change.
72
+
73
+ ## Events: Dispatching Actions
74
+
75
+ ```typescript
76
+ // Single module
77
+ system.events.increment();
78
+ system.events.setUser({ user: { id: "1", name: "Alice" } });
79
+
80
+ // Multi-module
81
+ system.events.auth.login({ email: "alice@example.com" });
82
+ system.events.cart.addItem({ productId: "p1", quantity: 2 });
83
+ ```
84
+
85
+ Events are synchronous. They mutate facts in the event handler, which triggers the reactive pipeline.
86
+
87
+ ## Subscribing to Changes
88
+
89
+ ```typescript
90
+ // Subscribe to specific keys (facts or derivations)
91
+ const unsub = system.subscribe(["count", "isLoading"], () => {
92
+ console.log(system.facts.count, system.derive.isLoading);
93
+ });
94
+
95
+ // Watch a single value with old/new
96
+ system.watch("count", (newVal, oldVal) => {
97
+ console.log(`Count: ${oldVal} -> ${newVal}`);
98
+ });
99
+
100
+ // Unsubscribe
101
+ unsub();
102
+ ```
103
+
104
+ ## Waiting for Conditions
105
+
106
+ ```typescript
107
+ // Wait until a condition is true
108
+ await system.when((facts) => facts.phase === "done");
109
+
110
+ // With timeout — throws if condition not met in time
111
+ await system.when((facts) => facts.phase === "done", { timeout: 5000 });
112
+ ```
113
+
114
+ ## Settling: Waiting for Async Completion
115
+
116
+ ```typescript
117
+ system.start();
118
+
119
+ // Wait for all resolvers and async constraints to complete
120
+ await system.settle();
121
+
122
+ // With timeout
123
+ await system.settle(5000); // Throws if not settled in 5s
124
+
125
+ // Check settlement state synchronously
126
+ if (system.isSettled) {
127
+ console.log("All resolvers complete");
128
+ }
129
+
130
+ // Subscribe to settlement changes
131
+ const unsub = system.onSettledChange(() => {
132
+ console.log("Settlement:", system.isSettled);
133
+ });
134
+ ```
135
+
136
+ ## Reading by Key
137
+
138
+ ```typescript
139
+ // Read fact or derivation by string key
140
+ const count = system.read("count");
141
+ const isLoading = system.read("isLoading");
142
+
143
+ // Multi-module — use dot notation
144
+ const token = system.read("auth.token");
145
+ const total = system.read("cart.totalPrice");
146
+ ```
147
+
148
+ ## Inspecting System State
149
+
150
+ ```typescript
151
+ const inspection = system.inspect();
152
+
153
+ // Full fact snapshot
154
+ inspection.facts;
155
+ // { count: 5, phase: "done", user: { id: "1", name: "Alice" } }
156
+
157
+ // Derivation values
158
+ inspection.derivations;
159
+ // { isLoading: false, displayName: "Alice" }
160
+
161
+ // Active requirements
162
+ inspection.requirements;
163
+ // [{ id: "req-1", type: "FETCH_USER", userId: "1" }]
164
+
165
+ // Constraint definitions and state
166
+ inspection.constraintDefs;
167
+ // [{ id: "fetchWhenAuth", priority: 0, disabled: false }]
168
+
169
+ // Resolver statuses
170
+ inspection.resolvers;
171
+ // { fetchUser: { state: "success", duration: 150 } }
172
+
173
+ // Currently inflight resolvers
174
+ inspection.inflight;
175
+ // [{ id: "req-2", resolverId: "fetchData", startedAt: 1709000000 }]
176
+
177
+ // Unmet requirements (no matching resolver)
178
+ inspection.unmet;
179
+
180
+ // Explain why a requirement exists
181
+ const explanation = system.explain("req-123");
182
+ ```
183
+
184
+ ## Lifecycle
185
+
186
+ ```typescript
187
+ // Start — begins constraint evaluation and reconciliation
188
+ system.start();
189
+
190
+ // Stop — pauses evaluation, cancels inflight resolvers
191
+ system.stop();
192
+
193
+ // Destroy — cleans up all resources, subscriptions, plugins
194
+ system.destroy();
195
+
196
+ // Lifecycle order:
197
+ // createSystem() → system.start() → ... → system.stop() → system.destroy()
198
+ ```
199
+
200
+ `system.start()` is auto-called in most cases. Call it explicitly when you need to set up subscriptions before the first evaluation cycle.
201
+
202
+ ## Constraint Control at Runtime
203
+
204
+ ```typescript
205
+ // Disable a constraint — it won't be evaluated
206
+ system.constraints.disable("fetchWhenReady");
207
+
208
+ // Check if disabled
209
+ system.constraints.isDisabled("fetchWhenReady"); // true
210
+
211
+ // Re-enable — triggers re-evaluation on next cycle
212
+ system.constraints.enable("fetchWhenReady");
213
+ ```
214
+
215
+ ## Common Mistakes
216
+
217
+ ### Reading facts before settling
218
+
219
+ ```typescript
220
+ // WRONG — resolver hasn't completed, facts are stale
221
+ system.start();
222
+ console.log(system.facts.user); // null
223
+
224
+ // CORRECT — wait for async resolution
225
+ system.start();
226
+ await system.settle();
227
+ console.log(system.facts.user); // { id: "1", name: "Alice" }
228
+ ```
229
+
230
+ ### Casting facts/derivations unnecessarily
231
+
232
+ ```typescript
233
+ // WRONG — the schema already provides types
234
+ const profile = system.facts.profile as ResourceState<Profile>;
235
+
236
+ // CORRECT — types are inferred from the schema
237
+ const profile = system.facts.profile;
238
+ ```
239
+
240
+ ### Using single-line returns without braces
241
+
242
+ ```typescript
243
+ // WRONG
244
+ system.watch("phase", (val) => { if (val === "done") return; });
245
+
246
+ // CORRECT
247
+ system.watch("phase", (val) => {
248
+ if (val === "done") {
249
+ return;
250
+ }
251
+ });
252
+ ```
253
+
254
+ ### Forgetting to destroy
255
+
256
+ ```typescript
257
+ // WRONG — resources leak
258
+ function setupSystem() {
259
+ const system = createSystem({ module: myModule });
260
+ system.start();
261
+
262
+ return system;
263
+ }
264
+
265
+ // CORRECT — clean up when done
266
+ const system = createSystem({ module: myModule });
267
+ system.start();
268
+ // ... when finished:
269
+ system.stop();
270
+ system.destroy();
271
+ ```
@@ -0,0 +1,257 @@
1
+ # Testing
2
+
3
+ Testing utilities for Directive modules and systems. Import from `@directive-run/core/testing`.
4
+
5
+ ## Decision Tree: "How should I test this?"
6
+
7
+ ```
8
+ What are you testing?
9
+ ├── A single module in isolation → createTestSystem(module)
10
+ ├── Multiple modules together → createTestSystemFromModules({ a, b })
11
+ ├── A constraint fires correctly → Set facts, check requirements
12
+ ├── A resolver mutates facts → Mock the resolver, dispatch, assert facts
13
+ ├── A derivation computes correctly → Set facts, read derived value
14
+ ├── Async settling behavior → settleWithFakeTimers(system, vi)
15
+ └── An effect runs → Set facts, assert side effects
16
+ ```
17
+
18
+ ## Creating Test Systems
19
+
20
+ ```typescript
21
+ import {
22
+ createTestSystem,
23
+ createTestSystemFromModules,
24
+ mockResolver,
25
+ flushMicrotasks,
26
+ settleWithFakeTimers,
27
+ assertFact,
28
+ assertDerivation,
29
+ assertRequirement,
30
+ } from "@directive-run/core/testing";
31
+
32
+ // Single module — same API as createSystem, with testing defaults
33
+ // (time-travel off, no plugins, synchronous settling)
34
+ const system = createTestSystem(myModule);
35
+
36
+ // With options
37
+ const system = createTestSystem(myModule, {
38
+ initialFacts: { count: 5, phase: "loading" },
39
+ mockResolvers: [mockResolver("FETCH_USER", async (req, context) => {
40
+ context.facts.user = { id: req.userId, name: "Test User" };
41
+ })],
42
+ });
43
+
44
+ // Multi-module
45
+ const system = createTestSystemFromModules(
46
+ { auth: authModule, cart: cartModule },
47
+ { mockResolvers: [mockResolver("AUTHENTICATE", async (req, context) => {
48
+ context.facts.auth.token = "test-token";
49
+ })] },
50
+ );
51
+ ```
52
+
53
+ ## Testing Constraints
54
+
55
+ Set facts to trigger the constraint's `when()`, then assert the requirement was emitted.
56
+
57
+ ```typescript
58
+ import { describe, it, expect } from "vitest";
59
+ import { createTestSystem, assertRequirement } from "@directive-run/core/testing";
60
+
61
+ describe("fetchWhenAuth constraint", () => {
62
+ it("emits FETCH_USER when authenticated without profile", () => {
63
+ const system = createTestSystem(userModule);
64
+
65
+ // Set facts to satisfy the constraint's when()
66
+ system.facts.isAuthenticated = true;
67
+ system.facts.profile = null;
68
+
69
+ // Assert the requirement was emitted
70
+ assertRequirement(system, "FETCH_USER");
71
+ });
72
+
73
+ it("does NOT emit when already has profile", () => {
74
+ const system = createTestSystem(userModule, {
75
+ initialFacts: {
76
+ isAuthenticated: true,
77
+ profile: { id: "1", name: "Alice" },
78
+ },
79
+ });
80
+
81
+ // No requirement should exist
82
+ const inspection = system.inspect();
83
+ const fetchReqs = inspection.requirements.filter(
84
+ (r) => r.type === "FETCH_USER",
85
+ );
86
+ expect(fetchReqs).toHaveLength(0);
87
+ });
88
+ });
89
+ ```
90
+
91
+ ## Testing Resolvers
92
+
93
+ Mock the resolver, trigger a requirement, and assert fact mutations.
94
+
95
+ ```typescript
96
+ describe("fetchUser resolver", () => {
97
+ it("stores fetched user in facts", async () => {
98
+ const system = createTestSystem(userModule, {
99
+ mockResolvers: [
100
+ mockResolver("FETCH_USER", async (req, context) => {
101
+ // Simulate API response
102
+ context.facts.user = { id: req.userId, name: "Mocked User" };
103
+ context.facts.phase = "loaded";
104
+ }),
105
+ ],
106
+ });
107
+
108
+ // Trigger the constraint that emits FETCH_USER
109
+ system.facts.isAuthenticated = true;
110
+ system.facts.user = null;
111
+
112
+ // Wait for resolver to complete
113
+ await system.settle();
114
+
115
+ assertFact(system, "user", { id: expect.any(String), name: "Mocked User" });
116
+ assertFact(system, "phase", "loaded");
117
+ });
118
+ });
119
+ ```
120
+
121
+ ## Testing Derivations
122
+
123
+ Set facts, then read the derived value.
124
+
125
+ ```typescript
126
+ describe("isOverBudget derivation", () => {
127
+ it("returns true when total exceeds budget", () => {
128
+ const system = createTestSystem(budgetModule, {
129
+ initialFacts: { total: 150, budget: 100 },
130
+ });
131
+
132
+ assertDerivation(system, "isOverBudget", true);
133
+ });
134
+
135
+ it("returns false when under budget", () => {
136
+ const system = createTestSystem(budgetModule, {
137
+ initialFacts: { total: 50, budget: 100 },
138
+ });
139
+
140
+ assertDerivation(system, "isOverBudget", false);
141
+ });
142
+
143
+ it("recomputes when facts change", () => {
144
+ const system = createTestSystem(budgetModule, {
145
+ initialFacts: { total: 50, budget: 100 },
146
+ });
147
+
148
+ assertDerivation(system, "isOverBudget", false);
149
+
150
+ system.facts.total = 200;
151
+
152
+ assertDerivation(system, "isOverBudget", true);
153
+ });
154
+ });
155
+ ```
156
+
157
+ ## Async Testing with Fake Timers
158
+
159
+ Use `settleWithFakeTimers` when resolvers have retry delays or timeouts.
160
+
161
+ ```typescript
162
+ import { describe, it, vi } from "vitest";
163
+ import { createTestSystem, settleWithFakeTimers } from "@directive-run/core/testing";
164
+
165
+ describe("retry behavior", () => {
166
+ it("retries on failure with exponential backoff", async () => {
167
+ vi.useFakeTimers();
168
+ let attempts = 0;
169
+
170
+ const system = createTestSystem(myModule, {
171
+ mockResolvers: [
172
+ mockResolver("FETCH_DATA", async (req, context) => {
173
+ attempts += 1;
174
+ if (attempts < 3) {
175
+ throw new Error("Temporary failure");
176
+ }
177
+ context.facts.data = "success";
178
+ }),
179
+ ],
180
+ });
181
+
182
+ system.facts.needsData = true;
183
+
184
+ // Advances fake timers through retry delays and settles
185
+ await settleWithFakeTimers(system, vi);
186
+
187
+ expect(attempts).toBe(3);
188
+ assertFact(system, "data", "success");
189
+
190
+ vi.useRealTimers();
191
+ });
192
+ });
193
+ ```
194
+
195
+ ## Flushing Microtasks
196
+
197
+ Use `flushMicrotasks()` when you need to process pending promises without fully settling.
198
+
199
+ ```typescript
200
+ it("processes intermediate state", async () => {
201
+ const system = createTestSystem(myModule);
202
+
203
+ system.facts.trigger = true;
204
+
205
+ // Flush pending microtasks without waiting for full settlement
206
+ await flushMicrotasks();
207
+
208
+ // Check intermediate state
209
+ assertFact(system, "phase", "loading");
210
+
211
+ // Now settle fully
212
+ await system.settle();
213
+
214
+ assertFact(system, "phase", "done");
215
+ });
216
+ ```
217
+
218
+ ## Common Mistakes
219
+
220
+ ### Testing real resolvers instead of mocking
221
+
222
+ ```typescript
223
+ // WRONG — tests hit real APIs, slow and flaky
224
+ const system = createTestSystem(myModule);
225
+ system.facts.needsFetch = true;
226
+ await system.settle(); // Makes real HTTP call
227
+
228
+ // CORRECT — mock the resolver
229
+ const system = createTestSystem(myModule, {
230
+ mockResolvers: [mockResolver("FETCH", async (req, context) => {
231
+ context.facts.data = { mocked: true };
232
+ })],
233
+ });
234
+ ```
235
+
236
+ ### Forgetting to settle before asserting async results
237
+
238
+ ```typescript
239
+ // WRONG — resolver hasn't completed yet
240
+ system.facts.trigger = true;
241
+ assertFact(system, "result", "done"); // Fails!
242
+
243
+ // CORRECT — wait for resolution
244
+ system.facts.trigger = true;
245
+ await system.settle();
246
+ assertFact(system, "result", "done");
247
+ ```
248
+
249
+ ### Using ctx instead of context in mock resolvers
250
+
251
+ ```typescript
252
+ // WRONG
253
+ mockResolver("FETCH", async (req, ctx) => { /* ... */ }),
254
+
255
+ // CORRECT
256
+ mockResolver("FETCH", async (req, context) => { /* ... */ }),
257
+ ```