@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,287 @@
1
+ // Example: async-chains
2
+ // Source: examples/async-chains/src/async-chains.ts
3
+ // Pure module file — no DOM wiring
4
+
5
+ /**
6
+ * Async Chains — Three Directive Modules
7
+ *
8
+ * Demonstrates cross-module constraint chaining with `after` ordering:
9
+ * Auth → Permissions → Dashboard
10
+ *
11
+ * Each step only fires after the previous step's resolver completes.
12
+ * Cross-module derivations drive the `when()` conditions.
13
+ */
14
+
15
+ import { type ModuleSchema, createModule, t } from "@directive-run/core";
16
+ import {
17
+ type DashboardWidget,
18
+ fetchDashboard,
19
+ fetchPermissions,
20
+ validateSession,
21
+ } from "./mock-api.js";
22
+
23
+ // ============================================================================
24
+ // Auth Module
25
+ // ============================================================================
26
+
27
+ export const authSchema = {
28
+ facts: {
29
+ token: t.string(),
30
+ status: t.string<"idle" | "validating" | "valid" | "expired">(),
31
+ userId: t.string(),
32
+ failRate: t.number(),
33
+ },
34
+ derivations: {
35
+ isValid: t.boolean(),
36
+ },
37
+ events: {
38
+ setToken: { value: t.string() },
39
+ setFailRate: { value: t.number() },
40
+ reset: {},
41
+ },
42
+ requirements: {
43
+ VALIDATE_SESSION: { token: t.string() },
44
+ },
45
+ } satisfies ModuleSchema;
46
+
47
+ export const authModule = createModule("auth", {
48
+ schema: authSchema,
49
+
50
+ init: (facts) => {
51
+ facts.token = "";
52
+ facts.status = "idle";
53
+ facts.userId = "";
54
+ facts.failRate = 0;
55
+ },
56
+
57
+ derive: {
58
+ isValid: (facts) => facts.status === "valid",
59
+ },
60
+
61
+ events: {
62
+ setToken: (facts, { value }) => {
63
+ facts.token = value;
64
+ facts.status = "idle";
65
+ facts.userId = "";
66
+ },
67
+ setFailRate: (facts, { value }) => {
68
+ facts.failRate = value;
69
+ },
70
+ reset: (facts) => {
71
+ facts.token = "";
72
+ facts.status = "idle";
73
+ facts.userId = "";
74
+ },
75
+ },
76
+
77
+ constraints: {
78
+ validateSession: {
79
+ when: (facts) => facts.token !== "" && facts.status === "idle",
80
+ require: (facts) => ({
81
+ type: "VALIDATE_SESSION",
82
+ token: facts.token,
83
+ }),
84
+ },
85
+ },
86
+
87
+ resolvers: {
88
+ validateSession: {
89
+ requirement: "VALIDATE_SESSION",
90
+ key: (req) => `validate-${req.token}`,
91
+ retry: {
92
+ attempts: 2,
93
+ backoff: "exponential",
94
+ initialDelay: 300,
95
+ },
96
+ resolve: async (req, context) => {
97
+ context.facts.status = "validating";
98
+
99
+ try {
100
+ const result = await validateSession(
101
+ req.token,
102
+ context.facts.failRate,
103
+ );
104
+ if (result.valid) {
105
+ context.facts.status = "valid";
106
+ context.facts.userId = result.userId;
107
+ } else {
108
+ context.facts.status = "expired";
109
+ }
110
+ } catch {
111
+ context.facts.status = "expired";
112
+ }
113
+ },
114
+ },
115
+ },
116
+ });
117
+
118
+ // ============================================================================
119
+ // Permissions Module
120
+ // ============================================================================
121
+
122
+ export const permissionsSchema = {
123
+ facts: {
124
+ role: t.string(),
125
+ permissions: t.array<string>(),
126
+ loaded: t.boolean(),
127
+ failRate: t.number(),
128
+ },
129
+ derivations: {
130
+ canEdit: t.boolean(),
131
+ canPublish: t.boolean(),
132
+ canManageUsers: t.boolean(),
133
+ },
134
+ events: {
135
+ setFailRate: { value: t.number() },
136
+ reset: {},
137
+ },
138
+ requirements: {
139
+ LOAD_PERMISSIONS: {},
140
+ },
141
+ } satisfies ModuleSchema;
142
+
143
+ export const permissionsModule = createModule("permissions", {
144
+ schema: permissionsSchema,
145
+
146
+ crossModuleDeps: { auth: authSchema },
147
+
148
+ init: (facts) => {
149
+ facts.role = "";
150
+ facts.permissions = [];
151
+ facts.loaded = false;
152
+ facts.failRate = 0;
153
+ },
154
+
155
+ derive: {
156
+ canEdit: (facts) => facts.self.permissions.includes("write"),
157
+ canPublish: (facts) =>
158
+ facts.self.permissions.includes("write") && facts.self.role !== "viewer",
159
+ canManageUsers: (facts) => facts.self.permissions.includes("manage-users"),
160
+ },
161
+
162
+ events: {
163
+ setFailRate: (facts, { value }) => {
164
+ facts.failRate = value;
165
+ },
166
+ reset: (facts) => {
167
+ facts.role = "";
168
+ facts.permissions = [];
169
+ facts.loaded = false;
170
+ },
171
+ },
172
+
173
+ constraints: {
174
+ loadPermissions: {
175
+ after: ["auth::validateSession"],
176
+ when: (facts) => {
177
+ // Use the fact directly — derivation values aren't available in the
178
+ // facts proxy passed to constraints (they live in the derive layer).
179
+ return facts.auth.status === "valid" && !facts.self.loaded;
180
+ },
181
+ require: { type: "LOAD_PERMISSIONS" },
182
+ },
183
+ },
184
+
185
+ resolvers: {
186
+ loadPermissions: {
187
+ requirement: "LOAD_PERMISSIONS",
188
+ retry: {
189
+ attempts: 2,
190
+ backoff: "exponential",
191
+ initialDelay: 200,
192
+ },
193
+ resolve: async (_req, context) => {
194
+ try {
195
+ const result = await fetchPermissions(context.facts.failRate);
196
+ context.facts.role = result.role;
197
+ context.facts.permissions = result.permissions;
198
+ context.facts.loaded = true;
199
+ } catch {
200
+ context.facts.loaded = false;
201
+ }
202
+ },
203
+ },
204
+ },
205
+ });
206
+
207
+ // ============================================================================
208
+ // Dashboard Module
209
+ // ============================================================================
210
+
211
+ export const dashboardSchema = {
212
+ facts: {
213
+ widgets: t.array<DashboardWidget>(),
214
+ loaded: t.boolean(),
215
+ failRate: t.number(),
216
+ },
217
+ derivations: {
218
+ widgetCount: t.number(),
219
+ },
220
+ events: {
221
+ setFailRate: { value: t.number() },
222
+ reset: {},
223
+ },
224
+ requirements: {
225
+ LOAD_DASHBOARD: { role: t.string() },
226
+ },
227
+ } satisfies ModuleSchema;
228
+
229
+ export const dashboardModule = createModule("dashboard", {
230
+ schema: dashboardSchema,
231
+
232
+ crossModuleDeps: { permissions: permissionsSchema },
233
+
234
+ init: (facts) => {
235
+ facts.widgets = [];
236
+ facts.loaded = false;
237
+ facts.failRate = 0;
238
+ },
239
+
240
+ derive: {
241
+ widgetCount: (facts) => facts.self.widgets.length,
242
+ },
243
+
244
+ events: {
245
+ setFailRate: (facts, { value }) => {
246
+ facts.failRate = value;
247
+ },
248
+ reset: (facts) => {
249
+ facts.widgets = [];
250
+ facts.loaded = false;
251
+ },
252
+ },
253
+
254
+ constraints: {
255
+ loadDashboard: {
256
+ after: ["permissions::loadPermissions"],
257
+ when: (facts) => {
258
+ return facts.permissions.role !== "" && !facts.self.loaded;
259
+ },
260
+ require: (facts) => ({
261
+ type: "LOAD_DASHBOARD",
262
+ role: facts.permissions.role,
263
+ }),
264
+ },
265
+ },
266
+
267
+ resolvers: {
268
+ loadDashboard: {
269
+ requirement: "LOAD_DASHBOARD",
270
+ key: (req) => `dashboard-${req.role}`,
271
+ retry: {
272
+ attempts: 2,
273
+ backoff: "exponential",
274
+ initialDelay: 300,
275
+ },
276
+ resolve: async (req, context) => {
277
+ try {
278
+ const result = await fetchDashboard(req.role, context.facts.failRate);
279
+ context.facts.widgets = result.widgets;
280
+ context.facts.loaded = true;
281
+ } catch {
282
+ context.facts.loaded = false;
283
+ }
284
+ },
285
+ },
286
+ },
287
+ });
@@ -0,0 +1,371 @@
1
+ // Example: auth-flow
2
+ // Source: examples/auth-flow/src/auth-flow.ts
3
+ // Pure module file — no DOM wiring
4
+
5
+ /**
6
+ * Auth Flow — Directive Module
7
+ *
8
+ * Demonstrates constraint `after` ordering, auto-tracked derivations
9
+ * driving constraints, resolvers with retry, effects for cleanup,
10
+ * and time-based reactivity (token expiry countdown).
11
+ */
12
+
13
+ import { type ModuleSchema, createModule, t } from "@directive-run/core";
14
+ import {
15
+ type User,
16
+ mockFetchUser,
17
+ mockLogin,
18
+ mockRefresh,
19
+ } from "./mock-auth.js";
20
+
21
+ // ============================================================================
22
+ // Types
23
+ // ============================================================================
24
+
25
+ export type AuthStatus =
26
+ | "idle"
27
+ | "authenticating"
28
+ | "authenticated"
29
+ | "refreshing"
30
+ | "expired";
31
+
32
+ export interface EventLogEntry {
33
+ timestamp: number;
34
+ event: string;
35
+ detail: string;
36
+ }
37
+
38
+ // ============================================================================
39
+ // Schema
40
+ // ============================================================================
41
+
42
+ export const authFlowSchema = {
43
+ facts: {
44
+ email: t.string(),
45
+ password: t.string(),
46
+ token: t.string(),
47
+ refreshToken: t.string(),
48
+ expiresAt: t.number(),
49
+ user: t.object<User | null>(),
50
+ status: t.string<AuthStatus>(),
51
+ loginRequested: t.boolean(),
52
+ now: t.number(),
53
+ tokenTTL: t.number(),
54
+ refreshBuffer: t.number(),
55
+ loginFailRate: t.number(),
56
+ refreshFailRate: t.number(),
57
+ eventLog: t.object<EventLogEntry[]>(),
58
+ },
59
+ derivations: {
60
+ isAuthenticated: t.boolean(),
61
+ isExpiringSoon: t.boolean(),
62
+ canRefresh: t.boolean(),
63
+ tokenTimeRemaining: t.number(),
64
+ canLogin: t.boolean(),
65
+ },
66
+ events: {
67
+ setEmail: { value: t.string() },
68
+ setPassword: { value: t.string() },
69
+ requestLogin: {},
70
+ logout: {},
71
+ forceExpire: {},
72
+ setTokenTTL: { value: t.number() },
73
+ setRefreshBuffer: { value: t.number() },
74
+ setLoginFailRate: { value: t.number() },
75
+ setRefreshFailRate: { value: t.number() },
76
+ tick: {},
77
+ },
78
+ requirements: {
79
+ LOGIN: { email: t.string(), password: t.string() },
80
+ REFRESH_TOKEN: { refreshToken: t.string() },
81
+ FETCH_USER: { token: t.string() },
82
+ },
83
+ } satisfies ModuleSchema;
84
+
85
+ // ============================================================================
86
+ // Helpers
87
+ // ============================================================================
88
+
89
+ function addLogEntry(facts: any, event: string, detail: string): void {
90
+ const log = [...(facts.eventLog as EventLogEntry[])];
91
+ log.push({ timestamp: Date.now(), event, detail });
92
+ facts.eventLog = log;
93
+ }
94
+
95
+ // ============================================================================
96
+ // Module
97
+ // ============================================================================
98
+
99
+ export const authFlowModule = createModule("auth-flow", {
100
+ schema: authFlowSchema,
101
+
102
+ init: (facts) => {
103
+ facts.email = "alice@test.com";
104
+ facts.password = "password";
105
+ facts.token = "";
106
+ facts.refreshToken = "";
107
+ facts.expiresAt = 0;
108
+ facts.user = null;
109
+ facts.status = "idle";
110
+ facts.loginRequested = false;
111
+ facts.now = Date.now();
112
+ facts.tokenTTL = 30;
113
+ facts.refreshBuffer = 5;
114
+ facts.loginFailRate = 0;
115
+ facts.refreshFailRate = 0;
116
+ facts.eventLog = [];
117
+ },
118
+
119
+ // ============================================================================
120
+ // Derivations
121
+ // ============================================================================
122
+
123
+ derive: {
124
+ isAuthenticated: (facts) => facts.status === "authenticated",
125
+
126
+ isExpiringSoon: (facts) => {
127
+ if (facts.token === "") {
128
+ return false;
129
+ }
130
+
131
+ return facts.now > facts.expiresAt - facts.refreshBuffer * 1000;
132
+ },
133
+
134
+ canRefresh: (facts) => {
135
+ return facts.refreshToken !== "" && facts.status !== "refreshing";
136
+ },
137
+
138
+ tokenTimeRemaining: (facts) => {
139
+ if (facts.token === "") {
140
+ return 0;
141
+ }
142
+
143
+ return Math.max(0, Math.round((facts.expiresAt - facts.now) / 1000));
144
+ },
145
+
146
+ canLogin: (facts) => {
147
+ return (
148
+ facts.email.trim() !== "" &&
149
+ facts.password.trim() !== "" &&
150
+ (facts.status === "idle" || facts.status === "expired")
151
+ );
152
+ },
153
+ },
154
+
155
+ // ============================================================================
156
+ // Events
157
+ // ============================================================================
158
+
159
+ events: {
160
+ setEmail: (facts, { value }) => {
161
+ facts.email = value;
162
+ },
163
+
164
+ setPassword: (facts, { value }) => {
165
+ facts.password = value;
166
+ },
167
+
168
+ requestLogin: (facts) => {
169
+ facts.loginRequested = true;
170
+ facts.status = "authenticating";
171
+ facts.token = "";
172
+ facts.refreshToken = "";
173
+ facts.expiresAt = 0;
174
+ facts.user = null;
175
+ facts.eventLog = [];
176
+ },
177
+
178
+ logout: (facts) => {
179
+ facts.token = "";
180
+ facts.refreshToken = "";
181
+ facts.expiresAt = 0;
182
+ facts.user = null;
183
+ facts.status = "idle";
184
+ facts.loginRequested = false;
185
+ },
186
+
187
+ forceExpire: (facts) => {
188
+ facts.expiresAt = 0;
189
+ },
190
+
191
+ setTokenTTL: (facts, { value }) => {
192
+ facts.tokenTTL = value;
193
+ },
194
+
195
+ setRefreshBuffer: (facts, { value }) => {
196
+ facts.refreshBuffer = value;
197
+ },
198
+
199
+ setLoginFailRate: (facts, { value }) => {
200
+ facts.loginFailRate = value;
201
+ },
202
+
203
+ setRefreshFailRate: (facts, { value }) => {
204
+ facts.refreshFailRate = value;
205
+ },
206
+
207
+ tick: (facts) => {
208
+ facts.now = Date.now();
209
+ },
210
+ },
211
+
212
+ // ============================================================================
213
+ // Constraints
214
+ // ============================================================================
215
+
216
+ constraints: {
217
+ needsLogin: {
218
+ priority: 100,
219
+ when: (facts) => {
220
+ return facts.loginRequested && facts.status === "authenticating";
221
+ },
222
+ require: (facts) => ({
223
+ type: "LOGIN",
224
+ email: facts.email,
225
+ password: facts.password,
226
+ }),
227
+ },
228
+
229
+ refreshNeeded: {
230
+ priority: 90,
231
+ when: (facts) => {
232
+ const isExpiringSoon =
233
+ facts.token !== "" &&
234
+ facts.now > facts.expiresAt - facts.refreshBuffer * 1000;
235
+ const canRefresh =
236
+ facts.refreshToken !== "" && facts.status !== "refreshing";
237
+
238
+ return isExpiringSoon && canRefresh && facts.status === "authenticated";
239
+ },
240
+ require: (facts) => ({
241
+ type: "REFRESH_TOKEN",
242
+ refreshToken: facts.refreshToken,
243
+ }),
244
+ },
245
+
246
+ needsUser: {
247
+ priority: 80,
248
+ after: ["refreshNeeded"],
249
+ when: (facts) => {
250
+ return (
251
+ facts.token !== "" &&
252
+ facts.user === null &&
253
+ facts.status !== "authenticating"
254
+ );
255
+ },
256
+ require: (facts) => ({
257
+ type: "FETCH_USER",
258
+ token: facts.token,
259
+ }),
260
+ },
261
+ },
262
+
263
+ // ============================================================================
264
+ // Resolvers
265
+ // ============================================================================
266
+
267
+ resolvers: {
268
+ login: {
269
+ requirement: "LOGIN",
270
+ timeout: 10000,
271
+ resolve: async (req, context) => {
272
+ addLogEntry(context.facts, "login", "Authenticating...");
273
+
274
+ try {
275
+ const tokens = await mockLogin(
276
+ req.email,
277
+ req.password,
278
+ context.facts.loginFailRate,
279
+ context.facts.tokenTTL,
280
+ );
281
+ context.facts.token = tokens.token;
282
+ context.facts.refreshToken = tokens.refreshToken;
283
+ context.facts.expiresAt = Date.now() + tokens.expiresIn * 1000;
284
+ context.facts.status = "authenticated";
285
+ context.facts.user = null; // trigger needsUser constraint
286
+ addLogEntry(
287
+ context.facts,
288
+ "login-success",
289
+ `Token: ${tokens.token.slice(0, 12)}...`,
290
+ );
291
+ } catch (err) {
292
+ const msg = err instanceof Error ? err.message : "Unknown error";
293
+ context.facts.status = "idle";
294
+ context.facts.loginRequested = false;
295
+ addLogEntry(context.facts, "login-error", msg);
296
+ throw err;
297
+ }
298
+ },
299
+ },
300
+
301
+ refreshToken: {
302
+ requirement: "REFRESH_TOKEN",
303
+ retry: { attempts: 2, backoff: "exponential" },
304
+ timeout: 10000,
305
+ resolve: async (req, context) => {
306
+ context.facts.status = "refreshing";
307
+ addLogEntry(context.facts, "refresh", "Refreshing token...");
308
+
309
+ try {
310
+ const tokens = await mockRefresh(
311
+ req.refreshToken,
312
+ context.facts.refreshFailRate,
313
+ context.facts.tokenTTL,
314
+ );
315
+ context.facts.token = tokens.token;
316
+ context.facts.refreshToken = tokens.refreshToken;
317
+ context.facts.expiresAt = Date.now() + tokens.expiresIn * 1000;
318
+ context.facts.status = "authenticated";
319
+ addLogEntry(
320
+ context.facts,
321
+ "refresh-success",
322
+ `New token: ${tokens.token.slice(0, 12)}...`,
323
+ );
324
+ } catch (err) {
325
+ const msg = err instanceof Error ? err.message : "Unknown error";
326
+ context.facts.token = "";
327
+ context.facts.refreshToken = "";
328
+ context.facts.expiresAt = 0;
329
+ context.facts.status = "expired";
330
+ addLogEntry(context.facts, "refresh-error", msg);
331
+ throw err;
332
+ }
333
+ },
334
+ },
335
+
336
+ fetchUser: {
337
+ requirement: "FETCH_USER",
338
+ resolve: async (req, context) => {
339
+ addLogEntry(context.facts, "fetch-user", "Fetching user profile...");
340
+
341
+ try {
342
+ const user = await mockFetchUser(req.token);
343
+ context.facts.user = user;
344
+ addLogEntry(
345
+ context.facts,
346
+ "fetch-user-success",
347
+ `${user.name} (${user.role})`,
348
+ );
349
+ } catch (err) {
350
+ const msg = err instanceof Error ? err.message : "Unknown error";
351
+ addLogEntry(context.facts, "fetch-user-error", msg);
352
+ }
353
+ },
354
+ },
355
+ },
356
+
357
+ // ============================================================================
358
+ // Effects
359
+ // ============================================================================
360
+
361
+ effects: {
362
+ logStatusChange: {
363
+ deps: ["status"],
364
+ run: (facts, prev) => {
365
+ if (prev && prev.status !== facts.status) {
366
+ addLogEntry(facts, "status", `${prev.status} → ${facts.status}`);
367
+ }
368
+ },
369
+ },
370
+ },
371
+ });