@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.
- package/LICENSE +21 -0
- package/README.md +63 -0
- package/ai/ai-adapters.md +250 -0
- package/ai/ai-agents-streaming.md +269 -0
- package/ai/ai-budget-resilience.md +235 -0
- package/ai/ai-communication.md +281 -0
- package/ai/ai-debug-observability.md +243 -0
- package/ai/ai-guardrails-memory.md +332 -0
- package/ai/ai-mcp-rag.md +288 -0
- package/ai/ai-multi-agent.md +274 -0
- package/ai/ai-orchestrator.md +227 -0
- package/ai/ai-security.md +293 -0
- package/ai/ai-tasks.md +261 -0
- package/ai/ai-testing-evals.md +378 -0
- package/api-skeleton.md +5 -0
- package/core/anti-patterns.md +382 -0
- package/core/constraints.md +263 -0
- package/core/core-patterns.md +228 -0
- package/core/error-boundaries.md +322 -0
- package/core/multi-module.md +315 -0
- package/core/naming.md +283 -0
- package/core/plugins.md +344 -0
- package/core/react-adapter.md +262 -0
- package/core/resolvers.md +357 -0
- package/core/schema-types.md +262 -0
- package/core/system-api.md +271 -0
- package/core/testing.md +257 -0
- package/core/time-travel.md +238 -0
- package/dist/index.cjs +111 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +102 -0
- package/dist/index.js.map +1 -0
- package/examples/ab-testing.ts +385 -0
- package/examples/ai-checkpoint.ts +509 -0
- package/examples/ai-guardrails.ts +319 -0
- package/examples/ai-orchestrator.ts +589 -0
- package/examples/async-chains.ts +287 -0
- package/examples/auth-flow.ts +371 -0
- package/examples/batch-resolver.ts +341 -0
- package/examples/checkers.ts +589 -0
- package/examples/contact-form.ts +176 -0
- package/examples/counter.ts +393 -0
- package/examples/dashboard-loader.ts +512 -0
- package/examples/debounce-constraints.ts +105 -0
- package/examples/dynamic-modules.ts +293 -0
- package/examples/error-boundaries.ts +430 -0
- package/examples/feature-flags.ts +220 -0
- package/examples/form-wizard.ts +347 -0
- package/examples/fraud-analysis.ts +663 -0
- package/examples/goal-heist.ts +341 -0
- package/examples/multi-module.ts +57 -0
- package/examples/newsletter.ts +241 -0
- package/examples/notifications.ts +210 -0
- package/examples/optimistic-updates.ts +317 -0
- package/examples/pagination.ts +260 -0
- package/examples/permissions.ts +337 -0
- package/examples/provider-routing.ts +403 -0
- package/examples/server.ts +316 -0
- package/examples/shopping-cart.ts +422 -0
- package/examples/sudoku.ts +630 -0
- package/examples/theme-locale.ts +204 -0
- package/examples/time-machine.ts +225 -0
- package/examples/topic-guard.ts +306 -0
- package/examples/url-sync.ts +333 -0
- package/examples/websocket.ts +404 -0
- package/package.json +65 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
// Example: batch-resolver
|
|
2
|
+
// Source: examples/batch-resolver/src/main.ts
|
|
3
|
+
// Extracted for AI rules — DOM wiring stripped
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Batch Data Loader — Batched Resolution & Schema Validation
|
|
7
|
+
*
|
|
8
|
+
* User directory that loads profiles. Multiple constraints fire simultaneously;
|
|
9
|
+
* the batch resolver groups them into one call. Dev-mode schema validation
|
|
10
|
+
* catches type mismatches.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
type ModuleSchema,
|
|
15
|
+
createModule,
|
|
16
|
+
createSystem,
|
|
17
|
+
t,
|
|
18
|
+
} from "@directive-run/core";
|
|
19
|
+
import { devtoolsPlugin } from "@directive-run/core/plugins";
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Types
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
interface UserProfile {
|
|
26
|
+
id: number;
|
|
27
|
+
name: string;
|
|
28
|
+
email: string;
|
|
29
|
+
role: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface TimelineEntry {
|
|
33
|
+
time: number;
|
|
34
|
+
event: string;
|
|
35
|
+
detail: string;
|
|
36
|
+
type: "info" | "batch" | "error" | "success" | "validation";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// Mock Data
|
|
41
|
+
// ============================================================================
|
|
42
|
+
|
|
43
|
+
const MOCK_USERS: Record<number, UserProfile> = {
|
|
44
|
+
1: { id: 1, name: "Alice Chen", email: "alice@acme.com", role: "Admin" },
|
|
45
|
+
2: { id: 2, name: "Bob Smith", email: "bob@acme.com", role: "Editor" },
|
|
46
|
+
3: { id: 3, name: "Carol Davis", email: "carol@acme.com", role: "Viewer" },
|
|
47
|
+
4: { id: 4, name: "Dave Wilson", email: "dave@acme.com", role: "Editor" },
|
|
48
|
+
5: { id: 5, name: "Eve Brown", email: "eve@acme.com", role: "Admin" },
|
|
49
|
+
6: { id: 6, name: "Frank Lee", email: "frank@acme.com", role: "Viewer" },
|
|
50
|
+
7: { id: 7, name: "Grace Kim", email: "grace@acme.com", role: "Editor" },
|
|
51
|
+
8: { id: 8, name: "Hank Moore", email: "hank@acme.com", role: "Viewer" },
|
|
52
|
+
9: { id: 9, name: "Iris Park", email: "iris@acme.com", role: "Admin" },
|
|
53
|
+
10: { id: 10, name: "Jack Turner", email: "jack@acme.com", role: "Editor" },
|
|
54
|
+
11: { id: 11, name: "Kate Adams", email: "kate@acme.com", role: "Viewer" },
|
|
55
|
+
12: { id: 12, name: "Leo Garcia", email: "leo@acme.com", role: "Editor" },
|
|
56
|
+
13: { id: 13, name: "Mia Jones", email: "mia@acme.com", role: "Admin" },
|
|
57
|
+
14: { id: 14, name: "Nick White", email: "nick@acme.com", role: "Viewer" },
|
|
58
|
+
15: { id: 15, name: "Olivia Hall", email: "olivia@acme.com", role: "Editor" },
|
|
59
|
+
16: { id: 16, name: "Pete Clark", email: "pete@acme.com", role: "Viewer" },
|
|
60
|
+
17: { id: 17, name: "Quinn Ross", email: "quinn@acme.com", role: "Admin" },
|
|
61
|
+
18: { id: 18, name: "Rosa Martin", email: "rosa@acme.com", role: "Editor" },
|
|
62
|
+
19: { id: 19, name: "Steve Young", email: "steve@acme.com", role: "Viewer" },
|
|
63
|
+
20: { id: 20, name: "Tina Allen", email: "tina@acme.com", role: "Admin" },
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Timeline
|
|
68
|
+
// ============================================================================
|
|
69
|
+
|
|
70
|
+
const timeline: TimelineEntry[] = [];
|
|
71
|
+
|
|
72
|
+
function addTimeline(
|
|
73
|
+
event: string,
|
|
74
|
+
detail: string,
|
|
75
|
+
type: TimelineEntry["type"],
|
|
76
|
+
) {
|
|
77
|
+
timeline.unshift({ time: Date.now(), event, detail, type });
|
|
78
|
+
if (timeline.length > 50) {
|
|
79
|
+
timeline.length = 50;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ============================================================================
|
|
84
|
+
// Schema
|
|
85
|
+
// ============================================================================
|
|
86
|
+
|
|
87
|
+
const schema = {
|
|
88
|
+
facts: {
|
|
89
|
+
users: t.object<UserProfile[]>(),
|
|
90
|
+
loadingIds: t.object<number[]>(),
|
|
91
|
+
batchCount: t.number(),
|
|
92
|
+
totalRequests: t.number(),
|
|
93
|
+
batchWindowMs: t.number(),
|
|
94
|
+
failItemId: t.number(),
|
|
95
|
+
validationErrors: t.object<string[]>(),
|
|
96
|
+
},
|
|
97
|
+
derivations: {
|
|
98
|
+
userCount: t.number(),
|
|
99
|
+
loadingCount: t.number(),
|
|
100
|
+
batchEfficiency: t.string(),
|
|
101
|
+
hasValidationErrors: t.boolean(),
|
|
102
|
+
},
|
|
103
|
+
events: {
|
|
104
|
+
loadUser: { id: t.number() },
|
|
105
|
+
loadRange: { start: t.number(), count: t.number() },
|
|
106
|
+
setBatchWindow: { value: t.number() },
|
|
107
|
+
setFailItemId: { value: t.number() },
|
|
108
|
+
injectSchemaError: {},
|
|
109
|
+
clearUsers: {},
|
|
110
|
+
resetAll: {},
|
|
111
|
+
},
|
|
112
|
+
requirements: {
|
|
113
|
+
LOAD_USER: { userId: t.number() },
|
|
114
|
+
},
|
|
115
|
+
} satisfies ModuleSchema;
|
|
116
|
+
|
|
117
|
+
// ============================================================================
|
|
118
|
+
// Module
|
|
119
|
+
// ============================================================================
|
|
120
|
+
|
|
121
|
+
const batchModule = createModule("batch-loader", {
|
|
122
|
+
schema,
|
|
123
|
+
|
|
124
|
+
init: (facts) => {
|
|
125
|
+
facts.users = [];
|
|
126
|
+
facts.loadingIds = [];
|
|
127
|
+
facts.batchCount = 0;
|
|
128
|
+
facts.totalRequests = 0;
|
|
129
|
+
facts.batchWindowMs = 50;
|
|
130
|
+
facts.failItemId = 0;
|
|
131
|
+
facts.validationErrors = [];
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
derive: {
|
|
135
|
+
userCount: (facts) => facts.users.length,
|
|
136
|
+
loadingCount: (facts) => facts.loadingIds.length,
|
|
137
|
+
batchEfficiency: (facts) => {
|
|
138
|
+
if (facts.totalRequests === 0) {
|
|
139
|
+
return "N/A";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return `${facts.batchCount} batches / ${facts.totalRequests} requests`;
|
|
143
|
+
},
|
|
144
|
+
hasValidationErrors: (facts) => facts.validationErrors.length > 0,
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
events: {
|
|
148
|
+
loadUser: (facts, { id }) => {
|
|
149
|
+
if (
|
|
150
|
+
!facts.loadingIds.includes(id) &&
|
|
151
|
+
!facts.users.find((u: UserProfile) => u.id === id)
|
|
152
|
+
) {
|
|
153
|
+
facts.loadingIds = [...facts.loadingIds, id];
|
|
154
|
+
facts.totalRequests = facts.totalRequests + 1;
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
loadRange: (facts, { start, count }) => {
|
|
158
|
+
const newIds: number[] = [];
|
|
159
|
+
for (let i = start; i < start + count; i++) {
|
|
160
|
+
if (
|
|
161
|
+
!facts.loadingIds.includes(i) &&
|
|
162
|
+
!facts.users.find((u: UserProfile) => u.id === i)
|
|
163
|
+
) {
|
|
164
|
+
newIds.push(i);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (newIds.length > 0) {
|
|
168
|
+
facts.loadingIds = [...facts.loadingIds, ...newIds];
|
|
169
|
+
facts.totalRequests = facts.totalRequests + newIds.length;
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
setBatchWindow: (facts, { value }) => {
|
|
173
|
+
facts.batchWindowMs = value;
|
|
174
|
+
},
|
|
175
|
+
setFailItemId: (facts, { value }) => {
|
|
176
|
+
facts.failItemId = value;
|
|
177
|
+
},
|
|
178
|
+
injectSchemaError: (facts) => {
|
|
179
|
+
// Intentionally write a bad type to trigger validation
|
|
180
|
+
(facts as Record<string, unknown>).users = "not-an-array";
|
|
181
|
+
facts.validationErrors = [
|
|
182
|
+
...facts.validationErrors,
|
|
183
|
+
"schema: expected array for 'users', got string",
|
|
184
|
+
];
|
|
185
|
+
"validation",
|
|
186
|
+
"schema error: expected array for 'users'",
|
|
187
|
+
"validation",
|
|
188
|
+
);
|
|
189
|
+
// Fix it immediately so the system keeps working
|
|
190
|
+
facts.users = [];
|
|
191
|
+
},
|
|
192
|
+
clearUsers: (facts) => {
|
|
193
|
+
facts.users = [];
|
|
194
|
+
},
|
|
195
|
+
resetAll: (facts) => {
|
|
196
|
+
facts.users = [];
|
|
197
|
+
facts.loadingIds = [];
|
|
198
|
+
facts.batchCount = 0;
|
|
199
|
+
facts.totalRequests = 0;
|
|
200
|
+
facts.failItemId = 0;
|
|
201
|
+
facts.validationErrors = [];
|
|
202
|
+
timeline.length = 0;
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
constraints: {
|
|
207
|
+
needsLoad: {
|
|
208
|
+
priority: 50,
|
|
209
|
+
when: (facts) => facts.loadingIds.length > 0,
|
|
210
|
+
require: (facts) => {
|
|
211
|
+
// Emit one requirement per loading ID — the batch resolver groups them
|
|
212
|
+
const id = facts.loadingIds[0];
|
|
213
|
+
|
|
214
|
+
return { type: "LOAD_USER", userId: id };
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
resolvers: {
|
|
220
|
+
loadUser: {
|
|
221
|
+
requirement: "LOAD_USER",
|
|
222
|
+
batch: {
|
|
223
|
+
enabled: true,
|
|
224
|
+
windowMs: 50,
|
|
225
|
+
},
|
|
226
|
+
resolveBatchWithResults: async (requirements, context) => {
|
|
227
|
+
const ids = requirements.map((r) => r.userId);
|
|
228
|
+
"batch",
|
|
229
|
+
`batch formed: ${ids.length} items [${ids.join(", ")}]`,
|
|
230
|
+
"batch",
|
|
231
|
+
);
|
|
232
|
+
context.facts.batchCount = context.facts.batchCount + 1;
|
|
233
|
+
|
|
234
|
+
// Simulate API delay
|
|
235
|
+
await new Promise((resolve) =>
|
|
236
|
+
setTimeout(resolve, 150 + Math.random() * 100),
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
const failId = context.facts.failItemId;
|
|
240
|
+
const results = ids.map((id) => {
|
|
241
|
+
if (id === failId) {
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
success: false as const,
|
|
245
|
+
error: new Error(`Failed to load user ${id}`),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const user = MOCK_USERS[id];
|
|
250
|
+
if (!user) {
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
success: false as const,
|
|
254
|
+
error: new Error(`User ${id} not found`),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return { success: true as const };
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Add successful users to facts
|
|
262
|
+
const successUsers = ids
|
|
263
|
+
.filter((id) => id !== failId && MOCK_USERS[id])
|
|
264
|
+
.map((id) => MOCK_USERS[id]!);
|
|
265
|
+
|
|
266
|
+
if (successUsers.length > 0) {
|
|
267
|
+
const existing = context.facts.users as UserProfile[];
|
|
268
|
+
context.facts.users = [...existing, ...successUsers];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Remove all processed IDs from loading
|
|
272
|
+
const loadingIds = context.facts.loadingIds as number[];
|
|
273
|
+
context.facts.loadingIds = loadingIds.filter(
|
|
274
|
+
(lid: number) => !ids.includes(lid),
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const successCount = results.filter((r) => r.success).length;
|
|
278
|
+
"success",
|
|
279
|
+
`batch resolved: ${successCount}/${ids.length} success`,
|
|
280
|
+
"success",
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
return results;
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// ============================================================================
|
|
290
|
+
// System
|
|
291
|
+
// ============================================================================
|
|
292
|
+
|
|
293
|
+
const system = createSystem({
|
|
294
|
+
module: batchModule,
|
|
295
|
+
debug: { runHistory: true },
|
|
296
|
+
plugins: [devtoolsPlugin({ name: "batch-resolver" })],
|
|
297
|
+
});
|
|
298
|
+
system.start();
|
|
299
|
+
|
|
300
|
+
// ============================================================================
|
|
301
|
+
// DOM References
|
|
302
|
+
// ============================================================================
|
|
303
|
+
|
|
304
|
+
"bl-fail-item",
|
|
305
|
+
|
|
306
|
+
// ============================================================================
|
|
307
|
+
// Render
|
|
308
|
+
// ============================================================================
|
|
309
|
+
|
|
310
|
+
function escapeHtml(text: string): string {
|
|
311
|
+
|
|
312
|
+
return div.innerHTML;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
// ============================================================================
|
|
317
|
+
// Subscribe
|
|
318
|
+
// ============================================================================
|
|
319
|
+
|
|
320
|
+
const allKeys = [
|
|
321
|
+
...Object.keys(schema.facts),
|
|
322
|
+
...Object.keys(schema.derivations),
|
|
323
|
+
];
|
|
324
|
+
system.subscribe(allKeys, render);
|
|
325
|
+
|
|
326
|
+
// ============================================================================
|
|
327
|
+
// Controls
|
|
328
|
+
// ============================================================================
|
|
329
|
+
|
|
330
|
+
// Individual load buttons (1-5)
|
|
331
|
+
for (let i = 1; i <= 5; i++) {
|
|
332
|
+
system.events.loadUser({ id: i });
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
// ============================================================================
|
|
338
|
+
// Initial Render
|
|
339
|
+
// ============================================================================
|
|
340
|
+
|
|
341
|
+
render();
|