@vpxa/aikit 0.1.4 → 0.1.5
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/package.json +1 -1
- package/packages/cli/dist/aikit-init.js +1 -1
- package/packages/cli/dist/commands/init/constants.d.ts +1 -1
- package/packages/cli/dist/commands/init/constants.js +1 -1
- package/packages/cli/dist/commands/init/user.js +1 -1
- package/packages/core/dist/index.d.ts +2 -2
- package/packages/core/dist/index.js +1 -1
- package/packages/core/dist/types.d.ts +12 -2
- package/packages/core/dist/types.js +1 -1
- package/packages/indexer/dist/incremental-indexer.d.ts +16 -0
- package/packages/indexer/dist/incremental-indexer.js +1 -1
- package/packages/indexer/dist/index.d.ts +2 -1
- package/packages/indexer/dist/index.js +1 -1
- package/packages/indexer/dist/smart-index-scheduler.d.ts +36 -0
- package/packages/indexer/dist/smart-index-scheduler.js +1 -0
- package/packages/server/dist/config.d.ts +3 -1
- package/packages/server/dist/config.js +1 -1
- package/packages/server/dist/index.js +1 -1
- package/packages/server/dist/server.d.ts +6 -2
- package/packages/server/dist/server.js +2 -2
- package/packages/server/dist/tools/context.tools.d.ts +1 -1
- package/packages/server/dist/tools/context.tools.js +2 -2
- package/packages/server/dist/tools/graph.tool.js +7 -2
- package/packages/store/dist/graph-store.interface.d.ts +42 -1
- package/packages/store/dist/index.d.ts +2 -2
- package/packages/store/dist/sqlite-graph-store.d.ts +10 -1
- package/packages/store/dist/sqlite-graph-store.js +49 -17
- package/packages/tools/dist/graph-query.d.ts +14 -2
- package/packages/tools/dist/graph-query.js +1 -1
- package/packages/tools/dist/trace.d.ts +16 -2
- package/packages/tools/dist/trace.js +2 -2
- package/scaffold/definitions/bodies.mjs +24 -0
- package/scaffold/definitions/prompts.mjs +22 -3
- package/scaffold/definitions/protocols.mjs +22 -0
- package/scaffold/general/agents/Architect-Reviewer-Alpha.agent.md +6 -0
- package/scaffold/general/agents/Architect-Reviewer-Beta.agent.md +6 -0
- package/scaffold/general/agents/Code-Reviewer-Alpha.agent.md +6 -0
- package/scaffold/general/agents/Code-Reviewer-Beta.agent.md +6 -0
- package/scaffold/general/agents/Debugger.agent.md +6 -0
- package/scaffold/general/agents/Documenter.agent.md +6 -0
- package/scaffold/general/agents/Explorer.agent.md +6 -0
- package/scaffold/general/agents/Frontend.agent.md +6 -0
- package/scaffold/general/agents/Implementer.agent.md +6 -0
- package/scaffold/general/agents/Orchestrator.agent.md +69 -0
- package/scaffold/general/agents/Planner.agent.md +18 -0
- package/scaffold/general/agents/Refactor.agent.md +6 -0
- package/scaffold/general/agents/Researcher-Alpha.agent.md +6 -0
- package/scaffold/general/agents/Researcher-Beta.agent.md +6 -0
- package/scaffold/general/agents/Researcher-Delta.agent.md +6 -0
- package/scaffold/general/agents/Researcher-Gamma.agent.md +6 -0
- package/scaffold/general/agents/Security.agent.md +6 -0
- package/scaffold/general/agents/_shared/code-agent-base.md +22 -0
- package/scaffold/general/prompts/ask.prompt.md +1 -1
- package/scaffold/general/prompts/debug.prompt.md +1 -0
- package/scaffold/general/prompts/implement.prompt.md +1 -0
- package/scaffold/general/prompts/plan.prompt.md +1 -1
- package/scaffold/general/prompts/review.prompt.md +1 -1
- package/scaffold/general/skills/frontend-design/SKILL.md +225 -0
- package/scaffold/general/skills/react/SKILL.md +297 -0
- package/scaffold/general/skills/typescript/SKILL.md +393 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# React
|
|
2
|
+
|
|
3
|
+
> Comprehensive React development patterns — component architecture, React 19 APIs, Server Components, TypeScript integration, and performance optimization. Synthesized from react-dev patterns, react-patterns (React 19), and Vercel React best practices.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
**MANDATORY** for the Frontend agent on every React task. Also use when:
|
|
8
|
+
- Building React components, hooks, or pages
|
|
9
|
+
- Working with Server Components or Server Actions
|
|
10
|
+
- Optimizing React performance (re-renders, bundle size, data fetching)
|
|
11
|
+
- Reviewing React code for correctness and best practices
|
|
12
|
+
|
|
13
|
+
## React 19 Quick Reference
|
|
14
|
+
|
|
15
|
+
| API | Use Case | Key Change |
|
|
16
|
+
|-----|----------|------------|
|
|
17
|
+
| `ref` as prop | Access DOM/component instance | No more `forwardRef` wrapper |
|
|
18
|
+
| `use()` | Read promises/context in render | Replaces `useContext`, enables async |
|
|
19
|
+
| `useActionState` | Form submission state + error | Replaces `useFormState` |
|
|
20
|
+
| `useOptimistic` | Instant UI feedback | Optimistic updates before server confirms |
|
|
21
|
+
| `useTransition` | Non-urgent state updates | `isPending` for loading states |
|
|
22
|
+
| `<form action>` | Server Actions in forms | Direct server function binding |
|
|
23
|
+
| Server Components | Zero-bundle components | Default in App Router, no `"use client"` |
|
|
24
|
+
| Server Actions | Server mutations | `"use server"` functions, form actions |
|
|
25
|
+
|
|
26
|
+
## Component Patterns
|
|
27
|
+
|
|
28
|
+
### Extend Native Elements
|
|
29
|
+
```tsx
|
|
30
|
+
type ButtonProps = React.ComponentProps<"button"> & {
|
|
31
|
+
variant?: "primary" | "secondary" | "ghost";
|
|
32
|
+
size?: "sm" | "md" | "lg";
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function Button({ variant = "primary", size = "md", className, ...props }: ButtonProps) {
|
|
36
|
+
return <button className={cn(variants[variant], sizes[size], className)} {...props} />;
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Discriminated Union Props
|
|
41
|
+
```tsx
|
|
42
|
+
type ModalProps =
|
|
43
|
+
| { variant: "alert"; message: string; onConfirm: () => void }
|
|
44
|
+
| { variant: "form"; children: React.ReactNode; onSubmit: (data: FormData) => void };
|
|
45
|
+
|
|
46
|
+
function Modal(props: ModalProps) {
|
|
47
|
+
switch (props.variant) {
|
|
48
|
+
case "alert": return <AlertModal {...props} />;
|
|
49
|
+
case "form": return <FormModal {...props} />;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Generic Components
|
|
55
|
+
```tsx
|
|
56
|
+
type TableProps<T> = {
|
|
57
|
+
data: T[];
|
|
58
|
+
columns: { key: keyof T; header: string; render?: (value: T[keyof T], row: T) => React.ReactNode }[];
|
|
59
|
+
onRowClick?: (row: T) => void;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
function Table<T extends Record<string, unknown>>({ data, columns, onRowClick }: TableProps<T>) {
|
|
63
|
+
// ...
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Children Typing
|
|
68
|
+
```tsx
|
|
69
|
+
// Specific children
|
|
70
|
+
type Props = { children: React.ReactElement<TabProps>[] };
|
|
71
|
+
|
|
72
|
+
// Render function children
|
|
73
|
+
type Props = { children: (data: T) => React.ReactNode };
|
|
74
|
+
|
|
75
|
+
// String only
|
|
76
|
+
type Props = { children: string };
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Ref as Prop (React 19)
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
// React 19: pass ref directly, no forwardRef needed
|
|
83
|
+
function Input({ ref, ...props }: React.ComponentProps<"input">) {
|
|
84
|
+
return <input ref={ref} {...props} />;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Cleanup function (new in React 19)
|
|
88
|
+
function MeasuredDiv({ ref, ...props }: React.ComponentProps<"div">) {
|
|
89
|
+
return (
|
|
90
|
+
<div
|
|
91
|
+
ref={(node) => {
|
|
92
|
+
if (node) measureElement(node);
|
|
93
|
+
return () => cleanupMeasurement(); // cleanup on unmount
|
|
94
|
+
}}
|
|
95
|
+
{...props}
|
|
96
|
+
/>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Server Components & Actions
|
|
102
|
+
|
|
103
|
+
### Server Component (default — no directive needed)
|
|
104
|
+
```tsx
|
|
105
|
+
// app/users/page.tsx — Server Component (default)
|
|
106
|
+
import { db } from "@/lib/db";
|
|
107
|
+
|
|
108
|
+
export default async function UsersPage() {
|
|
109
|
+
const users = await db.query.users.findMany(); // Direct DB access
|
|
110
|
+
return <UserList users={users} />; // Pass data to client
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Client Component (explicit opt-in)
|
|
115
|
+
```tsx
|
|
116
|
+
"use client";
|
|
117
|
+
import { useState, useOptimistic, useTransition } from "react";
|
|
118
|
+
|
|
119
|
+
export function Counter({ initialCount }: { initialCount: number }) {
|
|
120
|
+
const [count, setCount] = useState(initialCount);
|
|
121
|
+
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Server Action with Form
|
|
126
|
+
```tsx
|
|
127
|
+
// actions.ts
|
|
128
|
+
"use server";
|
|
129
|
+
import { z } from "zod";
|
|
130
|
+
|
|
131
|
+
const schema = z.object({ name: z.string().min(1), email: z.string().email() });
|
|
132
|
+
|
|
133
|
+
export async function createUser(_prev: unknown, formData: FormData) {
|
|
134
|
+
const result = schema.safeParse(Object.fromEntries(formData));
|
|
135
|
+
if (!result.success) return { error: result.error.flatten().fieldErrors };
|
|
136
|
+
await db.insert(users).values(result.data);
|
|
137
|
+
return { success: true };
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
"use client";
|
|
143
|
+
import { useActionState } from "react";
|
|
144
|
+
import { createUser } from "./actions";
|
|
145
|
+
|
|
146
|
+
export function CreateUserForm() {
|
|
147
|
+
const [state, action, isPending] = useActionState(createUser, null);
|
|
148
|
+
return (
|
|
149
|
+
<form action={action}>
|
|
150
|
+
<input name="name" required />
|
|
151
|
+
{state?.error?.name && <p className="text-red-500">{state.error.name}</p>}
|
|
152
|
+
<input name="email" type="email" required />
|
|
153
|
+
{state?.error?.email && <p className="text-red-500">{state.error.email}</p>}
|
|
154
|
+
<button disabled={isPending}>{isPending ? "Creating..." : "Create"}</button>
|
|
155
|
+
</form>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Optimistic Updates
|
|
161
|
+
```tsx
|
|
162
|
+
"use client";
|
|
163
|
+
import { useOptimistic } from "react";
|
|
164
|
+
|
|
165
|
+
function TodoList({ todos, addTodo }: { todos: Todo[]; addTodo: (text: string) => Promise<void> }) {
|
|
166
|
+
const [optimisticTodos, addOptimistic] = useOptimistic(
|
|
167
|
+
todos,
|
|
168
|
+
(state, newTodo: string) => [...state, { id: "temp", text: newTodo, pending: true }]
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
async function handleAdd(formData: FormData) {
|
|
172
|
+
const text = formData.get("text") as string;
|
|
173
|
+
addOptimistic(text);
|
|
174
|
+
await addTodo(text);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<form action={handleAdd}>
|
|
179
|
+
<input name="text" />
|
|
180
|
+
<button type="submit">Add</button>
|
|
181
|
+
<ul>
|
|
182
|
+
{optimisticTodos.map(t => (
|
|
183
|
+
<li key={t.id} style={{ opacity: t.pending ? 0.5 : 1 }}>{t.text}</li>
|
|
184
|
+
))}
|
|
185
|
+
</ul>
|
|
186
|
+
</form>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Event Handler Types
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
// Always use specific event types
|
|
195
|
+
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void
|
|
196
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
|
|
197
|
+
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void
|
|
198
|
+
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void
|
|
199
|
+
onFocus: (e: React.FocusEvent<HTMLInputElement>) => void
|
|
200
|
+
onDrag: (e: React.DragEvent<HTMLDivElement>) => void
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Hooks Typing
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
// useState with union
|
|
207
|
+
const [status, setStatus] = useState<"idle" | "loading" | "error">("idle");
|
|
208
|
+
|
|
209
|
+
// useRef for DOM — pass null, get RefObject
|
|
210
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
211
|
+
|
|
212
|
+
// useRef for mutable value — no null, get MutableRefObject
|
|
213
|
+
const intervalRef = useRef<number>(0);
|
|
214
|
+
|
|
215
|
+
// useReducer with discriminated actions
|
|
216
|
+
type Action = { type: "increment" } | { type: "set"; payload: number };
|
|
217
|
+
const [count, dispatch] = useReducer((state: number, action: Action) => {
|
|
218
|
+
switch (action.type) {
|
|
219
|
+
case "increment": return state + 1;
|
|
220
|
+
case "set": return action.payload;
|
|
221
|
+
}
|
|
222
|
+
}, 0);
|
|
223
|
+
|
|
224
|
+
// Custom hook: return as const for tuple types
|
|
225
|
+
function useToggle(initial = false) {
|
|
226
|
+
const [value, setValue] = useState(initial);
|
|
227
|
+
const toggle = useCallback(() => setValue(v => !v), []);
|
|
228
|
+
return [value, toggle] as const;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// useContext with null guard
|
|
232
|
+
const ctx = useContext(ThemeContext);
|
|
233
|
+
if (!ctx) throw new Error("useTheme must be used within ThemeProvider");
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Performance Rules
|
|
237
|
+
|
|
238
|
+
### Prevent Async Waterfalls
|
|
239
|
+
```tsx
|
|
240
|
+
// BAD — sequential fetching
|
|
241
|
+
const user = await getUser(id);
|
|
242
|
+
const posts = await getPosts(user.id); // Waits for user first
|
|
243
|
+
const comments = await getComments(posts[0].id); // Waits for posts
|
|
244
|
+
|
|
245
|
+
// GOOD — parallel where possible
|
|
246
|
+
const [user, posts] = await Promise.all([getUser(id), getPosts(id)]);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Bundle Size
|
|
250
|
+
- `dynamic(() => import("./HeavyComponent"))` — lazy load below-fold components
|
|
251
|
+
- Tree-shake: use named exports, avoid `import *`
|
|
252
|
+
- Analyze bundle: `@next/bundle-analyzer` to find bloat
|
|
253
|
+
- Code-split by route — each page loads only its own JS
|
|
254
|
+
- Avoid importing entire libraries: `import { debounce } from "lodash-es"` not `import _ from "lodash"`
|
|
255
|
+
|
|
256
|
+
### Server-Side Performance
|
|
257
|
+
- Default to Server Components — zero JS shipped to client
|
|
258
|
+
- Stream with Suspense: wrap slow data fetchers in `<Suspense fallback={<Skeleton />}>`
|
|
259
|
+
- Cache expensive computations with `"use cache"` directive
|
|
260
|
+
- Avoid serializing large objects across server/client boundary
|
|
261
|
+
|
|
262
|
+
### Client-Side Data Fetching
|
|
263
|
+
- Use SWR or React Query — automatic caching, revalidation, error retry
|
|
264
|
+
- Stale-while-revalidate: show cached data instantly, refresh in background
|
|
265
|
+
- Error boundaries: `<ErrorBoundary>` wrapping data-dependent trees
|
|
266
|
+
- Prefetch on hover/viewport for likely next navigations
|
|
267
|
+
|
|
268
|
+
### Re-render Optimization
|
|
269
|
+
- React Compiler (React 19): auto-memoizes — try this first before manual `useMemo`/`useCallback`
|
|
270
|
+
- If no compiler: `useMemo` for expensive computations, `useCallback` for stable function references
|
|
271
|
+
- Extract static JSX outside components — constants don't re-create
|
|
272
|
+
- Split context: separate frequently-changing state from rarely-changing state
|
|
273
|
+
- `React.memo()` on components receiving only primitive props
|
|
274
|
+
|
|
275
|
+
### Rendering Performance
|
|
276
|
+
- **Virtualize** lists > 100 items: `@tanstack/react-virtual`, `react-window`
|
|
277
|
+
- Defer heavy computations with `useDeferredValue`
|
|
278
|
+
- Batch state updates (React 18+ does this automatically in event handlers)
|
|
279
|
+
- Avoid reading DOM layout during renders (force sync layout = jank)
|
|
280
|
+
|
|
281
|
+
### Advanced Patterns
|
|
282
|
+
- Progressive hydration: load and hydrate components as they enter viewport
|
|
283
|
+
- Concurrent features: `useTransition` for non-blocking state updates
|
|
284
|
+
- Suspense caching: combine `<Suspense>` with data cache for instant page transitions
|
|
285
|
+
- React Compiler adoption: enable gradually, check for unsafe patterns first
|
|
286
|
+
|
|
287
|
+
## Decision Flow
|
|
288
|
+
|
|
289
|
+
When implementing a React feature:
|
|
290
|
+
|
|
291
|
+
1. **Server or Client?** → If it uses browser APIs, event handlers, or useState → Client. Everything else → Server.
|
|
292
|
+
2. **Data fetching?** → Server Component for initial data. SWR/React Query for client-side mutations.
|
|
293
|
+
3. **Form?** → Server Action + `useActionState` + Zod validation.
|
|
294
|
+
4. **Optimistic?** → `useOptimistic` for instant feedback.
|
|
295
|
+
5. **Loading?** → `<Suspense>` with skeleton fallback.
|
|
296
|
+
6. **Performance?** → Try React Compiler first. Then manual memo. Then virtualization. Then code splitting.
|
|
297
|
+
7. **Error?** → Error boundaries for rendering errors. try/catch for Server Actions.
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
# TypeScript
|
|
2
|
+
|
|
3
|
+
> Comprehensive TypeScript development patterns — type system performance, compiler configuration, advanced types, type safety, async patterns, module organization, and runtime optimization. Synthesized from pproenca TypeScript rules, TypeScript 6.0 features, wshobson advanced types and modern JS patterns, and Jeffallan typescript-pro patterns.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
**MANDATORY** for the Implementer agent on every TypeScript task. Also use when:
|
|
8
|
+
- Writing or reviewing TypeScript code
|
|
9
|
+
- Configuring `tsconfig.json` or compiler options
|
|
10
|
+
- Designing type-safe APIs, libraries, or utilities
|
|
11
|
+
- Optimizing type-check performance or build times
|
|
12
|
+
|
|
13
|
+
## Type System Performance
|
|
14
|
+
|
|
15
|
+
These rules prevent slow type-checking and IDE lag:
|
|
16
|
+
|
|
17
|
+
### Prefer Flat Unions Over Deep Conditionals
|
|
18
|
+
```tsx
|
|
19
|
+
// BAD — O(n²) type checking with deep conditionals
|
|
20
|
+
type DeepResolve<T> = T extends Promise<infer U> ? DeepResolve<U> : T extends Array<infer V> ? DeepResolve<V>[] : T;
|
|
21
|
+
|
|
22
|
+
// GOOD — flat unions check in O(n)
|
|
23
|
+
type Status = "idle" | "loading" | "success" | "error";
|
|
24
|
+
type Result = SuccessResult | ErrorResult | PendingResult;
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Interface Extends Over Intersection
|
|
28
|
+
```tsx
|
|
29
|
+
// BAD — intersection types are expensive to compute
|
|
30
|
+
type Extended = BaseType & { extra: string } & { more: number };
|
|
31
|
+
|
|
32
|
+
// GOOD — interface extends is cached by the compiler
|
|
33
|
+
interface Extended extends BaseType {
|
|
34
|
+
extra: string;
|
|
35
|
+
more: number;
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Rules
|
|
40
|
+
- **Avoid recursive conditional types** deeper than 3 levels — use iterative mapped types instead
|
|
41
|
+
- **Minimize computed types** in hot paths (function parameters, return types used in many places)
|
|
42
|
+
- **Use `interface` for object shapes**, `type` for unions, intersections, and mapped types
|
|
43
|
+
- **Annotate complex return types** explicitly — don't force the compiler to infer through 5 layers
|
|
44
|
+
- **Avoid generic defaults that trigger deep inference** — provide explicit type arguments at call sites
|
|
45
|
+
- **Break circular type references** — extract shared shapes into separate interfaces
|
|
46
|
+
|
|
47
|
+
## Compiler Configuration
|
|
48
|
+
|
|
49
|
+
### Strict Mode (all projects)
|
|
50
|
+
```jsonc
|
|
51
|
+
{
|
|
52
|
+
"compilerOptions": {
|
|
53
|
+
"strict": true, // Enables ALL strict checks
|
|
54
|
+
"noUncheckedIndexedAccess": true, // arr[0] is T | undefined
|
|
55
|
+
"exactOptionalProperties": true, // undefined ≠ missing
|
|
56
|
+
"noFallthroughCasesInSwitch": true,
|
|
57
|
+
"forceConsistentCasingInFileNames": true,
|
|
58
|
+
"isolatedModules": true, // Required for most bundlers
|
|
59
|
+
"verbatimModuleSyntax": true // Explicit import/export type
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Build Performance
|
|
65
|
+
```jsonc
|
|
66
|
+
{
|
|
67
|
+
"compilerOptions": {
|
|
68
|
+
"incremental": true, // Cache type-check results
|
|
69
|
+
"tsBuildInfoFile": ".tsbuildinfo",
|
|
70
|
+
"skipLibCheck": true // Skip .d.ts checking (safe for apps)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Project References (monorepos)
|
|
76
|
+
```jsonc
|
|
77
|
+
// tsconfig.json (root)
|
|
78
|
+
{
|
|
79
|
+
"references": [
|
|
80
|
+
{ "path": "packages/core" },
|
|
81
|
+
{ "path": "packages/server" },
|
|
82
|
+
{ "path": "packages/cli" }
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// packages/core/tsconfig.json
|
|
87
|
+
{
|
|
88
|
+
"compilerOptions": {
|
|
89
|
+
"composite": true,
|
|
90
|
+
"declaration": true,
|
|
91
|
+
"declarationMap": true
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### TypeScript 6.0+ Features
|
|
97
|
+
- **`#/` subpath imports**: `import { utils } from "#/lib/utils"` — no more `../../../` relative paths. Configure in `package.json` `"imports"` field.
|
|
98
|
+
- **`es2025` target**: Set, Iterator, Promise.withResolvers, RegExp v flag
|
|
99
|
+
- **`--noCheck`**: Skip type-checking in CI (when pre-checked) for faster builds
|
|
100
|
+
- **`--stableTypeOrdering`**: Deterministic declaration output for reproducible builds
|
|
101
|
+
- **`isolatedDeclarations`**: Generate `.d.ts` without type-checking — enables parallel builds
|
|
102
|
+
|
|
103
|
+
## Advanced Types
|
|
104
|
+
|
|
105
|
+
### Constrained Generics
|
|
106
|
+
```tsx
|
|
107
|
+
// Constrain to ensure property exists
|
|
108
|
+
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
|
|
109
|
+
return obj[key];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Constrain with interface
|
|
113
|
+
function merge<T extends Record<string, unknown>>(target: T, source: Partial<T>): T {
|
|
114
|
+
return { ...target, ...source };
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Conditional Types
|
|
119
|
+
```tsx
|
|
120
|
+
// Extract return type from async function
|
|
121
|
+
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
|
|
122
|
+
|
|
123
|
+
// Distributive conditional
|
|
124
|
+
type NonNullableDeep<T> = T extends null | undefined ? never : T extends object ? { [K in keyof T]: NonNullableDeep<T[K]> } : T;
|
|
125
|
+
|
|
126
|
+
// Infer from function
|
|
127
|
+
type Parameters<T> = T extends (...args: infer P) => unknown ? P : never;
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Mapped Types
|
|
131
|
+
```tsx
|
|
132
|
+
// Make all properties optional recursively
|
|
133
|
+
type DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P] };
|
|
134
|
+
|
|
135
|
+
// Remove readonly
|
|
136
|
+
type Mutable<T> = { -readonly [P in keyof T]: T[P] };
|
|
137
|
+
|
|
138
|
+
// Key remapping
|
|
139
|
+
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] };
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Template Literal Types
|
|
143
|
+
```tsx
|
|
144
|
+
// Event name typing
|
|
145
|
+
type EventName<T extends string> = `on${Capitalize<T>}`;
|
|
146
|
+
type Events = EventName<"click" | "focus" | "blur">; // "onClick" | "onFocus" | "onBlur"
|
|
147
|
+
|
|
148
|
+
// Route parameter extraction
|
|
149
|
+
type ExtractParams<T extends string> =
|
|
150
|
+
T extends `${string}:${infer Param}/${infer Rest}`
|
|
151
|
+
? Param | ExtractParams<Rest>
|
|
152
|
+
: T extends `${string}:${infer Param}`
|
|
153
|
+
? Param
|
|
154
|
+
: never;
|
|
155
|
+
type Params = ExtractParams<"/users/:id/posts/:postId">; // "id" | "postId"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Utility Type Patterns
|
|
159
|
+
```tsx
|
|
160
|
+
// Deep readonly
|
|
161
|
+
type DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P] };
|
|
162
|
+
|
|
163
|
+
// Make specific keys required
|
|
164
|
+
type RequireKeys<T, K extends keyof T> = T & Required<Pick<T, K>>;
|
|
165
|
+
|
|
166
|
+
// Object paths as union
|
|
167
|
+
type Paths<T, D extends number = 3> = [D] extends [never]
|
|
168
|
+
? never
|
|
169
|
+
: T extends object
|
|
170
|
+
? { [K in keyof T]-?: K extends string ? `${K}` | `${K}.${Paths<T[K]>}` : never }[keyof T]
|
|
171
|
+
: never;
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Type Safety Patterns
|
|
175
|
+
|
|
176
|
+
### Branded Types
|
|
177
|
+
```tsx
|
|
178
|
+
// Prevent mixing IDs of different entities
|
|
179
|
+
type UserId = string & { readonly __brand: "UserId" };
|
|
180
|
+
type OrderId = string & { readonly __brand: "OrderId" };
|
|
181
|
+
|
|
182
|
+
function createUserId(id: string): UserId { return id as UserId; }
|
|
183
|
+
function createOrderId(id: string): OrderId { return id as OrderId; }
|
|
184
|
+
|
|
185
|
+
function getUser(id: UserId) { /* ... */ }
|
|
186
|
+
getUser(createOrderId("123")); // TS Error! OrderId is not UserId
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Discriminated Unions with Exhaustive Matching
|
|
190
|
+
```tsx
|
|
191
|
+
type Shape =
|
|
192
|
+
| { kind: "circle"; radius: number }
|
|
193
|
+
| { kind: "rect"; width: number; height: number }
|
|
194
|
+
| { kind: "triangle"; base: number; height: number };
|
|
195
|
+
|
|
196
|
+
function area(shape: Shape): number {
|
|
197
|
+
switch (shape.kind) {
|
|
198
|
+
case "circle": return Math.PI * shape.radius ** 2;
|
|
199
|
+
case "rect": return shape.width * shape.height;
|
|
200
|
+
case "triangle": return 0.5 * shape.base * shape.height;
|
|
201
|
+
default: {
|
|
202
|
+
const _exhaustive: never = shape; // Compile error if case missed
|
|
203
|
+
return _exhaustive;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Result Pattern
|
|
210
|
+
```tsx
|
|
211
|
+
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
|
|
212
|
+
|
|
213
|
+
function ok<T>(value: T): Result<T, never> { return { ok: true, value }; }
|
|
214
|
+
function err<E>(error: E): Result<never, E> { return { ok: false, error }; }
|
|
215
|
+
|
|
216
|
+
async function parseConfig(path: string): Promise<Result<Config, ParseError>> {
|
|
217
|
+
try {
|
|
218
|
+
const raw = await readFile(path, "utf-8");
|
|
219
|
+
return ok(JSON.parse(raw));
|
|
220
|
+
} catch (e) {
|
|
221
|
+
return err(new ParseError(`Failed to parse ${path}`, { cause: e }));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Usage — forces error handling
|
|
226
|
+
const result = await parseConfig("config.json");
|
|
227
|
+
if (!result.ok) {
|
|
228
|
+
console.error(result.error.message);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
const config = result.value; // Type-narrowed to Config
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Async Patterns
|
|
235
|
+
|
|
236
|
+
### Parallel Execution
|
|
237
|
+
```tsx
|
|
238
|
+
// Promise.all — fail-fast on first rejection
|
|
239
|
+
const [users, posts] = await Promise.all([getUsers(), getPosts()]);
|
|
240
|
+
|
|
241
|
+
// Promise.allSettled — get all results regardless of failures
|
|
242
|
+
const results = await Promise.allSettled([getUsers(), getPosts(), getComments()]);
|
|
243
|
+
for (const r of results) {
|
|
244
|
+
if (r.status === "fulfilled") process(r.value);
|
|
245
|
+
else logError(r.reason);
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### AbortController
|
|
250
|
+
```tsx
|
|
251
|
+
async function fetchWithTimeout(url: string, timeoutMs: number): Promise<Response> {
|
|
252
|
+
const controller = new AbortController();
|
|
253
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
254
|
+
try {
|
|
255
|
+
return await fetch(url, { signal: controller.signal });
|
|
256
|
+
} finally {
|
|
257
|
+
clearTimeout(timeout);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Async Iterators
|
|
263
|
+
```tsx
|
|
264
|
+
async function* paginate<T>(fetcher: (page: number) => Promise<T[]>): AsyncGenerator<T> {
|
|
265
|
+
let page = 0;
|
|
266
|
+
while (true) {
|
|
267
|
+
const items = await fetcher(page++);
|
|
268
|
+
if (items.length === 0) break;
|
|
269
|
+
yield* items;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Usage
|
|
274
|
+
for await (const user of paginate(fetchUsersPage)) {
|
|
275
|
+
processUser(user);
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Module Organization
|
|
280
|
+
|
|
281
|
+
### Barrel File Performance
|
|
282
|
+
```tsx
|
|
283
|
+
// BAD — barrel files cause entire module tree to load
|
|
284
|
+
// index.ts
|
|
285
|
+
export * from "./userService";
|
|
286
|
+
export * from "./orderService";
|
|
287
|
+
export * from "./paymentService";
|
|
288
|
+
|
|
289
|
+
// GOOD — direct imports skip barrel overhead
|
|
290
|
+
import { UserService } from "./services/userService";
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Type-Only Imports
|
|
294
|
+
```tsx
|
|
295
|
+
// Always use type-only imports for types
|
|
296
|
+
import type { User, CreateUserDto } from "./types";
|
|
297
|
+
import { UserService } from "./userService";
|
|
298
|
+
// With verbatimModuleSyntax, this is enforced
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Re-export Patterns
|
|
302
|
+
```tsx
|
|
303
|
+
// Public API surface — explicit re-exports
|
|
304
|
+
export { UserService } from "./userService";
|
|
305
|
+
export type { User, CreateUserDto } from "./types";
|
|
306
|
+
// Don't export internal implementation details
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Memory & Runtime
|
|
310
|
+
|
|
311
|
+
### WeakRef for Caches
|
|
312
|
+
```tsx
|
|
313
|
+
class WeakCache<K extends object, V> {
|
|
314
|
+
private cache = new Map<K, WeakRef<V & object>>();
|
|
315
|
+
private registry = new FinalizationRegistry<K>(key => this.cache.delete(key));
|
|
316
|
+
|
|
317
|
+
set(key: K, value: V & object): void {
|
|
318
|
+
this.cache.set(key, new WeakRef(value));
|
|
319
|
+
this.registry.register(value, key);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
get(key: K): V | undefined {
|
|
323
|
+
return this.cache.get(key)?.deref();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Efficient Data Structures
|
|
329
|
+
```tsx
|
|
330
|
+
// Use Map/Set for frequent lookups
|
|
331
|
+
const userIndex = new Map<string, User>(); // O(1) lookup
|
|
332
|
+
const activeIds = new Set<string>(); // O(1) has/add/delete
|
|
333
|
+
|
|
334
|
+
// NOT plain objects for dynamic keys
|
|
335
|
+
const userIndex: Record<string, User> = {}; // Slower for frequent mutations
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Runtime Rules
|
|
339
|
+
- Minimize `Reflect.metadata` and decorator reflection — adds runtime overhead
|
|
340
|
+
- Prefer `structuredClone()` over `JSON.parse(JSON.stringify())` for deep cloning
|
|
341
|
+
- Use `Object.freeze()` for truly immutable constants
|
|
342
|
+
- Avoid `eval()`, `new Function()`, and dynamic `require()` — security + performance issues
|
|
343
|
+
|
|
344
|
+
## Modern JavaScript Patterns
|
|
345
|
+
|
|
346
|
+
### Nullish Handling
|
|
347
|
+
```tsx
|
|
348
|
+
const port = config.port ?? 3000; // Only for null/undefined
|
|
349
|
+
const name = user?.profile?.displayName; // Optional chaining
|
|
350
|
+
const normalized = (input ??= defaultValue); // Nullish assignment
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Immutable Updates
|
|
354
|
+
```tsx
|
|
355
|
+
// Object spread for shallow updates
|
|
356
|
+
const updated = { ...user, name: "New Name" };
|
|
357
|
+
|
|
358
|
+
// structuredClone for deep copy
|
|
359
|
+
const deep = structuredClone(complexObject);
|
|
360
|
+
|
|
361
|
+
// Array immutable operations
|
|
362
|
+
const added = [...items, newItem];
|
|
363
|
+
const removed = items.filter(i => i.id !== targetId);
|
|
364
|
+
const updated = items.map(i => i.id === targetId ? { ...i, ...changes } : i);
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Pipe Pattern
|
|
368
|
+
```tsx
|
|
369
|
+
function pipe<T>(value: T, ...fns: Array<(v: unknown) => unknown>): unknown {
|
|
370
|
+
return fns.reduce((acc, fn) => fn(acc), value as unknown);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Usage
|
|
374
|
+
const result = pipe(
|
|
375
|
+
rawData,
|
|
376
|
+
validate,
|
|
377
|
+
normalize,
|
|
378
|
+
transform,
|
|
379
|
+
serialize
|
|
380
|
+
);
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## Decision Flow
|
|
384
|
+
|
|
385
|
+
When writing TypeScript:
|
|
386
|
+
|
|
387
|
+
1. **Interface or Type?** → Object shape = interface. Union/intersection/mapped = type.
|
|
388
|
+
2. **Generic needed?** → If the function works with multiple types and you need to preserve the type → yes. Otherwise keep it simple.
|
|
389
|
+
3. **Strict enough?** → Enable all strict flags. Use branded types for domain IDs. Use discriminated unions for variants.
|
|
390
|
+
4. **Performance?** → Check: Are there deep conditional types? Circular references? Massive unions? Simplify if so.
|
|
391
|
+
5. **Import style?** → `import type` for types. Direct imports, not barrel files. Named exports, not default.
|
|
392
|
+
6. **Error handling?** → Result pattern for expected errors. throw for unexpected/programmer errors.
|
|
393
|
+
7. **Async?** → Promise.all for parallel. AbortController for timeouts. AsyncGenerator for streams.
|