@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,512 @@
|
|
|
1
|
+
// Example: dashboard-loader
|
|
2
|
+
// Source: examples/dashboard-loader/src/dashboard-loader.ts
|
|
3
|
+
// Pure module file — no DOM wiring
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Dashboard Loader — Directive Module
|
|
7
|
+
*
|
|
8
|
+
* Demonstrates loading & error states with concurrent resource fetching,
|
|
9
|
+
* configurable delays/failure rates, retry with exponential backoff,
|
|
10
|
+
* and combined status derivations.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { type ModuleSchema, createModule, t } from "@directive-run/core";
|
|
14
|
+
import {
|
|
15
|
+
type Permissions,
|
|
16
|
+
type Preferences,
|
|
17
|
+
type Profile,
|
|
18
|
+
fetchMockPermissions,
|
|
19
|
+
fetchMockPreferences,
|
|
20
|
+
fetchMockProfile,
|
|
21
|
+
} from "./mock-api.js";
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Types
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
export type ResourceStatus = "idle" | "loading" | "success" | "error";
|
|
28
|
+
|
|
29
|
+
export interface ResourceState<T> {
|
|
30
|
+
data: T | null;
|
|
31
|
+
status: ResourceStatus;
|
|
32
|
+
error: string | null;
|
|
33
|
+
attempts: number;
|
|
34
|
+
startedAt: number | null;
|
|
35
|
+
completedAt: number | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface EventLogEntry {
|
|
39
|
+
timestamp: number;
|
|
40
|
+
event: string;
|
|
41
|
+
resource: string;
|
|
42
|
+
detail: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function makeIdleResource<T>(): ResourceState<T> {
|
|
46
|
+
return {
|
|
47
|
+
data: null,
|
|
48
|
+
status: "idle",
|
|
49
|
+
error: null,
|
|
50
|
+
attempts: 0,
|
|
51
|
+
startedAt: null,
|
|
52
|
+
completedAt: null,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// Schema
|
|
58
|
+
// ============================================================================
|
|
59
|
+
|
|
60
|
+
export const dashboardLoaderSchema = {
|
|
61
|
+
facts: {
|
|
62
|
+
userId: t.string(),
|
|
63
|
+
profile: t.object<ResourceState<Profile>>(),
|
|
64
|
+
preferences: t.object<ResourceState<Preferences>>(),
|
|
65
|
+
permissions: t.object<ResourceState<Permissions>>(),
|
|
66
|
+
profileDelay: t.number(),
|
|
67
|
+
preferencesDelay: t.number(),
|
|
68
|
+
permissionsDelay: t.number(),
|
|
69
|
+
profileFailRate: t.number(),
|
|
70
|
+
preferencesFailRate: t.number(),
|
|
71
|
+
permissionsFailRate: t.number(),
|
|
72
|
+
loadRequested: t.boolean(),
|
|
73
|
+
eventLog: t.object<EventLogEntry[]>(),
|
|
74
|
+
},
|
|
75
|
+
derivations: {
|
|
76
|
+
loadedCount: t.number(),
|
|
77
|
+
totalResources: t.number(),
|
|
78
|
+
allLoaded: t.boolean(),
|
|
79
|
+
anyError: t.boolean(),
|
|
80
|
+
anyLoading: t.boolean(),
|
|
81
|
+
combinedStatus: t.string(),
|
|
82
|
+
canStart: t.boolean(),
|
|
83
|
+
},
|
|
84
|
+
events: {
|
|
85
|
+
setUserId: { value: t.string() },
|
|
86
|
+
start: {},
|
|
87
|
+
retryResource: { resource: t.string() },
|
|
88
|
+
reloadAll: {},
|
|
89
|
+
setDelay: { resource: t.string(), value: t.number() },
|
|
90
|
+
setFailRate: { resource: t.string(), value: t.number() },
|
|
91
|
+
},
|
|
92
|
+
requirements: {
|
|
93
|
+
FETCH_PROFILE: { userId: t.string() },
|
|
94
|
+
FETCH_PREFERENCES: { userId: t.string() },
|
|
95
|
+
FETCH_PERMISSIONS: { userId: t.string() },
|
|
96
|
+
},
|
|
97
|
+
} satisfies ModuleSchema;
|
|
98
|
+
|
|
99
|
+
// ============================================================================
|
|
100
|
+
// Helpers
|
|
101
|
+
// ============================================================================
|
|
102
|
+
|
|
103
|
+
function addLogEntry(
|
|
104
|
+
facts: any,
|
|
105
|
+
event: string,
|
|
106
|
+
resource: string,
|
|
107
|
+
detail: string,
|
|
108
|
+
): void {
|
|
109
|
+
const log = [...(facts.eventLog as EventLogEntry[])];
|
|
110
|
+
log.push({ timestamp: Date.now(), event, resource, detail });
|
|
111
|
+
facts.eventLog = log;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ============================================================================
|
|
115
|
+
// Module
|
|
116
|
+
// ============================================================================
|
|
117
|
+
|
|
118
|
+
export const dashboardLoaderModule = createModule("dashboard-loader", {
|
|
119
|
+
schema: dashboardLoaderSchema,
|
|
120
|
+
|
|
121
|
+
init: (facts) => {
|
|
122
|
+
facts.userId = "";
|
|
123
|
+
facts.profile = makeIdleResource<Profile>();
|
|
124
|
+
facts.preferences = makeIdleResource<Preferences>();
|
|
125
|
+
facts.permissions = makeIdleResource<Permissions>();
|
|
126
|
+
facts.profileDelay = 1000;
|
|
127
|
+
facts.preferencesDelay = 1500;
|
|
128
|
+
facts.permissionsDelay = 2000;
|
|
129
|
+
facts.profileFailRate = 0;
|
|
130
|
+
facts.preferencesFailRate = 0;
|
|
131
|
+
facts.permissionsFailRate = 0;
|
|
132
|
+
facts.loadRequested = false;
|
|
133
|
+
facts.eventLog = [];
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
// ============================================================================
|
|
137
|
+
// Derivations
|
|
138
|
+
// ============================================================================
|
|
139
|
+
|
|
140
|
+
derive: {
|
|
141
|
+
loadedCount: (facts) => {
|
|
142
|
+
const resources = [
|
|
143
|
+
facts.profile,
|
|
144
|
+
facts.preferences,
|
|
145
|
+
facts.permissions,
|
|
146
|
+
] as ResourceState<unknown>[];
|
|
147
|
+
|
|
148
|
+
return resources.filter((r) => r.status === "success").length;
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
totalResources: () => 3,
|
|
152
|
+
|
|
153
|
+
allLoaded: (facts) => {
|
|
154
|
+
const resources = [
|
|
155
|
+
facts.profile,
|
|
156
|
+
facts.preferences,
|
|
157
|
+
facts.permissions,
|
|
158
|
+
] as ResourceState<unknown>[];
|
|
159
|
+
|
|
160
|
+
return resources.every((r) => r.status === "success");
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
anyError: (facts) => {
|
|
164
|
+
const resources = [
|
|
165
|
+
facts.profile,
|
|
166
|
+
facts.preferences,
|
|
167
|
+
facts.permissions,
|
|
168
|
+
] as ResourceState<unknown>[];
|
|
169
|
+
|
|
170
|
+
return resources.some((r) => r.status === "error");
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
anyLoading: (facts) => {
|
|
174
|
+
const resources = [
|
|
175
|
+
facts.profile,
|
|
176
|
+
facts.preferences,
|
|
177
|
+
facts.permissions,
|
|
178
|
+
] as ResourceState<unknown>[];
|
|
179
|
+
|
|
180
|
+
return resources.some((r) => r.status === "loading");
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
combinedStatus: (facts, derive) => {
|
|
184
|
+
const loaded = derive.loadedCount as number;
|
|
185
|
+
const anyErr = derive.anyError as boolean;
|
|
186
|
+
const anyLoad = derive.anyLoading as boolean;
|
|
187
|
+
const allIdle = [
|
|
188
|
+
facts.profile,
|
|
189
|
+
facts.preferences,
|
|
190
|
+
facts.permissions,
|
|
191
|
+
].every((r: any) => r.status === "idle");
|
|
192
|
+
|
|
193
|
+
if (allIdle) {
|
|
194
|
+
return "Not started";
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const errCount = [
|
|
198
|
+
facts.profile,
|
|
199
|
+
facts.preferences,
|
|
200
|
+
facts.permissions,
|
|
201
|
+
].filter((r: any) => r.status === "error").length;
|
|
202
|
+
|
|
203
|
+
if (anyLoad) {
|
|
204
|
+
return `Loading ${loaded} of 3...`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (anyErr && loaded > 0) {
|
|
208
|
+
return `${errCount} failed, ${loaded} loaded`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (anyErr) {
|
|
212
|
+
return `${errCount} failed`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return "All loaded";
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
canStart: (facts) => {
|
|
219
|
+
const id = (facts.userId as string).trim();
|
|
220
|
+
const allIdle = [
|
|
221
|
+
facts.profile,
|
|
222
|
+
facts.preferences,
|
|
223
|
+
facts.permissions,
|
|
224
|
+
].every((r: any) => r.status === "idle");
|
|
225
|
+
|
|
226
|
+
return id.length > 0 && allIdle;
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
// ============================================================================
|
|
231
|
+
// Events
|
|
232
|
+
// ============================================================================
|
|
233
|
+
|
|
234
|
+
events: {
|
|
235
|
+
setUserId: (facts, { value }) => {
|
|
236
|
+
facts.userId = value;
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
start: (facts) => {
|
|
240
|
+
const id = (facts.userId as string).trim();
|
|
241
|
+
if (id.length === 0) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Reset all resources to idle so constraints re-fire
|
|
246
|
+
facts.profile = makeIdleResource<Profile>();
|
|
247
|
+
facts.preferences = makeIdleResource<Preferences>();
|
|
248
|
+
facts.permissions = makeIdleResource<Permissions>();
|
|
249
|
+
facts.loadRequested = true;
|
|
250
|
+
facts.eventLog = [];
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
retryResource: (facts, { resource }) => {
|
|
254
|
+
const res = (facts as any)[resource] as ResourceState<unknown>;
|
|
255
|
+
if (!res || res.status !== "error") {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
(facts as any)[resource] = {
|
|
260
|
+
...res,
|
|
261
|
+
status: "idle",
|
|
262
|
+
error: null,
|
|
263
|
+
};
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
reloadAll: (facts) => {
|
|
267
|
+
facts.profile = makeIdleResource<Profile>();
|
|
268
|
+
facts.preferences = makeIdleResource<Preferences>();
|
|
269
|
+
facts.permissions = makeIdleResource<Permissions>();
|
|
270
|
+
facts.eventLog = [];
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
setDelay: (facts, { resource, value }) => {
|
|
274
|
+
const key = `${resource}Delay` as keyof typeof facts;
|
|
275
|
+
if (key in facts) {
|
|
276
|
+
(facts as any)[key] = value;
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
setFailRate: (facts, { resource, value }) => {
|
|
281
|
+
const key = `${resource}FailRate` as keyof typeof facts;
|
|
282
|
+
if (key in facts) {
|
|
283
|
+
(facts as any)[key] = value;
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
// ============================================================================
|
|
289
|
+
// Constraints
|
|
290
|
+
// ============================================================================
|
|
291
|
+
|
|
292
|
+
constraints: {
|
|
293
|
+
needsProfile: {
|
|
294
|
+
priority: 100,
|
|
295
|
+
when: (facts) => {
|
|
296
|
+
const id = (facts.userId as string).trim();
|
|
297
|
+
const profile = facts.profile as ResourceState<Profile>;
|
|
298
|
+
|
|
299
|
+
return (
|
|
300
|
+
(facts.loadRequested as boolean) &&
|
|
301
|
+
id !== "" &&
|
|
302
|
+
profile.status === "idle"
|
|
303
|
+
);
|
|
304
|
+
},
|
|
305
|
+
require: (facts) => ({
|
|
306
|
+
type: "FETCH_PROFILE",
|
|
307
|
+
userId: (facts.userId as string).trim(),
|
|
308
|
+
}),
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
needsPreferences: {
|
|
312
|
+
priority: 90,
|
|
313
|
+
when: (facts) => {
|
|
314
|
+
const id = (facts.userId as string).trim();
|
|
315
|
+
const prefs = facts.preferences as ResourceState<Preferences>;
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
(facts.loadRequested as boolean) &&
|
|
319
|
+
id !== "" &&
|
|
320
|
+
prefs.status === "idle"
|
|
321
|
+
);
|
|
322
|
+
},
|
|
323
|
+
require: (facts) => ({
|
|
324
|
+
type: "FETCH_PREFERENCES",
|
|
325
|
+
userId: (facts.userId as string).trim(),
|
|
326
|
+
}),
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
needsPermissions: {
|
|
330
|
+
priority: 80,
|
|
331
|
+
when: (facts) => {
|
|
332
|
+
const id = (facts.userId as string).trim();
|
|
333
|
+
const perms = facts.permissions as ResourceState<Permissions>;
|
|
334
|
+
|
|
335
|
+
return (
|
|
336
|
+
(facts.loadRequested as boolean) &&
|
|
337
|
+
id !== "" &&
|
|
338
|
+
perms.status === "idle"
|
|
339
|
+
);
|
|
340
|
+
},
|
|
341
|
+
require: (facts) => ({
|
|
342
|
+
type: "FETCH_PERMISSIONS",
|
|
343
|
+
userId: (facts.userId as string).trim(),
|
|
344
|
+
}),
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
// ============================================================================
|
|
349
|
+
// Resolvers
|
|
350
|
+
// ============================================================================
|
|
351
|
+
|
|
352
|
+
resolvers: {
|
|
353
|
+
fetchProfile: {
|
|
354
|
+
requirement: "FETCH_PROFILE",
|
|
355
|
+
retry: { attempts: 3, backoff: "exponential" },
|
|
356
|
+
timeout: 10000,
|
|
357
|
+
resolve: async (req, context) => {
|
|
358
|
+
const prev = context.facts.profile as ResourceState<Profile>;
|
|
359
|
+
context.facts.profile = {
|
|
360
|
+
...prev,
|
|
361
|
+
status: "loading",
|
|
362
|
+
attempts: prev.attempts + 1,
|
|
363
|
+
startedAt: prev.startedAt ?? Date.now(),
|
|
364
|
+
};
|
|
365
|
+
addLogEntry(
|
|
366
|
+
context.facts,
|
|
367
|
+
"loading",
|
|
368
|
+
"profile",
|
|
369
|
+
`Attempt ${prev.attempts + 1}`,
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
const data = await fetchMockProfile(
|
|
374
|
+
req.userId,
|
|
375
|
+
context.facts.profileDelay as number,
|
|
376
|
+
context.facts.profileFailRate as number,
|
|
377
|
+
);
|
|
378
|
+
context.facts.profile = {
|
|
379
|
+
data,
|
|
380
|
+
status: "success",
|
|
381
|
+
error: null,
|
|
382
|
+
attempts: (context.facts.profile as ResourceState<Profile>)
|
|
383
|
+
.attempts,
|
|
384
|
+
startedAt: (context.facts.profile as ResourceState<Profile>)
|
|
385
|
+
.startedAt,
|
|
386
|
+
completedAt: Date.now(),
|
|
387
|
+
};
|
|
388
|
+
addLogEntry(context.facts, "success", "profile", data.name);
|
|
389
|
+
} catch (err) {
|
|
390
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
391
|
+
context.facts.profile = {
|
|
392
|
+
...(context.facts.profile as ResourceState<Profile>),
|
|
393
|
+
status: "error",
|
|
394
|
+
error: msg,
|
|
395
|
+
completedAt: Date.now(),
|
|
396
|
+
};
|
|
397
|
+
addLogEntry(context.facts, "error", "profile", msg);
|
|
398
|
+
throw err;
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
|
|
403
|
+
fetchPreferences: {
|
|
404
|
+
requirement: "FETCH_PREFERENCES",
|
|
405
|
+
retry: { attempts: 2, backoff: "exponential" },
|
|
406
|
+
resolve: async (req, context) => {
|
|
407
|
+
const prev = context.facts.preferences as ResourceState<Preferences>;
|
|
408
|
+
context.facts.preferences = {
|
|
409
|
+
...prev,
|
|
410
|
+
status: "loading",
|
|
411
|
+
attempts: prev.attempts + 1,
|
|
412
|
+
startedAt: prev.startedAt ?? Date.now(),
|
|
413
|
+
};
|
|
414
|
+
addLogEntry(
|
|
415
|
+
context.facts,
|
|
416
|
+
"loading",
|
|
417
|
+
"preferences",
|
|
418
|
+
`Attempt ${prev.attempts + 1}`,
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
try {
|
|
422
|
+
const data = await fetchMockPreferences(
|
|
423
|
+
req.userId,
|
|
424
|
+
context.facts.preferencesDelay as number,
|
|
425
|
+
context.facts.preferencesFailRate as number,
|
|
426
|
+
);
|
|
427
|
+
context.facts.preferences = {
|
|
428
|
+
data,
|
|
429
|
+
status: "success",
|
|
430
|
+
error: null,
|
|
431
|
+
attempts: (context.facts.preferences as ResourceState<Preferences>)
|
|
432
|
+
.attempts,
|
|
433
|
+
startedAt: (context.facts.preferences as ResourceState<Preferences>)
|
|
434
|
+
.startedAt,
|
|
435
|
+
completedAt: Date.now(),
|
|
436
|
+
};
|
|
437
|
+
addLogEntry(
|
|
438
|
+
context.facts,
|
|
439
|
+
"success",
|
|
440
|
+
"preferences",
|
|
441
|
+
`${data.theme} / ${data.locale}`,
|
|
442
|
+
);
|
|
443
|
+
} catch (err) {
|
|
444
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
445
|
+
context.facts.preferences = {
|
|
446
|
+
...(context.facts.preferences as ResourceState<Preferences>),
|
|
447
|
+
status: "error",
|
|
448
|
+
error: msg,
|
|
449
|
+
completedAt: Date.now(),
|
|
450
|
+
};
|
|
451
|
+
addLogEntry(context.facts, "error", "preferences", msg);
|
|
452
|
+
throw err;
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
fetchPermissions: {
|
|
458
|
+
requirement: "FETCH_PERMISSIONS",
|
|
459
|
+
retry: { attempts: 3, backoff: "exponential" },
|
|
460
|
+
timeout: 15000,
|
|
461
|
+
resolve: async (req, context) => {
|
|
462
|
+
const prev = context.facts.permissions as ResourceState<Permissions>;
|
|
463
|
+
context.facts.permissions = {
|
|
464
|
+
...prev,
|
|
465
|
+
status: "loading",
|
|
466
|
+
attempts: prev.attempts + 1,
|
|
467
|
+
startedAt: prev.startedAt ?? Date.now(),
|
|
468
|
+
};
|
|
469
|
+
addLogEntry(
|
|
470
|
+
context.facts,
|
|
471
|
+
"loading",
|
|
472
|
+
"permissions",
|
|
473
|
+
`Attempt ${prev.attempts + 1}`,
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
try {
|
|
477
|
+
const data = await fetchMockPermissions(
|
|
478
|
+
req.userId,
|
|
479
|
+
context.facts.permissionsDelay as number,
|
|
480
|
+
context.facts.permissionsFailRate as number,
|
|
481
|
+
);
|
|
482
|
+
context.facts.permissions = {
|
|
483
|
+
data,
|
|
484
|
+
status: "success",
|
|
485
|
+
error: null,
|
|
486
|
+
attempts: (context.facts.permissions as ResourceState<Permissions>)
|
|
487
|
+
.attempts,
|
|
488
|
+
startedAt: (context.facts.permissions as ResourceState<Permissions>)
|
|
489
|
+
.startedAt,
|
|
490
|
+
completedAt: Date.now(),
|
|
491
|
+
};
|
|
492
|
+
addLogEntry(
|
|
493
|
+
context.facts,
|
|
494
|
+
"success",
|
|
495
|
+
"permissions",
|
|
496
|
+
`${data.role} (${data.features.join(", ")})`,
|
|
497
|
+
);
|
|
498
|
+
} catch (err) {
|
|
499
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
500
|
+
context.facts.permissions = {
|
|
501
|
+
...(context.facts.permissions as ResourceState<Permissions>),
|
|
502
|
+
status: "error",
|
|
503
|
+
error: msg,
|
|
504
|
+
completedAt: Date.now(),
|
|
505
|
+
};
|
|
506
|
+
addLogEntry(context.facts, "error", "permissions", msg);
|
|
507
|
+
throw err;
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Example: debounce-constraints
|
|
2
|
+
// Source: examples/debounce-constraints/src/main.ts
|
|
3
|
+
// Extracted for AI rules — DOM wiring stripped
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Debounce Constraints — DOM Rendering & System Wiring
|
|
7
|
+
*
|
|
8
|
+
* Creates the Directive system, subscribes to state changes,
|
|
9
|
+
* renders the search input, debounce progress bar, results list,
|
|
10
|
+
* stats, config sliders, and event timeline.
|
|
11
|
+
* A 100ms timer drives reactive debounce countdown.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createSystem } from "@directive-run/core";
|
|
15
|
+
import { devtoolsPlugin } from "@directive-run/core/plugins";
|
|
16
|
+
import {
|
|
17
|
+
type EventLogEntry,
|
|
18
|
+
type SearchResult,
|
|
19
|
+
debounceSearchModule,
|
|
20
|
+
debounceSearchSchema,
|
|
21
|
+
} from "./debounce-search.js";
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// System
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
const system = createSystem({
|
|
28
|
+
module: debounceSearchModule,
|
|
29
|
+
debug: { runHistory: true },
|
|
30
|
+
plugins: [devtoolsPlugin({ name: "debounce-constraints" })],
|
|
31
|
+
});
|
|
32
|
+
system.start();
|
|
33
|
+
|
|
34
|
+
const allKeys = [
|
|
35
|
+
...Object.keys(debounceSearchSchema.facts),
|
|
36
|
+
...Object.keys(debounceSearchSchema.derivations),
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// DOM References
|
|
41
|
+
// ============================================================================
|
|
42
|
+
|
|
43
|
+
// Status bar
|
|
44
|
+
|
|
45
|
+
// Search form
|
|
46
|
+
"dc-search-input",
|
|
47
|
+
|
|
48
|
+
// Progress bar
|
|
49
|
+
|
|
50
|
+
// Query display
|
|
51
|
+
|
|
52
|
+
// Results
|
|
53
|
+
|
|
54
|
+
// Stats
|
|
55
|
+
|
|
56
|
+
// Config sliders
|
|
57
|
+
"dc-debounce-delay",
|
|
58
|
+
"dc-api-delay",
|
|
59
|
+
"dc-min-chars",
|
|
60
|
+
|
|
61
|
+
// Timeline
|
|
62
|
+
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// Render
|
|
65
|
+
// ============================================================================
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// Subscribe
|
|
70
|
+
// ============================================================================
|
|
71
|
+
|
|
72
|
+
system.subscribe(allKeys, render);
|
|
73
|
+
|
|
74
|
+
// Timer — tick every 100ms for smooth debounce progress bar
|
|
75
|
+
const tickInterval = setInterval(() => {
|
|
76
|
+
system.events.tick();
|
|
77
|
+
}, 100);
|
|
78
|
+
|
|
79
|
+
// ============================================================================
|
|
80
|
+
// Controls
|
|
81
|
+
// ============================================================================
|
|
82
|
+
|
|
83
|
+
// Search input — fire on every keystroke
|
|
84
|
+
|
|
85
|
+
// Clear
|
|
86
|
+
|
|
87
|
+
// Sliders
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// Helpers
|
|
92
|
+
// ============================================================================
|
|
93
|
+
|
|
94
|
+
function escapeHtml(text: string): string {
|
|
95
|
+
|
|
96
|
+
return div.innerHTML;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ============================================================================
|
|
100
|
+
// Initial Render
|
|
101
|
+
// ============================================================================
|
|
102
|
+
|
|
103
|
+
render();
|
|
104
|
+
|
|
105
|
+
// Signal to tests that the module script has fully initialized
|