@directive-run/claude-plugin 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/.claude-plugin/plugin.json +6 -0
- package/LICENSE +26 -0
- package/README.md +113 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +66 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
- package/skills/README.md +9 -0
- package/skills/building-ai-agents/SKILL.md +414 -0
- package/skills/building-ai-agents/ai-adapters.md +257 -0
- package/skills/building-ai-agents/ai-agents-streaming.md +307 -0
- package/skills/building-ai-agents/ai-communication.md +304 -0
- package/skills/building-ai-agents/api-skeleton.md +5 -0
- package/skills/building-ai-agents/examples.md +849 -0
- package/skills/building-ai-orchestrators/SKILL.md +401 -0
- package/skills/building-ai-orchestrators/ai-multi-agent.md +375 -0
- package/skills/building-ai-orchestrators/ai-orchestrator.md +466 -0
- package/skills/building-ai-orchestrators/ai-tasks.md +268 -0
- package/skills/building-ai-orchestrators/api-skeleton.md +5 -0
- package/skills/building-ai-orchestrators/examples.md +1609 -0
- package/skills/building-directive-systems/SKILL.md +499 -0
- package/skills/building-directive-systems/api-skeleton.md +5 -0
- package/skills/building-directive-systems/examples.md +4149 -0
- package/skills/building-directive-systems/multi-module.md +327 -0
- package/skills/building-directive-systems/plugins.md +361 -0
- package/skills/building-directive-systems/react-adapter.md +376 -0
- package/skills/building-directive-systems/system-api.md +463 -0
- package/skills/getting-started-with-directive/SKILL.md +198 -0
- package/skills/getting-started-with-directive/api-skeleton.md +5 -0
- package/skills/getting-started-with-directive/core-patterns.md +240 -0
- package/skills/getting-started-with-directive/examples.md +87 -0
- package/skills/getting-started-with-directive/sitemap.md +202 -0
- package/skills/hardening-ai-systems/SKILL.md +377 -0
- package/skills/hardening-ai-systems/ai-budget-resilience.md +408 -0
- package/skills/hardening-ai-systems/ai-guardrails-memory.md +370 -0
- package/skills/hardening-ai-systems/ai-security.md +400 -0
- package/skills/hardening-ai-systems/api-skeleton.md +5 -0
- package/skills/hardening-ai-systems/examples.md +628 -0
- package/skills/migrating-to-directive/SKILL.md +249 -0
- package/skills/migrating-to-directive/anti-patterns.md +382 -0
- package/skills/migrating-to-directive/api-skeleton.md +5 -0
- package/skills/migrating-to-directive/core-patterns.md +240 -0
- package/skills/migrating-to-directive/examples.md +511 -0
- package/skills/migrating-to-directive/schema-types.md +273 -0
- package/skills/reviewing-directive-code/SKILL.md +232 -0
- package/skills/reviewing-directive-code/anti-patterns.md +382 -0
- package/skills/reviewing-directive-code/api-skeleton.md +5 -0
- package/skills/reviewing-directive-code/core-patterns.md +240 -0
- package/skills/reviewing-directive-code/examples.md +463 -0
- package/skills/reviewing-directive-code/naming.md +321 -0
- package/skills/scaffolding-directive-modules/SKILL.md +218 -0
- package/skills/scaffolding-directive-modules/api-skeleton.md +5 -0
- package/skills/scaffolding-directive-modules/core-patterns.md +240 -0
- package/skills/scaffolding-directive-modules/examples.md +1386 -0
- package/skills/scaffolding-directive-modules/naming.md +321 -0
- package/skills/scaffolding-directive-modules/schema-types.md +273 -0
- package/skills/testing-ai-systems/SKILL.md +398 -0
- package/skills/testing-ai-systems/ai-debug-observability.md +329 -0
- package/skills/testing-ai-systems/ai-mcp-rag.md +293 -0
- package/skills/testing-ai-systems/ai-testing-evals.md +485 -0
- package/skills/testing-ai-systems/api-skeleton.md +5 -0
- package/skills/testing-ai-systems/examples.md +1261 -0
- package/skills/testing-directive-code/SKILL.md +479 -0
- package/skills/testing-directive-code/api-skeleton.md +5 -0
- package/skills/testing-directive-code/examples.md +156 -0
- package/skills/testing-directive-code/history.md +351 -0
- package/skills/testing-directive-code/testing.md +412 -0
- package/skills/writing-directive-constraints/SKILL.md +486 -0
- package/skills/writing-directive-constraints/api-skeleton.md +5 -0
- package/skills/writing-directive-constraints/constraints.md +274 -0
- package/skills/writing-directive-constraints/error-boundaries.md +330 -0
- package/skills/writing-directive-constraints/examples.md +1361 -0
- package/skills/writing-directive-constraints/resolvers.md +367 -0
- package/skills/writing-directive-modules/SKILL.md +342 -0
- package/skills/writing-directive-modules/anti-patterns.md +382 -0
- package/skills/writing-directive-modules/api-skeleton.md +5 -0
- package/skills/writing-directive-modules/core-patterns.md +240 -0
- package/skills/writing-directive-modules/examples.md +1639 -0
- package/skills/writing-directive-modules/naming.md +321 -0
- package/skills/writing-directive-modules/schema-types.md +273 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# Naming Conventions
|
|
2
|
+
|
|
3
|
+
> Covers all `@directive-run/*` packages — terminology, parameter names, return-style rules.
|
|
4
|
+
|
|
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
|
+
|
|
7
|
+
This file is both the rules and the bridge. Search for the term you already know; you'll land on the Directive equivalent.
|
|
8
|
+
|
|
9
|
+
## Coming from another library? Start here
|
|
10
|
+
|
|
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. |
|
|
26
|
+
|
|
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.
|
|
28
|
+
|
|
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
|
|
62
|
+
|
|
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
|
|
70
|
+
|
|
71
|
+
### `req` = requirement (NOT request)
|
|
72
|
+
|
|
73
|
+
The `req` parameter in resolvers and constraint `key()` functions is short for **requirement** — the object emitted by a constraint's `require` property.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// CORRECT — req is a requirement
|
|
77
|
+
resolvers: {
|
|
78
|
+
fetchUser: {
|
|
79
|
+
requirement: "FETCH_USER",
|
|
80
|
+
key: (req) => `fetch-${req.userId}`,
|
|
81
|
+
resolve: async (req, context) => {
|
|
82
|
+
// req.type === "FETCH_USER"
|
|
83
|
+
const user = await fetchUser(req.userId);
|
|
84
|
+
context.facts.user = user;
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
// WRONG — never "request" or "r"
|
|
90
|
+
resolve: async (request, context) => { /* ... */ },
|
|
91
|
+
resolve: async (r, context) => { /* ... */ },
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### `context` is never abbreviated
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// CORRECT
|
|
98
|
+
resolve: async (req, context) => {
|
|
99
|
+
context.facts.status = "loaded";
|
|
100
|
+
context.signal; // AbortSignal
|
|
101
|
+
context.snapshot(); // facts snapshot
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
// WRONG — never abbreviate to ctx
|
|
105
|
+
resolve: async (req, ctx) => { /* ... */ },
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Return style
|
|
109
|
+
|
|
110
|
+
### Braces for `if` blocks with `return` (NOT for arrow expressions)
|
|
111
|
+
|
|
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`.
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
// CORRECT — single-line arrow expressions stay concise
|
|
118
|
+
derive: {
|
|
119
|
+
isReady: (facts) => facts.phase === "ready",
|
|
120
|
+
greeting: (facts) => `Hi, ${facts.name}!`,
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
constraints: {
|
|
124
|
+
check: {
|
|
125
|
+
when: (facts) => facts.count > 10,
|
|
126
|
+
require: { type: "PROCESS" },
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Control-flow statements** (`if`, `for`, `while`): braces required, even for single-line bodies. Single-line `if (x) return y` shapes are never used.
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// WRONG — single-line if return
|
|
135
|
+
if (facts.user) return "ready";
|
|
136
|
+
|
|
137
|
+
// CORRECT — always wrap the body in braces
|
|
138
|
+
if (facts.user) {
|
|
139
|
+
return "ready";
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Blank line before `return`
|
|
144
|
+
|
|
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.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// CORRECT — blank line before return when code precedes it
|
|
149
|
+
function getStatus(facts) {
|
|
150
|
+
const phase = facts.phase;
|
|
151
|
+
const hasUser = facts.user !== null;
|
|
152
|
+
|
|
153
|
+
return phase === "ready" && hasUser;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// CORRECT — no blank line when return is first statement
|
|
157
|
+
function isReady(facts) {
|
|
158
|
+
return facts.phase === "ready";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// CORRECT — blank line after brace-style return block
|
|
162
|
+
function process(facts) {
|
|
163
|
+
if (!facts.ready) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const result = computeResult(facts);
|
|
168
|
+
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Multi-line code formatting
|
|
174
|
+
|
|
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.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// WRONG — properties crammed on one line
|
|
179
|
+
schema: {
|
|
180
|
+
facts: { phase: t.string(), count: t.number() },
|
|
181
|
+
requirements: { FETCH_USER: { id: t.string() }, RESET: {} },
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
// CORRECT — one property per line, always expanded
|
|
185
|
+
schema: {
|
|
186
|
+
facts: {
|
|
187
|
+
phase: t.string(),
|
|
188
|
+
count: t.number(),
|
|
189
|
+
},
|
|
190
|
+
requirements: {
|
|
191
|
+
FETCH_USER: {
|
|
192
|
+
id: t.string(),
|
|
193
|
+
},
|
|
194
|
+
RESET: {},
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
// WRONG — statements crammed on one line
|
|
199
|
+
init: (facts) => { facts.phase = "idle"; facts.count = 0; },
|
|
200
|
+
|
|
201
|
+
// CORRECT — one statement per line
|
|
202
|
+
init: (facts) => {
|
|
203
|
+
facts.phase = "idle";
|
|
204
|
+
facts.count = 0;
|
|
205
|
+
},
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Single-expression arrows (no braces) are fine on one line. Empty objects `{}` are fine inline.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
// OK — single expression, no braces
|
|
212
|
+
derive: {
|
|
213
|
+
isReady: (facts) => facts.phase === "ready",
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
// OK — empty object
|
|
217
|
+
RESET: {},
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Multi-module naming
|
|
221
|
+
|
|
222
|
+
### `facts.self.*` for own module
|
|
223
|
+
|
|
224
|
+
In multi-module systems, constraints, effects, and derivations with `crossModuleDeps` receive namespaced facts. Own module facts are always at `facts.self.*`.
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
// CORRECT
|
|
228
|
+
constraints: {
|
|
229
|
+
loadWhenAuth: {
|
|
230
|
+
when: (facts) => facts.auth.isAuthenticated && !facts.self.loaded,
|
|
231
|
+
require: { type: "LOAD_DATA" },
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
// WRONG — bare facts.* in multi-module context
|
|
236
|
+
constraints: {
|
|
237
|
+
loadWhenAuth: {
|
|
238
|
+
when: (facts) => facts.isAuthenticated && !facts.loaded,
|
|
239
|
+
require: { type: "LOAD_DATA" },
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### System-level access uses dot notation
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
// CORRECT — dot notation through namespace proxy
|
|
248
|
+
system.facts.auth.token;
|
|
249
|
+
system.facts.cart.items;
|
|
250
|
+
system.derive.auth.isLoggedIn;
|
|
251
|
+
system.events.auth.login({ token: "..." });
|
|
252
|
+
|
|
253
|
+
// WRONG — bracket notation with internal separator
|
|
254
|
+
system.facts["auth::token"];
|
|
255
|
+
system.facts["auth_token"];
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Type casting rules
|
|
259
|
+
|
|
260
|
+
### Never cast when reading
|
|
261
|
+
|
|
262
|
+
The schema provides all types. Do not add `as` casts when reading facts or derivations from the system.
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
// CORRECT — schema provides the type
|
|
266
|
+
const profile = system.facts.profile;
|
|
267
|
+
const isReady = system.derive.isReady;
|
|
268
|
+
|
|
269
|
+
// WRONG — unnecessary cast
|
|
270
|
+
const profile = system.facts.profile as UserProfile;
|
|
271
|
+
const isReady = system.derive.isReady as boolean;
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Cast only in schema definition
|
|
275
|
+
|
|
276
|
+
Type assertions are only valid in schema definition using the `{} as {}` pattern, or via the `t.*` builders (preferred):
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// CORRECT — cast in schema definition
|
|
280
|
+
schema: {
|
|
281
|
+
facts: {} as { profile: UserProfile; settings: AppSettings },
|
|
282
|
+
derivations: {} as { displayName: string },
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
// PREFERRED — t.* builders
|
|
286
|
+
schema: {
|
|
287
|
+
facts: {
|
|
288
|
+
profile: t.object<UserProfile>(),
|
|
289
|
+
settings: t.object<AppSettings>(),
|
|
290
|
+
},
|
|
291
|
+
derivations: {
|
|
292
|
+
displayName: t.string(),
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Terminology quick reference
|
|
298
|
+
|
|
299
|
+
Two-way lookup. Search the term you know.
|
|
300
|
+
|
|
301
|
+
| Directive term | Cross-paradigm aliases (use these to search) |
|
|
302
|
+
|---|---|
|
|
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)
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# Schema Types
|
|
2
|
+
|
|
3
|
+
> Covers `@directive-run/core` — schema builders (`t.string` / `t.number` / `t.array` / etc.) and inference helpers.
|
|
4
|
+
|
|
5
|
+
The `t.*()` builders define fact types in module schemas. They provide runtime validation in dev mode and full TypeScript inference.
|
|
6
|
+
|
|
7
|
+
## Decision Tree: "Which type builder do I use?"
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
What kind of value?
|
|
11
|
+
├── String → t.string() or t.string<"a" | "b">() for literal unions
|
|
12
|
+
├── Number → t.number() with optional .min() / .max()
|
|
13
|
+
├── Boolean → t.boolean()
|
|
14
|
+
├── Array → t.array<ItemType>() with optional .of() / .nonEmpty()
|
|
15
|
+
├── Object/Record → t.object<Shape>()
|
|
16
|
+
├── Fixed set of string values → t.enum("a", "b", "c")
|
|
17
|
+
├── Exact value → t.literal(42) or t.literal("active")
|
|
18
|
+
├── Nullable → t.nullable(t.string())
|
|
19
|
+
├── Optional → t.optional(t.number())
|
|
20
|
+
├── Union → t.union(t.string(), t.number())
|
|
21
|
+
├── Map/Set → t.object<Map<K,V>>() or t.object<Set<T>>()
|
|
22
|
+
├── Date → t.object<Date>() or t.number() for timestamps
|
|
23
|
+
├── Unknown/any → t.object<unknown>()
|
|
24
|
+
└── Any type + metadata → t.string().meta({ label: "Email", tags: ["pii"] })
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
All builders support `.meta({ label, description, category, color, tags })` for devtools/AI annotations. Chains with other methods: `t.string().meta({...}).nullable()`.
|
|
28
|
+
|
|
29
|
+
## Primitive Types
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { createModule, t } from "@directive-run/core";
|
|
33
|
+
|
|
34
|
+
const myModule = createModule("example", {
|
|
35
|
+
schema: {
|
|
36
|
+
facts: {
|
|
37
|
+
// Basic string
|
|
38
|
+
name: t.string(),
|
|
39
|
+
|
|
40
|
+
// String literal union – full type safety
|
|
41
|
+
phase: t.string<"idle" | "loading" | "done">(),
|
|
42
|
+
|
|
43
|
+
// Number with validation
|
|
44
|
+
count: t.number(),
|
|
45
|
+
age: t.number().min(0).max(150),
|
|
46
|
+
|
|
47
|
+
// Boolean
|
|
48
|
+
isActive: t.boolean(),
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
init: (facts) => {
|
|
52
|
+
facts.name = "";
|
|
53
|
+
facts.phase = "idle";
|
|
54
|
+
facts.count = 0;
|
|
55
|
+
facts.age = 25;
|
|
56
|
+
facts.isActive = false;
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Complex Types
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
schema: {
|
|
65
|
+
facts: {
|
|
66
|
+
// Object with shape
|
|
67
|
+
user: t.object<{ id: string; name: string; email: string }>(),
|
|
68
|
+
|
|
69
|
+
// Nullable object
|
|
70
|
+
profile: t.object<{ bio: string; avatar: string } | null>(),
|
|
71
|
+
|
|
72
|
+
// Array
|
|
73
|
+
tags: t.array<string>(),
|
|
74
|
+
items: t.array<{ id: string; label: string }>().nonEmpty(),
|
|
75
|
+
|
|
76
|
+
// Record (key-value map)
|
|
77
|
+
scores: t.object<Record<string, number>>(),
|
|
78
|
+
|
|
79
|
+
// Nested complex type
|
|
80
|
+
config: t.object<{
|
|
81
|
+
theme: "light" | "dark";
|
|
82
|
+
notifications: { email: boolean; push: boolean };
|
|
83
|
+
}>(),
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Enum and Literal
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
schema: {
|
|
92
|
+
facts: {
|
|
93
|
+
// Enum – string literal union from values
|
|
94
|
+
status: t.enum("pending", "active", "archived"),
|
|
95
|
+
// TypeScript type: "pending" | "active" | "archived"
|
|
96
|
+
|
|
97
|
+
// Literal – exact match
|
|
98
|
+
version: t.literal(2),
|
|
99
|
+
mode: t.literal("strict"),
|
|
100
|
+
enabled: t.literal(true),
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Nullable and Optional
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
schema: {
|
|
109
|
+
facts: {
|
|
110
|
+
// Nullable – T | null
|
|
111
|
+
selectedItem: t.nullable(t.string()),
|
|
112
|
+
|
|
113
|
+
// Optional – T | undefined
|
|
114
|
+
nickname: t.optional(t.string()),
|
|
115
|
+
|
|
116
|
+
// Union – combine types
|
|
117
|
+
result: t.union(t.string(), t.number()),
|
|
118
|
+
|
|
119
|
+
// Nullable via object generic (also valid)
|
|
120
|
+
user: t.object<{ id: string } | null>(),
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Chainable Methods (Available on All Types)
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
schema: {
|
|
129
|
+
facts: {
|
|
130
|
+
// Default value – used if init doesn't set it
|
|
131
|
+
theme: t.string<"light" | "dark">().default("light"),
|
|
132
|
+
|
|
133
|
+
// Custom validation – runs in dev mode
|
|
134
|
+
email: t.string().validate((val) => val.includes("@")),
|
|
135
|
+
|
|
136
|
+
// Transform on set – runs when fact is mutated
|
|
137
|
+
slug: t.string().transform((val) => val.toLowerCase().replace(/\s+/g, "-")),
|
|
138
|
+
|
|
139
|
+
// Branded type – nominal typing
|
|
140
|
+
userId: t.string().brand<"UserId">(),
|
|
141
|
+
|
|
142
|
+
// Description – for docs and devtools
|
|
143
|
+
retryCount: t.number().describe("Number of failed attempts"),
|
|
144
|
+
|
|
145
|
+
// Refinement – predicate with error message
|
|
146
|
+
port: t.number().refine(
|
|
147
|
+
(n) => n >= 1 && n <= 65535,
|
|
148
|
+
"Port must be between 1 and 65535",
|
|
149
|
+
),
|
|
150
|
+
|
|
151
|
+
// Chaining – combine multiple modifiers
|
|
152
|
+
score: t.number()
|
|
153
|
+
.min(0)
|
|
154
|
+
.max(100)
|
|
155
|
+
.default(0)
|
|
156
|
+
.describe("Player score"),
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Array-Specific Methods
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
schema: {
|
|
165
|
+
facts: {
|
|
166
|
+
// Basic array
|
|
167
|
+
items: t.array<string>(),
|
|
168
|
+
|
|
169
|
+
// Non-empty validation
|
|
170
|
+
requiredItems: t.array<string>().nonEmpty(),
|
|
171
|
+
|
|
172
|
+
// Length constraints
|
|
173
|
+
topFive: t.array<string>().maxLength(5),
|
|
174
|
+
atLeastThree: t.array<number>().minLength(3),
|
|
175
|
+
|
|
176
|
+
// Combined
|
|
177
|
+
tags: t.array<string>().nonEmpty().maxLength(10),
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Object-Specific Methods
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
schema: {
|
|
186
|
+
facts: {
|
|
187
|
+
// Non-null assertion
|
|
188
|
+
config: t.object<{ url: string }>().nonNull(),
|
|
189
|
+
|
|
190
|
+
// Required keys validation
|
|
191
|
+
settings: t.object<Record<string, unknown>>().hasKeys("apiUrl", "timeout"),
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Types That DO NOT Exist
|
|
197
|
+
|
|
198
|
+
These are common AI hallucinations. Do not use them.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
// WRONG – t.map() does not exist
|
|
202
|
+
items: t.map<string, number>(),
|
|
203
|
+
// CORRECT
|
|
204
|
+
items: t.object<Map<string, number>>(),
|
|
205
|
+
|
|
206
|
+
// WRONG – t.set() does not exist
|
|
207
|
+
tags: t.set<string>(),
|
|
208
|
+
// CORRECT
|
|
209
|
+
tags: t.object<Set<string>>(),
|
|
210
|
+
|
|
211
|
+
// WRONG – t.date() does not exist
|
|
212
|
+
createdAt: t.date(),
|
|
213
|
+
// CORRECT
|
|
214
|
+
createdAt: t.object<Date>(),
|
|
215
|
+
// OR use timestamps
|
|
216
|
+
createdAt: t.number(), // Unix ms
|
|
217
|
+
|
|
218
|
+
// WRONG – t.tuple() does not exist
|
|
219
|
+
coords: t.tuple<[number, number]>(),
|
|
220
|
+
// CORRECT
|
|
221
|
+
coords: t.array<[number, number]>(),
|
|
222
|
+
|
|
223
|
+
// WRONG – t.record() does not exist
|
|
224
|
+
scores: t.record<string, number>(),
|
|
225
|
+
// CORRECT
|
|
226
|
+
scores: t.object<Record<string, number>>(),
|
|
227
|
+
|
|
228
|
+
// WRONG – t.promise() does not exist (facts are synchronous)
|
|
229
|
+
result: t.promise<string>(),
|
|
230
|
+
|
|
231
|
+
// WRONG – t.any() does not exist
|
|
232
|
+
data: t.any(),
|
|
233
|
+
// CORRECT
|
|
234
|
+
data: t.object<unknown>(),
|
|
235
|
+
|
|
236
|
+
// WRONG – t.void() does not exist (not a fact type)
|
|
237
|
+
// WRONG – t.function() does not exist (functions are not serializable)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Type Assertion Alternative
|
|
241
|
+
|
|
242
|
+
For simple modules, you can skip `t.*()` and use TypeScript type assertions:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
const myModule = createModule("simple", {
|
|
246
|
+
schema: {
|
|
247
|
+
facts: {} as {
|
|
248
|
+
count: number;
|
|
249
|
+
name: string;
|
|
250
|
+
items: string[];
|
|
251
|
+
},
|
|
252
|
+
derivations: {} as {
|
|
253
|
+
doubled: number;
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
init: (facts) => {
|
|
257
|
+
facts.count = 0;
|
|
258
|
+
facts.name = "";
|
|
259
|
+
facts.items = [];
|
|
260
|
+
},
|
|
261
|
+
derive: {
|
|
262
|
+
doubled: (facts) => facts.count * 2,
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
This gives full TypeScript inference but skips runtime validation. Use `t.*()` when you want dev-mode validation, transforms, or self-documenting schemas.
|
|
268
|
+
|
|
269
|
+
## See also
|
|
270
|
+
|
|
271
|
+
- [`core-patterns.md`](./core-patterns.md) — where the schemas these builders define get attached (modules)
|
|
272
|
+
- [`system-api.md`](./system-api.md) — schema flows through `createSystem` and shows up on `system.facts.*`
|
|
273
|
+
- [`anti-patterns.md`](./anti-patterns.md) — hallucinated schema types (`t.map`, `t.set`, `t.date`, `t.promise`, `t.tuple`, `t.record`, `t.any`) that don't exist
|