@directive-run/knowledge 1.14.0 → 1.16.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/README.md +50 -15
- package/ai/ai-adapters.md +7 -0
- package/ai/ai-agents-streaming.md +187 -149
- package/ai/ai-budget-resilience.md +305 -132
- package/ai/ai-communication.md +220 -197
- package/ai/ai-debug-observability.md +259 -173
- package/ai/ai-guardrails-memory.md +191 -153
- package/ai/ai-mcp-rag.md +204 -199
- package/ai/ai-multi-agent.md +254 -153
- package/ai/ai-orchestrator.md +353 -114
- package/ai/ai-security.md +287 -180
- package/ai/ai-tasks.md +8 -1
- package/ai/ai-testing-evals.md +363 -256
- package/core/anti-patterns.md +18 -2
- package/core/constraints.md +13 -2
- package/core/core-patterns.md +12 -0
- package/core/error-boundaries.md +8 -0
- package/core/history.md +7 -0
- package/core/multi-module.md +15 -3
- package/core/naming.md +128 -90
- package/core/plugins.md +7 -0
- package/core/react-adapter.md +256 -174
- package/core/resolvers.md +10 -0
- package/core/schema-types.md +8 -0
- package/core/system-api.md +10 -0
- package/core/testing.md +257 -143
- package/examples/checkers.ts +15 -16
- package/examples/contact-form.ts +2 -2
- package/examples/counter-react.ts +1 -1
- package/examples/counter-svelte.ts +1 -1
- package/examples/counter-vue.ts +1 -1
- package/examples/counter.ts +1 -1
- package/examples/data-triggers.ts +4 -4
- package/examples/feature-flags.ts +2 -2
- package/examples/form-wizard.ts +2 -2
- package/examples/newsletter.ts +2 -2
- package/examples/server.ts +2 -2
- package/examples/shopping-cart.ts +1 -1
- package/examples/topic-guard.ts +1 -1
- package/package.json +3 -3
- package/sitemap.md +17 -11
- package/examples/debounce-constraints.ts +0 -95
- package/examples/multi-module.ts +0 -58
package/core/anti-patterns.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Anti-Patterns
|
|
2
2
|
|
|
3
|
+
> Covers `@directive-run/core` and `@directive-run/react` — hallucination-prone API patterns to avoid.
|
|
4
|
+
|
|
3
5
|
19 most common mistakes when generating Directive code, ranked by AI hallucination frequency. Every code generation MUST be checked against this list.
|
|
4
6
|
|
|
5
7
|
## 1. Unnecessary Type Casting on Facts/Derivations
|
|
@@ -113,11 +115,16 @@ createModule("timer", {
|
|
|
113
115
|
## 7. String-Based Event Dispatch
|
|
114
116
|
|
|
115
117
|
```typescript
|
|
116
|
-
// WRONG –
|
|
118
|
+
// WRONG – there is no two-argument string-keyed dispatch signature
|
|
117
119
|
system.dispatch("login", { token: "abc" });
|
|
118
120
|
|
|
119
|
-
// CORRECT – use the events accessor
|
|
121
|
+
// CORRECT – use the typed events accessor (preferred — autocomplete + payload typing)
|
|
120
122
|
system.events.login({ token: "abc" });
|
|
123
|
+
|
|
124
|
+
// ALSO VALID – the single-arg object form of dispatch() is supported when you
|
|
125
|
+
// need to forward a programmatically-built event. Prefer the events accessor
|
|
126
|
+
// for normal code.
|
|
127
|
+
system.dispatch({ type: "login", token: "abc" });
|
|
121
128
|
```
|
|
122
129
|
|
|
123
130
|
## 8. Direct Array/Object Mutation
|
|
@@ -364,3 +371,12 @@ Before generating any Directive code, verify:
|
|
|
364
371
|
8. Multi-module uses `facts.self.*` for own facts
|
|
365
372
|
9. Imports from `@directive-run/core`, not deep paths
|
|
366
373
|
10. `await system.settle()` after `system.start()`
|
|
374
|
+
|
|
375
|
+
## See also
|
|
376
|
+
|
|
377
|
+
- [`naming.md`](./naming.md) — the strict canonical-term rules AND the alias map for cross-paradigm searches
|
|
378
|
+
- [`constraints.md`](./constraints.md) — the constraint shape these anti-patterns reference (`facts` not in scope inside static `require:`, etc.)
|
|
379
|
+
- [`resolvers.md`](./resolvers.md) — the resolver shape these anti-patterns reference (return `void`, mutate `context.facts`, `(req, context)` not `(req, ctx)`)
|
|
380
|
+
- [`schema-types.md`](./schema-types.md) — the `t.*()` builders that exist and the hallucinated ones (`t.map`, `t.set`, `t.promise`, `t.date`) that don't
|
|
381
|
+
- [`react-adapter.md`](./react-adapter.md) — the React hooks that exist and the hallucinated ones (`useEvent`, `useSystem`, `DirectiveProvider`) that don't
|
|
382
|
+
- [`error-boundaries.md`](./error-boundaries.md) — error-handling shapes AI assistants sometimes invent
|
package/core/constraints.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Constraints
|
|
2
2
|
|
|
3
|
+
> Covers `@directive-run/core` — constraint definition: `when` / `require`, async constraints with `deps`, priority, data-form predicates.
|
|
4
|
+
|
|
3
5
|
Constraints declare WHEN something is needed. They are the demand side of the constraint-resolver pattern. Constraints evaluate conditions against facts and emit requirements that resolvers fulfill.
|
|
4
6
|
|
|
5
7
|
## Decision Tree: "Should this be a constraint?"
|
|
@@ -22,8 +24,10 @@ constraints: {
|
|
|
22
24
|
// when() returns boolean – evaluated on every fact change
|
|
23
25
|
when: (facts) => facts.isAuthenticated && !facts.user,
|
|
24
26
|
|
|
25
|
-
// require – the requirement to emit when condition is true
|
|
26
|
-
|
|
27
|
+
// require – the requirement to emit when condition is true.
|
|
28
|
+
// Use the function form whenever you need to read facts —
|
|
29
|
+
// facts is NOT in scope inside the static object form.
|
|
30
|
+
require: (facts) => ({ type: "FETCH_USER", userId: facts.userId }),
|
|
27
31
|
},
|
|
28
32
|
},
|
|
29
33
|
```
|
|
@@ -261,3 +265,10 @@ constraints: {
|
|
|
261
265
|
"Whenever X changes, log it" → Effect
|
|
262
266
|
"X is always facts.a + facts.b" → Derivation
|
|
263
267
|
```
|
|
268
|
+
|
|
269
|
+
## See also
|
|
270
|
+
|
|
271
|
+
- [`resolvers.md`](./resolvers.md) — the other half of the constraint-resolver loop; the supply side that fulfills the requirements constraints emit
|
|
272
|
+
- [`core-patterns.md`](./core-patterns.md) — how constraints fit into the module shape alongside facts, derivations, events, and effects
|
|
273
|
+
- [`multi-module.md`](./multi-module.md) — `crossModuleDeps` for constraints that fire on facts owned by another module
|
|
274
|
+
- [`anti-patterns.md`](./anti-patterns.md) — the constraint-shaped mistakes AI assistants reliably make
|
package/core/core-patterns.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Core Patterns
|
|
2
2
|
|
|
3
|
+
> Covers `@directive-run/core` — modules, facts, derivations, effects, events, and the constraint-resolver loop.
|
|
4
|
+
|
|
3
5
|
How to think about building with Directive: modules, systems, and the constraint-resolver pattern.
|
|
4
6
|
|
|
5
7
|
## Decision Tree: "Where does this logic go?"
|
|
@@ -226,3 +228,13 @@ const typed = createModule("typed", {
|
|
|
226
228
|
},
|
|
227
229
|
});
|
|
228
230
|
```
|
|
231
|
+
|
|
232
|
+
## See also
|
|
233
|
+
|
|
234
|
+
- [`constraints.md`](./constraints.md) — the demand side of the constraint-resolver loop
|
|
235
|
+
- [`resolvers.md`](./resolvers.md) — the supply side that fulfills requirements
|
|
236
|
+
- [`schema-types.md`](./schema-types.md) — the `t.*()` builders that define fact and derivation types
|
|
237
|
+
- [`multi-module.md`](./multi-module.md) — when a single module isn't enough
|
|
238
|
+
- [`system-api.md`](./system-api.md) — `createSystem` and the runtime instance once your module is defined
|
|
239
|
+
- [`naming.md`](./naming.md) — terminology conventions every example here follows
|
|
240
|
+
- [`testing.md`](./testing.md) — `createTestSystem` for the modules you're learning to write
|
package/core/error-boundaries.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Error Boundaries
|
|
2
2
|
|
|
3
|
+
> Covers `@directive-run/core` — error boundaries, recovery strategies, lifecycle hooks, and circuit breakers.
|
|
4
|
+
|
|
3
5
|
How to handle errors in Directive: recovery strategies, error boundaries, lifecycle hooks, and the circuit breaker pattern.
|
|
4
6
|
|
|
5
7
|
## Decision Tree: "How should errors be handled?"
|
|
@@ -320,3 +322,9 @@ try {
|
|
|
320
322
|
6. Add `onError` callback for logging/monitoring
|
|
321
323
|
7. Use `"throw"` for derivation errors (they indicate bugs)
|
|
322
324
|
8. Use `"skip"` for non-critical effects (logging, analytics)
|
|
325
|
+
|
|
326
|
+
## See also
|
|
327
|
+
|
|
328
|
+
- [`resolvers.md`](./resolvers.md) — retry policies and cancellation; the most common error source
|
|
329
|
+
- [`plugins.md`](./plugins.md) — `createCircuitBreaker` lives in `@directive-run/core/plugins`; pair it with error boundaries for cascading-failure isolation
|
|
330
|
+
- [`anti-patterns.md`](./anti-patterns.md) — common error-handling mistakes (catching the wrong type, swallowing recoverable errors)
|
package/core/history.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# History & Snapshots
|
|
2
2
|
|
|
3
|
+
> Covers `@directive-run/core` — time-travel: snapshot, undo/redo, replay, export/import, changeset grouping.
|
|
4
|
+
|
|
3
5
|
Directive records fact changes as snapshots, enabling undo/redo, replay, export/import, and changeset grouping.
|
|
4
6
|
|
|
5
7
|
## Decision Tree: "Should I enable history?"
|
|
@@ -342,3 +344,8 @@ const system = createSystem({
|
|
|
342
344
|
history: true,
|
|
343
345
|
});
|
|
344
346
|
```
|
|
347
|
+
|
|
348
|
+
## See also
|
|
349
|
+
|
|
350
|
+
- [`system-api.md`](./system-api.md) — `history` config in `createSystem` + the full `system.history.*` API
|
|
351
|
+
- [`testing.md`](./testing.md) — replay-driven testing patterns that pair with recorded snapshots
|
package/core/multi-module.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Multi-Module Systems
|
|
2
2
|
|
|
3
|
+
> Covers `@directive-run/core` — namespaced multi-module systems, cross-module deps, `facts.self.*`.
|
|
4
|
+
|
|
3
5
|
How to compose multiple modules into a namespaced system with cross-module type safety.
|
|
4
6
|
|
|
5
7
|
## Decision Tree: "Single or Multi-Module?"
|
|
@@ -303,13 +305,23 @@ system.registerModule("chat", chatModule.default);
|
|
|
303
305
|
|
|
304
306
|
## Cross-Module Events
|
|
305
307
|
|
|
306
|
-
Events are namespaced at the system level
|
|
308
|
+
Events are namespaced at the system level. **Prefer the typed events accessor** — it carries autocomplete and per-event payload typing.
|
|
307
309
|
|
|
308
310
|
```typescript
|
|
309
|
-
// Multi-module events
|
|
311
|
+
// Multi-module events — canonical form
|
|
310
312
|
system.events.auth.login({ token: "abc" });
|
|
311
313
|
system.events.cart.addItem({ id: "item-1", qty: 1 });
|
|
312
314
|
|
|
313
|
-
// dispatch()
|
|
315
|
+
// dispatch() is supported when you need to forward a programmatically-built
|
|
316
|
+
// event (e.g., replaying a serialized event). The string form
|
|
317
|
+
// (system.dispatch("login", payload)) does NOT exist — only the single-arg
|
|
318
|
+
// object form is valid.
|
|
314
319
|
system.dispatch({ type: "login", token: "abc" });
|
|
315
320
|
```
|
|
321
|
+
|
|
322
|
+
## See also
|
|
323
|
+
|
|
324
|
+
- [`core-patterns.md`](./core-patterns.md) — start here for the single-module shape before going namespaced
|
|
325
|
+
- [`system-api.md`](./system-api.md) — `createSystem({ modules: { ... } })` and the namespaced facts proxy
|
|
326
|
+
- [`constraints.md`](./constraints.md) — `crossModuleDeps` for constraints that fire on facts owned by another module
|
|
327
|
+
- [`naming.md`](./naming.md) — `facts.self.*` convention; dot-notation vs bracket-notation rules
|
package/core/naming.md
CHANGED
|
@@ -1,81 +1,123 @@
|
|
|
1
1
|
# Naming Conventions
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Covers all `@directive-run/*` packages — terminology, parameter names, return-style rules.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Directive uses precise vocabulary by design: each concept has one canonical name across every package, every language adapter, and every doc. The names are non-negotiable for code Directive ships, AND every term has a cross-paradigm alias so a developer coming from Redux / Zustand / XState / Jotai / React Query can find the right Directive concept on the first search.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
Resolver parameter names?
|
|
9
|
-
→ (req, context) req = requirement, NEVER "request"
|
|
10
|
-
→ NEVER (req, ctx) context is NEVER abbreviated
|
|
7
|
+
This file is both the rules and the bridge. Search for the term you already know; you'll land on the Directive equivalent.
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
→ "derivations" / derive NEVER "computed", "selectors", "getters"
|
|
14
|
-
→ system.derive.myValue NEVER system.computed.myValue
|
|
9
|
+
## Coming from another library? Start here
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
| If you call it… | (in this library) | Directive calls it… | Why the name |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| **state**, **store**, **atom**, **signal**, **observable** | Redux, Zustand, Jotai, Preact Signals, MobX | **facts** | "Facts about the world" — the system observes them. Same value-bag role as state, with a proxy that auto-tracks reads. |
|
|
14
|
+
| **selector**, **computed**, **getter**, **memo**, **derived store** | Redux, Vue/MobX, Zustand getters, Memoize, Svelte | **derivations** / `derive` | Auto-tracked computed values. No dep arrays. Directive's name aligns with "derived from facts." |
|
|
15
|
+
| **action**, **dispatch**, **reducer**, **command**, **intent** | Redux, Redux Toolkit, CQRS | **events** + **resolvers** | Events are the user-facing trigger; resolvers fulfill them. Reducers are split into the two halves Directive cares about. |
|
|
16
|
+
| **thunk**, **saga**, **effect**, **middleware**, **listener** | Redux Toolkit, redux-saga, redux-observable, NgRx | **resolvers** | Async logic that mutates state in response to a requirement. Same role as a thunk; declared once, run by the runtime. |
|
|
17
|
+
| **rule**, **condition**, **guard**, **trigger**, **policy** | XState guards, Cerbos, OPA, business-rule engines | **constraints** | Declarative "when X is true, Y must happen." Constraints emit requirements; resolvers fulfill them. |
|
|
18
|
+
| **request**, **query**, **side-effect spec** | React Query / TanStack Query queries, Apollo queries | **requirements** | The object a constraint emits. The runtime dedupes them, dispatches to resolvers, tracks status. |
|
|
19
|
+
| **subscription**, **listener**, **reaction**, **watcher** | Zustand subscribe, MobX reactions, Redux subscriptions | **effects** | Side-effects that run when watched facts change. Cleaner than `subscribe()` callbacks because effects auto-track. |
|
|
20
|
+
| **slice**, **feature**, **domain**, **bounded context** | Redux Toolkit slices, NgRx feature modules | **modules** | Encapsulates facts + derivations + constraints + resolvers + events for one bounded slice of the system. |
|
|
21
|
+
| **store**, **container**, **context**, **app state** | Redux Store, Pinia, React Context, NgRx Store | **system** | The runtime that wires modules together, evaluates constraints, dispatches resolvers, and exposes observation. |
|
|
22
|
+
| **state machine**, **statechart**, **xstate machine** | XState | (closest: **module** + **constraints**) | Directive isn't a state machine — but if your XState `states` model business rules (not UI flow), constraints + facts express the same logic declaratively with no transition functions. |
|
|
23
|
+
| **cache**, **query cache**, **stale-while-revalidate** | TanStack Query, SWR, Apollo cache | (closest: `@directive-run/query` + **constraints**) | Use `@directive-run/query` for the cache; constraints decide when refetches fire. Directive doesn't replace TanStack Query — it composes with it. |
|
|
24
|
+
| **agent**, **chain**, **graph**, **workflow** | LangChain, LangGraph, OpenAI Assistants | **agent** + **orchestrator** | `@directive-run/ai` keeps the names — agents are agents, orchestrators are orchestrators — but they run under the same constraint engine as the rest of Directive. |
|
|
25
|
+
| **guardrail**, **filter**, **safety check**, **moderation** | OpenAI moderation API, NVIDIA NeMo Guardrails | **guardrails** | Same name, same role; declarative input/output/tool-call checks. |
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
→ "constraints" NEVER "rules", "conditions", "triggers"
|
|
22
|
-
→ when() + require() NEVER if/then, trigger/action
|
|
27
|
+
If your term isn't in the table, search the canonical name in this doc's [Terminology quick reference](#terminology-quick-reference) — both directions are listed.
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
## Canonical terms (non-negotiable for code Directive ships)
|
|
30
|
+
|
|
31
|
+
Above is the bridge from your existing vocabulary. Below are the canonical names — the only ones that should appear in `@directive-run/*` source, tests, docs, and generated AI rules.
|
|
32
|
+
|
|
33
|
+
### `facts` — the value bag
|
|
34
|
+
|
|
35
|
+
Not state, not store, not atoms. **Facts.** Accessed at `system.facts.x`. Mutated through events and resolvers. Read through derivations, hooks, and `system.facts.$store`.
|
|
36
|
+
|
|
37
|
+
### `derivations` — auto-tracked computed values
|
|
38
|
+
|
|
39
|
+
Declared via `derive: { name: (facts) => … }`. Read at `system.derive.name`. The hook is `useDerived` (not `useComputed`, not `useSelector` — Directive's `useSelector` is a different, broader primitive that takes a selector function).
|
|
40
|
+
|
|
41
|
+
### `constraints` — when-then declarations
|
|
42
|
+
|
|
43
|
+
Declared via `constraints: { name: { when: …, require: … } }`. The `when` returns a boolean; the `require` returns a requirement.
|
|
44
|
+
|
|
45
|
+
### `resolvers` — requirement fulfillment
|
|
46
|
+
|
|
47
|
+
Declared via `resolvers: { name: { requirement: …, resolve: async (req, context) => … } }`. Mutate `context.facts`; never return data from `resolve`.
|
|
48
|
+
|
|
49
|
+
### `requirements` — what constraints emit
|
|
50
|
+
|
|
51
|
+
Tagged union `{ type: "FETCH_USER", … }`. Constraints emit them; the runtime dedupes by `type` + `key()`; resolvers fulfill them.
|
|
52
|
+
|
|
53
|
+
### `effects` — side effects from watched changes
|
|
54
|
+
|
|
55
|
+
Declared via `effects: { name: { run: (facts, prev) => … } }`. Run on relevant fact changes. The auto-tracking dependency model means no `deps` array unless you specifically need a partial dep set.
|
|
56
|
+
|
|
57
|
+
### `events` — typed mutation entry points
|
|
58
|
+
|
|
59
|
+
Declared via `events: { name: (facts, payload) => … }`. Called as `system.events.name(payload)`. The typed events accessor is the canonical way to dispatch — `system.dispatch({type, ...payload})` works but the typed accessor carries autocomplete.
|
|
60
|
+
|
|
61
|
+
### `module` — bounded slice
|
|
28
62
|
|
|
29
|
-
|
|
63
|
+
Created via `createModule(name, { schema, init, derive, effects, events, constraints, resolvers })`. Composes into systems.
|
|
64
|
+
|
|
65
|
+
### `system` — the runtime
|
|
66
|
+
|
|
67
|
+
Created via `createSystem({ module })` or `createSystem({ modules: { x, y } })`. Hosts the reconciliation loop.
|
|
68
|
+
|
|
69
|
+
## Parameter naming
|
|
30
70
|
|
|
31
71
|
### `req` = requirement (NOT request)
|
|
32
72
|
|
|
33
|
-
The `req` parameter in resolvers and constraint `key()` functions is short for **requirement**
|
|
73
|
+
The `req` parameter in resolvers and constraint `key()` functions is short for **requirement** — the object emitted by a constraint's `require` property.
|
|
34
74
|
|
|
35
75
|
```typescript
|
|
36
|
-
// CORRECT
|
|
76
|
+
// CORRECT — req is a requirement
|
|
37
77
|
resolvers: {
|
|
38
78
|
fetchUser: {
|
|
39
79
|
requirement: "FETCH_USER",
|
|
40
80
|
key: (req) => `fetch-${req.userId}`,
|
|
41
81
|
resolve: async (req, context) => {
|
|
42
82
|
// req.type === "FETCH_USER"
|
|
43
|
-
// req.userId from the requirement payload
|
|
44
83
|
const user = await fetchUser(req.userId);
|
|
45
84
|
context.facts.user = user;
|
|
46
85
|
},
|
|
47
86
|
},
|
|
48
87
|
},
|
|
49
88
|
|
|
50
|
-
// WRONG
|
|
89
|
+
// WRONG — never "request" or "r"
|
|
51
90
|
resolve: async (request, context) => { /* ... */ },
|
|
52
91
|
resolve: async (r, context) => { /* ... */ },
|
|
53
92
|
```
|
|
54
93
|
|
|
55
|
-
### `context` is
|
|
94
|
+
### `context` is never abbreviated
|
|
56
95
|
|
|
57
96
|
```typescript
|
|
58
97
|
// CORRECT
|
|
59
98
|
resolve: async (req, context) => {
|
|
60
99
|
context.facts.status = "loaded";
|
|
61
|
-
context.signal;
|
|
62
|
-
context.snapshot();
|
|
100
|
+
context.signal; // AbortSignal
|
|
101
|
+
context.snapshot(); // facts snapshot
|
|
63
102
|
},
|
|
64
103
|
|
|
65
|
-
// WRONG
|
|
104
|
+
// WRONG — never abbreviate to ctx
|
|
66
105
|
resolve: async (req, ctx) => { /* ... */ },
|
|
67
106
|
```
|
|
68
107
|
|
|
69
|
-
## Return
|
|
108
|
+
## Return style
|
|
70
109
|
|
|
71
|
-
###
|
|
110
|
+
### Braces for `if` blocks with `return` (NOT for arrow expressions)
|
|
72
111
|
|
|
73
|
-
|
|
112
|
+
The brace rule applies to control-flow blocks — not to arrow-expression bodies.
|
|
113
|
+
|
|
114
|
+
**Arrow expressions** (single-line derivations, predicates, computed requirements): the concise form is preferred. No braces, no explicit `return`.
|
|
74
115
|
|
|
75
116
|
```typescript
|
|
76
|
-
//
|
|
117
|
+
// CORRECT — single-line arrow expressions stay concise
|
|
77
118
|
derive: {
|
|
78
119
|
isReady: (facts) => facts.phase === "ready",
|
|
120
|
+
greeting: (facts) => `Hi, ${facts.name}!`,
|
|
79
121
|
},
|
|
80
122
|
|
|
81
123
|
constraints: {
|
|
@@ -84,25 +126,26 @@ constraints: {
|
|
|
84
126
|
require: { type: "PROCESS" },
|
|
85
127
|
},
|
|
86
128
|
},
|
|
129
|
+
```
|
|
87
130
|
|
|
88
|
-
|
|
89
|
-
// The brace rule applies to if/return blocks:
|
|
131
|
+
**Control-flow statements** (`if`, `for`, `while`): braces required, even for single-line bodies. Single-line `if (x) return y` shapes are never used.
|
|
90
132
|
|
|
91
|
-
|
|
133
|
+
```typescript
|
|
134
|
+
// WRONG — single-line if return
|
|
92
135
|
if (facts.user) return "ready";
|
|
93
136
|
|
|
94
|
-
// CORRECT
|
|
137
|
+
// CORRECT — always wrap the body in braces
|
|
95
138
|
if (facts.user) {
|
|
96
139
|
return "ready";
|
|
97
140
|
}
|
|
98
141
|
```
|
|
99
142
|
|
|
100
|
-
### Blank
|
|
143
|
+
### Blank line before `return`
|
|
101
144
|
|
|
102
145
|
Add a blank line before `return` when there is code above it. Skip the blank line when `return` is the first statement in a block.
|
|
103
146
|
|
|
104
147
|
```typescript
|
|
105
|
-
// CORRECT
|
|
148
|
+
// CORRECT — blank line before return when code precedes it
|
|
106
149
|
function getStatus(facts) {
|
|
107
150
|
const phase = facts.phase;
|
|
108
151
|
const hasUser = facts.user !== null;
|
|
@@ -110,12 +153,12 @@ function getStatus(facts) {
|
|
|
110
153
|
return phase === "ready" && hasUser;
|
|
111
154
|
}
|
|
112
155
|
|
|
113
|
-
// CORRECT
|
|
156
|
+
// CORRECT — no blank line when return is first statement
|
|
114
157
|
function isReady(facts) {
|
|
115
158
|
return facts.phase === "ready";
|
|
116
159
|
}
|
|
117
160
|
|
|
118
|
-
// CORRECT
|
|
161
|
+
// CORRECT — blank line after brace-style return block
|
|
119
162
|
function process(facts) {
|
|
120
163
|
if (!facts.ready) {
|
|
121
164
|
return null;
|
|
@@ -125,26 +168,20 @@ function process(facts) {
|
|
|
125
168
|
|
|
126
169
|
return result;
|
|
127
170
|
}
|
|
128
|
-
|
|
129
|
-
// WRONG – no blank line before return after code
|
|
130
|
-
function getStatus(facts) {
|
|
131
|
-
const phase = facts.phase;
|
|
132
|
-
return phase === "ready"; // Missing blank line
|
|
133
|
-
}
|
|
134
171
|
```
|
|
135
172
|
|
|
136
|
-
## Multi-
|
|
173
|
+
## Multi-line code formatting
|
|
137
174
|
|
|
138
175
|
Never put properties or statements on a single line inside braces. Always expand to one item per line with proper indentation. This applies everywhere: schema definitions, init functions, events, effects, requirement types, and any other object or block.
|
|
139
176
|
|
|
140
177
|
```typescript
|
|
141
|
-
// WRONG
|
|
178
|
+
// WRONG — properties crammed on one line
|
|
142
179
|
schema: {
|
|
143
180
|
facts: { phase: t.string(), count: t.number() },
|
|
144
181
|
requirements: { FETCH_USER: { id: t.string() }, RESET: {} },
|
|
145
182
|
},
|
|
146
183
|
|
|
147
|
-
// CORRECT
|
|
184
|
+
// CORRECT — one property per line, always expanded
|
|
148
185
|
schema: {
|
|
149
186
|
facts: {
|
|
150
187
|
phase: t.string(),
|
|
@@ -158,41 +195,31 @@ schema: {
|
|
|
158
195
|
},
|
|
159
196
|
},
|
|
160
197
|
|
|
161
|
-
// WRONG
|
|
198
|
+
// WRONG — statements crammed on one line
|
|
162
199
|
init: (facts) => { facts.phase = "idle"; facts.count = 0; },
|
|
163
200
|
|
|
164
|
-
// CORRECT
|
|
201
|
+
// CORRECT — one statement per line
|
|
165
202
|
init: (facts) => {
|
|
166
203
|
facts.phase = "idle";
|
|
167
204
|
facts.count = 0;
|
|
168
205
|
},
|
|
169
|
-
|
|
170
|
-
// WRONG
|
|
171
|
-
events: { reset: (facts) => { facts.count = 0; facts.phase = "idle"; } },
|
|
172
|
-
|
|
173
|
-
// CORRECT
|
|
174
|
-
events: {
|
|
175
|
-
reset: (facts) => {
|
|
176
|
-
facts.count = 0;
|
|
177
|
-
facts.phase = "idle";
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
206
|
```
|
|
181
207
|
|
|
182
208
|
Single-expression arrows (no braces) are fine on one line. Empty objects `{}` are fine inline.
|
|
209
|
+
|
|
183
210
|
```typescript
|
|
184
|
-
// OK
|
|
211
|
+
// OK — single expression, no braces
|
|
185
212
|
derive: {
|
|
186
213
|
isReady: (facts) => facts.phase === "ready",
|
|
187
214
|
},
|
|
188
215
|
|
|
189
|
-
// OK
|
|
216
|
+
// OK — empty object
|
|
190
217
|
RESET: {},
|
|
191
218
|
```
|
|
192
219
|
|
|
193
|
-
## Multi-
|
|
220
|
+
## Multi-module naming
|
|
194
221
|
|
|
195
|
-
### `facts.self.*` for
|
|
222
|
+
### `facts.self.*` for own module
|
|
196
223
|
|
|
197
224
|
In multi-module systems, constraints, effects, and derivations with `crossModuleDeps` receive namespaced facts. Own module facts are always at `facts.self.*`.
|
|
198
225
|
|
|
@@ -205,7 +232,7 @@ constraints: {
|
|
|
205
232
|
},
|
|
206
233
|
},
|
|
207
234
|
|
|
208
|
-
// WRONG
|
|
235
|
+
// WRONG — bare facts.* in multi-module context
|
|
209
236
|
constraints: {
|
|
210
237
|
loadWhenAuth: {
|
|
211
238
|
when: (facts) => facts.isAuthenticated && !facts.loaded,
|
|
@@ -214,48 +241,48 @@ constraints: {
|
|
|
214
241
|
},
|
|
215
242
|
```
|
|
216
243
|
|
|
217
|
-
### System-
|
|
244
|
+
### System-level access uses dot notation
|
|
218
245
|
|
|
219
246
|
```typescript
|
|
220
|
-
// CORRECT
|
|
247
|
+
// CORRECT — dot notation through namespace proxy
|
|
221
248
|
system.facts.auth.token;
|
|
222
249
|
system.facts.cart.items;
|
|
223
250
|
system.derive.auth.isLoggedIn;
|
|
224
251
|
system.events.auth.login({ token: "..." });
|
|
225
252
|
|
|
226
|
-
// WRONG
|
|
253
|
+
// WRONG — bracket notation with internal separator
|
|
227
254
|
system.facts["auth::token"];
|
|
228
255
|
system.facts["auth_token"];
|
|
229
256
|
```
|
|
230
257
|
|
|
231
|
-
## Type
|
|
258
|
+
## Type casting rules
|
|
232
259
|
|
|
233
|
-
### Never
|
|
260
|
+
### Never cast when reading
|
|
234
261
|
|
|
235
262
|
The schema provides all types. Do not add `as` casts when reading facts or derivations from the system.
|
|
236
263
|
|
|
237
264
|
```typescript
|
|
238
|
-
// CORRECT
|
|
265
|
+
// CORRECT — schema provides the type
|
|
239
266
|
const profile = system.facts.profile;
|
|
240
267
|
const isReady = system.derive.isReady;
|
|
241
268
|
|
|
242
|
-
// WRONG
|
|
269
|
+
// WRONG — unnecessary cast
|
|
243
270
|
const profile = system.facts.profile as UserProfile;
|
|
244
271
|
const isReady = system.derive.isReady as boolean;
|
|
245
272
|
```
|
|
246
273
|
|
|
247
|
-
### Cast
|
|
274
|
+
### Cast only in schema definition
|
|
248
275
|
|
|
249
|
-
Type assertions are only valid in schema definition using the `{} as {}` pattern:
|
|
276
|
+
Type assertions are only valid in schema definition using the `{} as {}` pattern, or via the `t.*` builders (preferred):
|
|
250
277
|
|
|
251
278
|
```typescript
|
|
252
|
-
// CORRECT
|
|
279
|
+
// CORRECT — cast in schema definition
|
|
253
280
|
schema: {
|
|
254
281
|
facts: {} as { profile: UserProfile; settings: AppSettings },
|
|
255
282
|
derivations: {} as { displayName: string },
|
|
256
283
|
},
|
|
257
284
|
|
|
258
|
-
//
|
|
285
|
+
// PREFERRED — t.* builders
|
|
259
286
|
schema: {
|
|
260
287
|
facts: {
|
|
261
288
|
profile: t.object<UserProfile>(),
|
|
@@ -267,17 +294,28 @@ schema: {
|
|
|
267
294
|
},
|
|
268
295
|
```
|
|
269
296
|
|
|
270
|
-
## Terminology
|
|
297
|
+
## Terminology quick reference
|
|
298
|
+
|
|
299
|
+
Two-way lookup. Search the term you know.
|
|
271
300
|
|
|
272
|
-
| Directive
|
|
301
|
+
| Directive term | Cross-paradigm aliases (use these to search) |
|
|
273
302
|
|---|---|
|
|
274
|
-
| facts | state, store, atoms, signals |
|
|
275
|
-
| derivations / derive | computed, selectors, getters, memos |
|
|
276
|
-
| constraints | rules, conditions, triggers, guards |
|
|
277
|
-
| resolvers | handlers, actions, reducers, sagas |
|
|
278
|
-
| requirements | requests, commands, intents |
|
|
279
|
-
| effects | watchers, subscriptions, reactions |
|
|
280
|
-
|
|
|
281
|
-
|
|
|
282
|
-
|
|
|
283
|
-
|
|
|
303
|
+
| **facts** | state, store, atoms, signals, observables, model |
|
|
304
|
+
| **derivations** / `derive` | computed, selectors, getters, memos, derived stores, $: blocks (Svelte) |
|
|
305
|
+
| **constraints** | rules, conditions, triggers, guards, policies, invariants |
|
|
306
|
+
| **resolvers** | handlers, actions, reducers, sagas, thunks, effects (Redux), middleware, listeners |
|
|
307
|
+
| **requirements** | requests, commands, intents, queries (TanStack), tasks |
|
|
308
|
+
| **effects** | watchers, subscriptions, reactions, autoruns, listeners |
|
|
309
|
+
| **events** | actions, intents, commands, methods (Pinia), signals (NgRx) |
|
|
310
|
+
| **module** | slice, feature, domain, bounded context, NgRx feature module |
|
|
311
|
+
| **system** | store, container, context, app state, app shell |
|
|
312
|
+
| **`req`** (parameter) | request, r, requirement (spelled out) |
|
|
313
|
+
| **`context`** (parameter) | ctx, c, resolverContext |
|
|
314
|
+
|
|
315
|
+
The forbidden direction is also non-negotiable: in code Directive ships, the columns reverse. Do NOT use `state`, `store`, `selectors`, `computed`, `actions`, `reducers`, `subscriptions`, `slices`, `request`, or `ctx` in `@directive-run/*` source. The alias map is for retrieval — the canonical names are for code.
|
|
316
|
+
|
|
317
|
+
## See also
|
|
318
|
+
|
|
319
|
+
- [`core-patterns.md`](./core-patterns.md) — the actual code shapes every naming rule here applies to
|
|
320
|
+
- [`multi-module.md`](./multi-module.md) — `facts.self.*` convention and dot-notation rules
|
|
321
|
+
- [`anti-patterns.md`](./anti-patterns.md) — naming-shaped mistakes (`request` for `req`, `ctx` for `context`, hallucinated TS schema types)
|
package/core/plugins.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Plugins
|
|
2
2
|
|
|
3
|
+
> Covers `@directive-run/core/plugins` — plugin authoring + built-ins (logging, devtools, persistence, audit-ledger, observability, OTLP).
|
|
4
|
+
|
|
3
5
|
Plugins extend Directive systems with cross-cutting functionality like logging, persistence, devtools, and resilience patterns.
|
|
4
6
|
|
|
5
7
|
## Decision Tree: "Which plugin do I need?"
|
|
@@ -352,3 +354,8 @@ persistencePlugin({
|
|
|
352
354
|
exclude: ["authToken", "isLoading", "error"],
|
|
353
355
|
})
|
|
354
356
|
```
|
|
357
|
+
|
|
358
|
+
## See also
|
|
359
|
+
|
|
360
|
+
- [`system-api.md`](./system-api.md) — `plugins:` option on `createSystem` and the lifecycle hooks plugins observe
|
|
361
|
+
- [`error-boundaries.md`](./error-boundaries.md) — `createCircuitBreaker` and the resilience plugins that pair with error boundaries
|