@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,210 @@
1
+ // Example: notifications
2
+ // Source: examples/notifications/src/notifications.ts
3
+ // Pure module file — no DOM wiring
4
+
5
+ /**
6
+ * Notifications & Toasts — Directive Modules
7
+ *
8
+ * Two modules:
9
+ * - notifications: queue management, auto-dismiss via constraints, overflow protection
10
+ * - app: action log that triggers cross-module notifications via effects
11
+ */
12
+
13
+ import { type ModuleSchema, createModule, t } from "@directive-run/core";
14
+
15
+ // ============================================================================
16
+ // Types
17
+ // ============================================================================
18
+
19
+ export interface Notification {
20
+ id: string;
21
+ message: string;
22
+ level: "info" | "success" | "warning" | "error";
23
+ createdAt: number;
24
+ ttl: number;
25
+ }
26
+
27
+ // ============================================================================
28
+ // Notifications Module
29
+ // ============================================================================
30
+
31
+ export const notificationsSchema = {
32
+ facts: {
33
+ queue: t.object<Notification[]>(),
34
+ maxVisible: t.number(),
35
+ now: t.number(),
36
+ idCounter: t.number(),
37
+ },
38
+ derivations: {
39
+ visibleNotifications: t.object<Notification[]>(),
40
+ hasNotifications: t.boolean(),
41
+ oldestExpired: t.object<Notification | null>(),
42
+ },
43
+ events: {
44
+ addNotification: {
45
+ message: t.string(),
46
+ level: t.string(),
47
+ ttl: t.number().optional(),
48
+ },
49
+ dismissNotification: { id: t.string() },
50
+ tick: {},
51
+ setMaxVisible: { value: t.number() },
52
+ },
53
+ requirements: {
54
+ DISMISS_NOTIFICATION: { id: t.string() },
55
+ },
56
+ } satisfies ModuleSchema;
57
+
58
+ export const notificationsModule = createModule("notifications", {
59
+ schema: notificationsSchema,
60
+
61
+ init: (facts) => {
62
+ facts.queue = [];
63
+ facts.maxVisible = 5;
64
+ facts.now = Date.now();
65
+ facts.idCounter = 0;
66
+ },
67
+
68
+ // ============================================================================
69
+ // Derivations
70
+ // ============================================================================
71
+
72
+ derive: {
73
+ visibleNotifications: (facts) => {
74
+ return (facts.queue as Notification[]).slice(
75
+ 0,
76
+ facts.maxVisible as number,
77
+ );
78
+ },
79
+
80
+ hasNotifications: (facts) => {
81
+ return (facts.queue as Notification[]).length > 0;
82
+ },
83
+
84
+ oldestExpired: (facts) => {
85
+ const queue = facts.queue as Notification[];
86
+ const oldest = queue[0];
87
+ if (!oldest) {
88
+ return null;
89
+ }
90
+
91
+ if ((facts.now as number) > oldest.createdAt + oldest.ttl) {
92
+ return oldest;
93
+ }
94
+
95
+ return null;
96
+ },
97
+ },
98
+
99
+ // ============================================================================
100
+ // Constraints
101
+ // ============================================================================
102
+
103
+ constraints: {
104
+ autoDismiss: {
105
+ priority: 50,
106
+ when: (_facts, derive) => derive.oldestExpired !== null,
107
+ require: (_facts, derive) => ({
108
+ type: "DISMISS_NOTIFICATION" as const,
109
+ id: (derive.oldestExpired as Notification).id,
110
+ }),
111
+ },
112
+
113
+ overflow: {
114
+ priority: 60,
115
+ when: (facts) => {
116
+ const queue = facts.queue as Notification[];
117
+
118
+ return queue.length > (facts.maxVisible as number) + 5;
119
+ },
120
+ require: (facts) => ({
121
+ type: "DISMISS_NOTIFICATION" as const,
122
+ id: (facts.queue as Notification[])[0].id,
123
+ }),
124
+ },
125
+ },
126
+
127
+ // ============================================================================
128
+ // Resolvers
129
+ // ============================================================================
130
+
131
+ resolvers: {
132
+ dismiss: {
133
+ requirement: "DISMISS_NOTIFICATION",
134
+ resolve: async (req, context) => {
135
+ context.facts.queue = (context.facts.queue as Notification[]).filter(
136
+ (n) => n.id !== req.id,
137
+ );
138
+ },
139
+ },
140
+ },
141
+
142
+ // ============================================================================
143
+ // Events
144
+ // ============================================================================
145
+
146
+ events: {
147
+ addNotification: (
148
+ facts,
149
+ payload: { message: string; level: string; ttl?: number },
150
+ ) => {
151
+ const ttlMap: Record<string, number> = {
152
+ info: 4000,
153
+ success: 3000,
154
+ warning: 6000,
155
+ error: 10000,
156
+ };
157
+ const counter = (facts.idCounter as number) + 1;
158
+ facts.idCounter = counter;
159
+
160
+ const notification: Notification = {
161
+ id: `notif-${counter}`,
162
+ message: payload.message,
163
+ level: payload.level as Notification["level"],
164
+ createdAt: Date.now(),
165
+ ttl: payload.ttl ?? ttlMap[payload.level] ?? 4000,
166
+ };
167
+
168
+ facts.queue = [...(facts.queue as Notification[]), notification];
169
+ },
170
+
171
+ dismissNotification: (facts, { id }: { id: string }) => {
172
+ facts.queue = (facts.queue as Notification[]).filter((n) => n.id !== id);
173
+ },
174
+
175
+ tick: (facts) => {
176
+ facts.now = Date.now();
177
+ },
178
+
179
+ setMaxVisible: (facts, { value }: { value: number }) => {
180
+ facts.maxVisible = value;
181
+ },
182
+ },
183
+ });
184
+
185
+ // ============================================================================
186
+ // App Module
187
+ // ============================================================================
188
+
189
+ export const appSchema = {
190
+ facts: {
191
+ actionLog: t.object<string[]>(),
192
+ },
193
+ events: {
194
+ simulateAction: { message: t.string(), level: t.string() },
195
+ },
196
+ } satisfies ModuleSchema;
197
+
198
+ export const appModule = createModule("app", {
199
+ schema: appSchema,
200
+
201
+ init: (facts) => {
202
+ facts.actionLog = [];
203
+ },
204
+
205
+ events: {
206
+ simulateAction: (facts, { message }: { message: string }) => {
207
+ facts.actionLog = [...(facts.actionLog as string[]), message];
208
+ },
209
+ },
210
+ });
@@ -0,0 +1,317 @@
1
+ // Example: optimistic-updates
2
+ // Source: examples/optimistic-updates/src/optimistic-updates.ts
3
+ // Pure module file — no DOM wiring
4
+
5
+ /**
6
+ * Optimistic Updates — Directive Module
7
+ *
8
+ * Demonstrates optimistic mutations via events (instant UI), server sync via
9
+ * constraint-resolver pattern, per-operation rollback from a sync queue,
10
+ * resolver key deduplication, toast notifications, and context.snapshot().
11
+ */
12
+
13
+ import { type ModuleSchema, createModule, t } from "@directive-run/core";
14
+ import { mockServerSync } from "./mock-server.js";
15
+
16
+ // ============================================================================
17
+ // Types
18
+ // ============================================================================
19
+
20
+ export interface TodoItem {
21
+ id: string;
22
+ text: string;
23
+ done: boolean;
24
+ }
25
+
26
+ export type OpType = "toggle" | "delete" | "add";
27
+
28
+ export interface SyncQueueEntry {
29
+ opId: string;
30
+ itemId: string;
31
+ op: OpType;
32
+ undoItems: TodoItem[];
33
+ }
34
+
35
+ export interface EventLogEntry {
36
+ timestamp: number;
37
+ event: string;
38
+ detail: string;
39
+ }
40
+
41
+ // ============================================================================
42
+ // ID Generation
43
+ // ============================================================================
44
+
45
+ let nextId = 6; // items are pre-seeded 1-5
46
+ let nextOpId = 1;
47
+
48
+ // ============================================================================
49
+ // Schema
50
+ // ============================================================================
51
+
52
+ export const optimisticUpdatesSchema = {
53
+ facts: {
54
+ items: t.object<TodoItem[]>(),
55
+ syncQueue: t.object<SyncQueueEntry[]>(),
56
+ syncingOpId: t.string(),
57
+ newItemText: t.string(),
58
+ serverDelay: t.number(),
59
+ failRate: t.number(),
60
+ toastMessage: t.string(),
61
+ toastType: t.string(),
62
+ eventLog: t.object<EventLogEntry[]>(),
63
+ },
64
+ derivations: {
65
+ totalCount: t.number(),
66
+ doneCount: t.number(),
67
+ pendingCount: t.number(),
68
+ canAdd: t.boolean(),
69
+ isSyncing: t.boolean(),
70
+ },
71
+ events: {
72
+ toggleItem: { id: t.string() },
73
+ deleteItem: { id: t.string() },
74
+ addItem: {},
75
+ setNewItemText: { value: t.string() },
76
+ setServerDelay: { value: t.number() },
77
+ setFailRate: { value: t.number() },
78
+ dismissToast: {},
79
+ },
80
+ requirements: {
81
+ SYNC_TODO: {
82
+ opId: t.string(),
83
+ },
84
+ },
85
+ } satisfies ModuleSchema;
86
+
87
+ // ============================================================================
88
+ // Helpers
89
+ // ============================================================================
90
+
91
+ function addLogEntry(facts: any, event: string, detail: string): void {
92
+ const log = [...(facts.eventLog as EventLogEntry[])];
93
+ log.push({ timestamp: Date.now(), event, detail });
94
+ if (log.length > 100) {
95
+ log.splice(0, log.length - 100);
96
+ }
97
+ facts.eventLog = log;
98
+ }
99
+
100
+ // ============================================================================
101
+ // Module
102
+ // ============================================================================
103
+
104
+ export const optimisticUpdatesModule = createModule("optimistic-updates", {
105
+ schema: optimisticUpdatesSchema,
106
+
107
+ init: (facts) => {
108
+ facts.items = [
109
+ { id: "1", text: "Buy groceries", done: false },
110
+ { id: "2", text: "Learn Directive", done: true },
111
+ { id: "3", text: "Walk the dog", done: false },
112
+ { id: "4", text: "Read a book", done: false },
113
+ { id: "5", text: "Fix the bug", done: true },
114
+ ];
115
+ facts.syncQueue = [];
116
+ facts.syncingOpId = "";
117
+ facts.newItemText = "";
118
+ facts.serverDelay = 800;
119
+ facts.failRate = 30;
120
+ facts.toastMessage = "";
121
+ facts.toastType = "";
122
+ facts.eventLog = [];
123
+ },
124
+
125
+ // ============================================================================
126
+ // Derivations
127
+ // ============================================================================
128
+
129
+ derive: {
130
+ totalCount: (facts) => (facts.items as TodoItem[]).length,
131
+
132
+ doneCount: (facts) =>
133
+ (facts.items as TodoItem[]).filter((i) => i.done).length,
134
+
135
+ pendingCount: (facts) => (facts.syncQueue as SyncQueueEntry[]).length,
136
+
137
+ canAdd: (facts) => (facts.newItemText as string).trim() !== "",
138
+
139
+ isSyncing: (facts) => (facts.syncingOpId as string) !== "",
140
+ },
141
+
142
+ // ============================================================================
143
+ // Events
144
+ // ============================================================================
145
+
146
+ events: {
147
+ toggleItem: (facts, { id }) => {
148
+ const items = facts.items as TodoItem[];
149
+ const undoItems = items.map((i) => ({ ...i }));
150
+
151
+ facts.items = items.map((i) =>
152
+ i.id === id ? { ...i, done: !i.done } : i,
153
+ );
154
+
155
+ const opId = String(nextOpId++);
156
+ const queue = [...(facts.syncQueue as SyncQueueEntry[])];
157
+ queue.push({ opId, itemId: id, op: "toggle", undoItems });
158
+ facts.syncQueue = queue;
159
+
160
+ addLogEntry(facts, "optimistic", `Toggle item ${id}`);
161
+ },
162
+
163
+ deleteItem: (facts, { id }) => {
164
+ const items = facts.items as TodoItem[];
165
+ const undoItems = items.map((i) => ({ ...i }));
166
+
167
+ facts.items = items.filter((i) => i.id !== id);
168
+
169
+ const opId = String(nextOpId++);
170
+ const queue = [...(facts.syncQueue as SyncQueueEntry[])];
171
+ queue.push({ opId, itemId: id, op: "delete", undoItems });
172
+ facts.syncQueue = queue;
173
+
174
+ addLogEntry(facts, "optimistic", `Delete item ${id}`);
175
+ },
176
+
177
+ addItem: (facts) => {
178
+ const text = (facts.newItemText as string).trim();
179
+ if (!text) {
180
+ return;
181
+ }
182
+
183
+ const items = facts.items as TodoItem[];
184
+ const undoItems = items.map((i) => ({ ...i }));
185
+
186
+ const itemId = String(nextId++);
187
+ facts.items = [...items, { id: itemId, text, done: false }];
188
+ facts.newItemText = "";
189
+
190
+ const opId = String(nextOpId++);
191
+ const queue = [...(facts.syncQueue as SyncQueueEntry[])];
192
+ queue.push({ opId, itemId, op: "add", undoItems });
193
+ facts.syncQueue = queue;
194
+
195
+ addLogEntry(facts, "optimistic", `Add item "${text}"`);
196
+ },
197
+
198
+ setNewItemText: (facts, { value }) => {
199
+ facts.newItemText = value;
200
+ },
201
+
202
+ setServerDelay: (facts, { value }) => {
203
+ facts.serverDelay = value;
204
+ },
205
+
206
+ setFailRate: (facts, { value }) => {
207
+ facts.failRate = value;
208
+ },
209
+
210
+ dismissToast: (facts) => {
211
+ facts.toastMessage = "";
212
+ facts.toastType = "";
213
+ },
214
+ },
215
+
216
+ // ============================================================================
217
+ // Constraints
218
+ // ============================================================================
219
+
220
+ constraints: {
221
+ needsSync: {
222
+ priority: 100,
223
+ when: (facts) => {
224
+ const queue = facts.syncQueue as SyncQueueEntry[];
225
+ const syncingOpId = facts.syncingOpId as string;
226
+
227
+ return queue.length > 0 && syncingOpId === "";
228
+ },
229
+ require: (facts) => {
230
+ const queue = facts.syncQueue as SyncQueueEntry[];
231
+
232
+ return {
233
+ type: "SYNC_TODO",
234
+ opId: queue[0].opId,
235
+ };
236
+ },
237
+ },
238
+ },
239
+
240
+ // ============================================================================
241
+ // Resolvers
242
+ // ============================================================================
243
+
244
+ resolvers: {
245
+ syncTodo: {
246
+ requirement: "SYNC_TODO",
247
+ key: (req) => `sync-${req.opId}`,
248
+ timeout: 10000,
249
+ resolve: async (req, context) => {
250
+ const queue = context.facts.syncQueue as SyncQueueEntry[];
251
+ const entry = queue.find((e) => e.opId === req.opId);
252
+ if (!entry) {
253
+ return;
254
+ }
255
+
256
+ context.facts.syncingOpId = req.opId;
257
+ addLogEntry(
258
+ context.facts,
259
+ "syncing",
260
+ `Syncing ${entry.op} for item ${entry.itemId}...`,
261
+ );
262
+
263
+ const serverDelay = context.facts.serverDelay as number;
264
+ const failRate = context.facts.failRate as number;
265
+
266
+ try {
267
+ await mockServerSync(entry.op, entry.itemId, serverDelay, failRate);
268
+
269
+ addLogEntry(
270
+ context.facts,
271
+ "success",
272
+ `${entry.op} item ${entry.itemId} synced`,
273
+ );
274
+ context.facts.toastMessage = `${entry.op} synced successfully`;
275
+ context.facts.toastType = "success";
276
+ } catch {
277
+ context.facts.items = entry.undoItems;
278
+ addLogEntry(
279
+ context.facts,
280
+ "rollback",
281
+ `Failed to ${entry.op} item ${entry.itemId} — rolled back`,
282
+ );
283
+ context.facts.toastMessage = `Failed to ${entry.op} — rolled back`;
284
+ context.facts.toastType = "error";
285
+ }
286
+
287
+ // Remove entry from queue
288
+ const currentQueue = context.facts.syncQueue as SyncQueueEntry[];
289
+ context.facts.syncQueue = currentQueue.filter(
290
+ (e) => e.opId !== req.opId,
291
+ );
292
+ context.facts.syncingOpId = "";
293
+ },
294
+ },
295
+ },
296
+
297
+ // ============================================================================
298
+ // Effects
299
+ // ============================================================================
300
+
301
+ effects: {
302
+ logSyncChange: {
303
+ deps: ["syncingOpId"],
304
+ run: (facts, prev) => {
305
+ if (prev) {
306
+ const prevId = prev.syncingOpId as string;
307
+ const currId = facts.syncingOpId as string;
308
+ if (prevId === "" && currId !== "") {
309
+ addLogEntry(facts, "status", `Sync started: op ${currId}`);
310
+ } else if (prevId !== "" && currId === "") {
311
+ addLogEntry(facts, "status", `Sync completed: op ${prevId}`);
312
+ }
313
+ }
314
+ },
315
+ },
316
+ },
317
+ });