@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,404 @@
1
+ // Example: websocket
2
+ // Source: examples/websocket/src/websocket.ts
3
+ // Pure module file — no DOM wiring
4
+
5
+ /**
6
+ * WebSocket Connections — Directive Module
7
+ *
8
+ * Demonstrates resolver-driven connection lifecycle, automatic reconnection
9
+ * via constraints with exponential backoff, live message streaming,
10
+ * reconnect countdown via time-based reactivity, and cleanup functions.
11
+ */
12
+
13
+ import { type ModuleSchema, createModule, t } from "@directive-run/core";
14
+ import { MockWebSocket, type WsMessage } from "./mock-ws.js";
15
+
16
+ // ============================================================================
17
+ // Types
18
+ // ============================================================================
19
+
20
+ export type WsStatus =
21
+ | "disconnected"
22
+ | "connecting"
23
+ | "connected"
24
+ | "reconnecting"
25
+ | "error";
26
+
27
+ export interface EventLogEntry {
28
+ timestamp: number;
29
+ event: string;
30
+ detail: string;
31
+ }
32
+
33
+ // ============================================================================
34
+ // Module-level socket reference
35
+ // ============================================================================
36
+
37
+ let activeSocket: MockWebSocket | null = null;
38
+
39
+ export function getActiveSocket(): MockWebSocket | null {
40
+ return activeSocket;
41
+ }
42
+
43
+ // ============================================================================
44
+ // Schema
45
+ // ============================================================================
46
+
47
+ export const websocketSchema = {
48
+ facts: {
49
+ url: t.string(),
50
+ status: t.string<WsStatus>(),
51
+ connectRequested: t.boolean(),
52
+ messages: t.object<WsMessage[]>(),
53
+ retryCount: t.number(),
54
+ maxRetries: t.number(),
55
+ messageToSend: t.string(),
56
+ now: t.number(),
57
+ reconnectTargetTime: t.number(),
58
+ messageRate: t.number(),
59
+ connectFailRate: t.number(),
60
+ reconnectFailRate: t.number(),
61
+ eventLog: t.object<EventLogEntry[]>(),
62
+ },
63
+ derivations: {
64
+ isConnected: t.boolean(),
65
+ shouldReconnect: t.boolean(),
66
+ reconnectDelay: t.number(),
67
+ reconnectCountdown: t.number(),
68
+ canSend: t.boolean(),
69
+ messageCount: t.number(),
70
+ },
71
+ events: {
72
+ requestConnect: {},
73
+ disconnect: {},
74
+ setMessageToSend: { value: t.string() },
75
+ messageSent: {},
76
+ setUrl: { value: t.string() },
77
+ setMessageRate: { value: t.number() },
78
+ setConnectFailRate: { value: t.number() },
79
+ setReconnectFailRate: { value: t.number() },
80
+ setMaxRetries: { value: t.number() },
81
+ tick: {},
82
+ clearMessages: {},
83
+ forceError: {},
84
+ },
85
+ requirements: {
86
+ CONNECT: {
87
+ url: t.string(),
88
+ messageRate: t.number(),
89
+ connectFailRate: t.number(),
90
+ },
91
+ RECONNECT: {
92
+ delay: t.number(),
93
+ reconnectFailRate: t.number(),
94
+ },
95
+ },
96
+ } satisfies ModuleSchema;
97
+
98
+ // ============================================================================
99
+ // Helpers
100
+ // ============================================================================
101
+
102
+ function addLogEntry(facts: any, event: string, detail: string): void {
103
+ const log = [...(facts.eventLog as EventLogEntry[])];
104
+ log.push({ timestamp: Date.now(), event, detail });
105
+ // Cap at 100
106
+ if (log.length > 100) {
107
+ log.splice(0, log.length - 100);
108
+ }
109
+ facts.eventLog = log;
110
+ }
111
+
112
+ // ============================================================================
113
+ // Module
114
+ // ============================================================================
115
+
116
+ export const websocketModule = createModule("websocket", {
117
+ schema: websocketSchema,
118
+
119
+ init: (facts) => {
120
+ facts.url = "wss://demo.directive.run/chat";
121
+ facts.status = "disconnected";
122
+ facts.connectRequested = false;
123
+ facts.messages = [];
124
+ facts.retryCount = 0;
125
+ facts.maxRetries = 5;
126
+ facts.messageToSend = "";
127
+ facts.now = Date.now();
128
+ facts.reconnectTargetTime = 0;
129
+ facts.messageRate = 3;
130
+ facts.connectFailRate = 0;
131
+ facts.reconnectFailRate = 0;
132
+ facts.eventLog = [];
133
+ },
134
+
135
+ // ============================================================================
136
+ // Derivations
137
+ // ============================================================================
138
+
139
+ derive: {
140
+ isConnected: (facts) => facts.status === "connected",
141
+
142
+ shouldReconnect: (facts) => {
143
+ return (
144
+ facts.status === "error" &&
145
+ facts.retryCount < facts.maxRetries &&
146
+ facts.connectRequested
147
+ );
148
+ },
149
+
150
+ reconnectDelay: (facts) => {
151
+ return Math.min(1000 * 2 ** facts.retryCount, 30000);
152
+ },
153
+
154
+ reconnectCountdown: (facts) => {
155
+ if (facts.reconnectTargetTime <= 0) {
156
+ return 0;
157
+ }
158
+
159
+ return Math.max(
160
+ 0,
161
+ Math.ceil((facts.reconnectTargetTime - facts.now) / 1000),
162
+ );
163
+ },
164
+
165
+ canSend: (facts) => {
166
+ return facts.status === "connected" && facts.messageToSend.trim() !== "";
167
+ },
168
+
169
+ messageCount: (facts) => facts.messages.length,
170
+ },
171
+
172
+ // ============================================================================
173
+ // Events
174
+ // ============================================================================
175
+
176
+ events: {
177
+ requestConnect: (facts) => {
178
+ facts.connectRequested = true;
179
+ facts.status = "connecting";
180
+ facts.retryCount = 0;
181
+ facts.reconnectTargetTime = 0;
182
+ facts.messages = [];
183
+ facts.eventLog = [];
184
+ },
185
+
186
+ disconnect: (facts) => {
187
+ facts.connectRequested = false;
188
+ facts.status = "disconnected";
189
+ facts.reconnectTargetTime = 0;
190
+
191
+ // Null out before close() so the onclose handler's stale-socket guard works
192
+ const socket = activeSocket;
193
+ activeSocket = null;
194
+ if (socket) {
195
+ socket.close();
196
+ }
197
+ },
198
+
199
+ setMessageToSend: (facts, { value }) => {
200
+ facts.messageToSend = value;
201
+ },
202
+
203
+ messageSent: (facts) => {
204
+ facts.messageToSend = "";
205
+ },
206
+
207
+ setUrl: (facts, { value }) => {
208
+ facts.url = value;
209
+ },
210
+
211
+ setMessageRate: (facts, { value }) => {
212
+ facts.messageRate = value;
213
+ },
214
+
215
+ setConnectFailRate: (facts, { value }) => {
216
+ facts.connectFailRate = value;
217
+ },
218
+
219
+ setReconnectFailRate: (facts, { value }) => {
220
+ facts.reconnectFailRate = value;
221
+ },
222
+
223
+ setMaxRetries: (facts, { value }) => {
224
+ facts.maxRetries = value;
225
+ },
226
+
227
+ tick: (facts) => {
228
+ facts.now = Date.now();
229
+ },
230
+
231
+ clearMessages: (facts) => {
232
+ facts.messages = [];
233
+ },
234
+
235
+ forceError: (facts) => {
236
+ facts.status = "error";
237
+
238
+ // Null out before close() so the onclose handler's stale-socket guard works
239
+ const socket = activeSocket;
240
+ activeSocket = null;
241
+ if (socket) {
242
+ socket.close();
243
+ }
244
+ },
245
+ },
246
+
247
+ // ============================================================================
248
+ // Constraints
249
+ // ============================================================================
250
+
251
+ constraints: {
252
+ needsConnection: {
253
+ priority: 100,
254
+ when: (facts) => {
255
+ return facts.connectRequested && facts.status === "connecting";
256
+ },
257
+ require: (facts) => ({
258
+ type: "CONNECT",
259
+ url: facts.url,
260
+ messageRate: facts.messageRate,
261
+ connectFailRate: facts.connectFailRate,
262
+ }),
263
+ },
264
+
265
+ needsReconnect: {
266
+ priority: 90,
267
+ when: (facts) => {
268
+ return (
269
+ facts.status === "error" &&
270
+ facts.retryCount < facts.maxRetries &&
271
+ facts.connectRequested
272
+ );
273
+ },
274
+ require: (facts) => ({
275
+ type: "RECONNECT",
276
+ delay: Math.min(1000 * 2 ** facts.retryCount, 30000),
277
+ reconnectFailRate: facts.reconnectFailRate,
278
+ }),
279
+ },
280
+ },
281
+
282
+ // ============================================================================
283
+ // Resolvers
284
+ // ============================================================================
285
+
286
+ resolvers: {
287
+ connect: {
288
+ requirement: "CONNECT",
289
+ timeout: 10000,
290
+ resolve: async (req, context) => {
291
+ addLogEntry(context.facts, "connect", `Connecting to ${req.url}...`);
292
+
293
+ // Close any existing socket
294
+ if (activeSocket) {
295
+ activeSocket.close();
296
+ activeSocket = null;
297
+ }
298
+
299
+ try {
300
+ const socket = new MockWebSocket(
301
+ req.url,
302
+ req.connectFailRate,
303
+ req.messageRate * 1000,
304
+ );
305
+
306
+ // Track this socket so we can detect stale callbacks
307
+ activeSocket = socket;
308
+ const currentSocket = socket;
309
+
310
+ socket.onmessage = (msg) => {
311
+ if (activeSocket !== currentSocket) {
312
+ return;
313
+ }
314
+
315
+ const messages = [...(context.facts.messages as WsMessage[])];
316
+ messages.push(msg);
317
+ // Cap at 50
318
+ if (messages.length > 50) {
319
+ messages.splice(0, messages.length - 50);
320
+ }
321
+ context.facts.messages = messages;
322
+ };
323
+
324
+ socket.onclose = () => {
325
+ if (activeSocket !== currentSocket) {
326
+ return;
327
+ }
328
+
329
+ context.facts.status = "disconnected";
330
+ activeSocket = null;
331
+ addLogEntry(context.facts, "close", "Connection closed");
332
+ };
333
+
334
+ socket.onerror = (error) => {
335
+ if (activeSocket !== currentSocket) {
336
+ return;
337
+ }
338
+
339
+ context.facts.status = "error";
340
+ activeSocket = null;
341
+ addLogEntry(context.facts, "error", error.message);
342
+ };
343
+
344
+ // Wait for connection to open
345
+ await new Promise<void>((resolve, reject) => {
346
+ socket.onopen = () => resolve();
347
+ const prevError = socket.onerror;
348
+ socket.onerror = (error) => {
349
+ prevError?.(error);
350
+ reject(error);
351
+ };
352
+ });
353
+
354
+ context.facts.status = "connected";
355
+ context.facts.retryCount = 0;
356
+ context.facts.reconnectTargetTime = 0;
357
+ addLogEntry(context.facts, "connected", "Connection established");
358
+ } catch (err) {
359
+ const msg = err instanceof Error ? err.message : "Unknown error";
360
+ context.facts.status = "error";
361
+ activeSocket = null;
362
+ addLogEntry(context.facts, "connect-error", msg);
363
+ throw err;
364
+ }
365
+ },
366
+ },
367
+
368
+ reconnect: {
369
+ requirement: "RECONNECT",
370
+ timeout: 60000,
371
+ resolve: async (req, context) => {
372
+ const retryCount = context.facts.retryCount as number;
373
+ context.facts.status = "reconnecting";
374
+ context.facts.reconnectTargetTime = Date.now() + req.delay;
375
+ addLogEntry(
376
+ context.facts,
377
+ "reconnect",
378
+ `Waiting ${(req.delay / 1000).toFixed(1)}s (attempt ${retryCount + 1})...`,
379
+ );
380
+
381
+ await new Promise((resolve) => setTimeout(resolve, req.delay));
382
+
383
+ context.facts.retryCount = retryCount + 1;
384
+ context.facts.reconnectTargetTime = 0;
385
+ context.facts.status = "connecting";
386
+ },
387
+ },
388
+ },
389
+
390
+ // ============================================================================
391
+ // Effects
392
+ // ============================================================================
393
+
394
+ effects: {
395
+ logStatusChange: {
396
+ deps: ["status"],
397
+ run: (facts, prev) => {
398
+ if (prev && prev.status !== facts.status) {
399
+ addLogEntry(facts, "status", `${prev.status} \u2192 ${facts.status}`);
400
+ }
401
+ },
402
+ },
403
+ },
404
+ });
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@directive-run/knowledge",
3
+ "version": "0.2.0",
4
+ "description": "Knowledge files, examples, and validation for Directive — the constraint-driven TypeScript runtime.",
5
+ "license": "MIT",
6
+ "author": "Jason Comes",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/directive-run/directive",
10
+ "directory": "packages/knowledge"
11
+ },
12
+ "homepage": "https://directive.run",
13
+ "bugs": {
14
+ "url": "https://github.com/directive-run/directive/issues"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public",
18
+ "registry": "https://registry.npmjs.org"
19
+ },
20
+ "engines": {
21
+ "node": ">=18"
22
+ },
23
+ "keywords": [
24
+ "directive",
25
+ "knowledge",
26
+ "ai-rules",
27
+ "examples"
28
+ ],
29
+ "type": "module",
30
+ "main": "./dist/index.cjs",
31
+ "module": "./dist/index.js",
32
+ "types": "./dist/index.d.ts",
33
+ "exports": {
34
+ ".": {
35
+ "types": "./dist/index.d.ts",
36
+ "require": "./dist/index.cjs",
37
+ "import": "./dist/index.js"
38
+ }
39
+ },
40
+ "files": [
41
+ "dist",
42
+ "core",
43
+ "ai",
44
+ "examples",
45
+ "api-skeleton.md"
46
+ ],
47
+ "devDependencies": {
48
+ "@types/node": "^25.2.0",
49
+ "tsup": "^8.3.5",
50
+ "tsx": "^4.19.2",
51
+ "typescript": "^5.7.2",
52
+ "vitest": "^3.0.0",
53
+ "@directive-run/core": "0.4.1",
54
+ "@directive-run/ai": "0.4.1"
55
+ },
56
+ "scripts": {
57
+ "build": "tsx scripts/generate-api-skeleton.ts && tsx scripts/extract-examples.ts && tsup",
58
+ "generate": "tsx scripts/generate-api-skeleton.ts",
59
+ "extract-examples": "tsx scripts/extract-examples.ts",
60
+ "validate": "tsx scripts/validate-knowledge.ts",
61
+ "test": "tsx scripts/validate-knowledge.ts && vitest run",
62
+ "typecheck": "tsc --noEmit",
63
+ "clean": "rm -rf dist"
64
+ }
65
+ }