@vpxa/aikit 0.1.55 → 0.1.57
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/scaffold/adapters/copilot.mjs +7 -4
- package/scaffold/definitions/tools.mjs +6 -0
- package/scaffold/general/agents/Orchestrator.agent.md +1 -1
- package/scaffold/general/agents/Planner.agent.md +1 -1
- package/scaffold/general/prompts/aikit-ask.prompt.md +0 -8
- package/scaffold/general/prompts/aikit-debug.prompt.md +0 -11
- package/scaffold/general/prompts/aikit-design.prompt.md +0 -8
- package/scaffold/general/prompts/aikit-flow-add.prompt.md +0 -8
- package/scaffold/general/prompts/aikit-flow-create.prompt.md +0 -8
- package/scaffold/general/prompts/aikit-flow-manage.prompt.md +0 -9
- package/scaffold/general/prompts/aikit-implement.prompt.md +0 -10
- package/scaffold/general/prompts/aikit-plan.prompt.md +0 -10
- package/scaffold/general/prompts/aikit-review.prompt.md +0 -8
- package/scaffold/general/skills/adr-skill/SKILL.md +6 -6
- package/scaffold/general/skills/frontend-design/SKILL.md +237 -237
- package/scaffold/general/skills/lesson-learned/SKILL.md +7 -7
- package/scaffold/general/skills/react/SKILL.md +309 -309
- package/scaffold/general/skills/requirements-clarity/SKILL.md +6 -6
- package/scaffold/general/skills/session-handoff/SKILL.md +7 -7
- package/scaffold/general/skills/typescript/SKILL.md +405 -405
|
@@ -1,405 +1,405 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: typescript
|
|
3
|
-
description: "Comprehensive TypeScript development patterns — type system performance, compiler configuration, advanced types, type safety, async patterns, module organization, and runtime optimization."
|
|
4
|
-
metadata:
|
|
5
|
-
category: domain
|
|
6
|
-
domain: typescript
|
|
7
|
-
applicability: on-demand
|
|
8
|
-
inputs: [codebase, requirements]
|
|
9
|
-
outputs: [type-patterns, code]
|
|
10
|
-
relatedSkills: [react]
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# TypeScript
|
|
14
|
-
|
|
15
|
-
> 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.
|
|
16
|
-
|
|
17
|
-
## When to Use
|
|
18
|
-
|
|
19
|
-
**MANDATORY** for the Implementer agent on every TypeScript task. Also use when:
|
|
20
|
-
- Writing or reviewing TypeScript code
|
|
21
|
-
- Configuring `tsconfig.json` or compiler options
|
|
22
|
-
- Designing type-safe APIs, libraries, or utilities
|
|
23
|
-
- Optimizing type-check performance or build times
|
|
24
|
-
|
|
25
|
-
## Type System Performance
|
|
26
|
-
|
|
27
|
-
These rules prevent slow type-checking and IDE lag:
|
|
28
|
-
|
|
29
|
-
### Prefer Flat Unions Over Deep Conditionals
|
|
30
|
-
```tsx
|
|
31
|
-
// BAD — O(n²) type checking with deep conditionals
|
|
32
|
-
type DeepResolve<T> = T extends Promise<infer U> ? DeepResolve<U> : T extends Array<infer V> ? DeepResolve<V>[] : T;
|
|
33
|
-
|
|
34
|
-
// GOOD — flat unions check in O(n)
|
|
35
|
-
type Status = "idle" | "loading" | "success" | "error";
|
|
36
|
-
type Result = SuccessResult | ErrorResult | PendingResult;
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### Interface Extends Over Intersection
|
|
40
|
-
```tsx
|
|
41
|
-
// BAD — intersection types are expensive to compute
|
|
42
|
-
type Extended = BaseType & { extra: string } & { more: number };
|
|
43
|
-
|
|
44
|
-
// GOOD — interface extends is cached by the compiler
|
|
45
|
-
interface Extended extends BaseType {
|
|
46
|
-
extra: string;
|
|
47
|
-
more: number;
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### Rules
|
|
52
|
-
- **Avoid recursive conditional types** deeper than 3 levels — use iterative mapped types instead
|
|
53
|
-
- **Minimize computed types** in hot paths (function parameters, return types used in many places)
|
|
54
|
-
- **Use `interface` for object shapes**, `type` for unions, intersections, and mapped types
|
|
55
|
-
- **Annotate complex return types** explicitly — don't force the compiler to infer through 5 layers
|
|
56
|
-
- **Avoid generic defaults that trigger deep inference** — provide explicit type arguments at call sites
|
|
57
|
-
- **Break circular type references** — extract shared shapes into separate interfaces
|
|
58
|
-
|
|
59
|
-
## Compiler Configuration
|
|
60
|
-
|
|
61
|
-
### Strict Mode (all projects)
|
|
62
|
-
```jsonc
|
|
63
|
-
{
|
|
64
|
-
"compilerOptions": {
|
|
65
|
-
"strict": true, // Enables ALL strict checks
|
|
66
|
-
"noUncheckedIndexedAccess": true, // arr[0] is T | undefined
|
|
67
|
-
"exactOptionalProperties": true, // undefined ≠ missing
|
|
68
|
-
"noFallthroughCasesInSwitch": true,
|
|
69
|
-
"forceConsistentCasingInFileNames": true,
|
|
70
|
-
"isolatedModules": true, // Required for most bundlers
|
|
71
|
-
"verbatimModuleSyntax": true // Explicit import/export type
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### Build Performance
|
|
77
|
-
```jsonc
|
|
78
|
-
{
|
|
79
|
-
"compilerOptions": {
|
|
80
|
-
"incremental": true, // Cache type-check results
|
|
81
|
-
"tsBuildInfoFile": ".tsbuildinfo",
|
|
82
|
-
"skipLibCheck": true // Skip .d.ts checking (safe for apps)
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Project References (monorepos)
|
|
88
|
-
```jsonc
|
|
89
|
-
// tsconfig.json (root)
|
|
90
|
-
{
|
|
91
|
-
"references": [
|
|
92
|
-
{ "path": "packages/core" },
|
|
93
|
-
{ "path": "packages/server" },
|
|
94
|
-
{ "path": "packages/cli" }
|
|
95
|
-
]
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// packages/core/tsconfig.json
|
|
99
|
-
{
|
|
100
|
-
"compilerOptions": {
|
|
101
|
-
"composite": true,
|
|
102
|
-
"declaration": true,
|
|
103
|
-
"declarationMap": true
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### TypeScript 6.0+ Features
|
|
109
|
-
- **`#/` subpath imports**: `import { utils } from "#/lib/utils"` — no more `../../../` relative paths. Configure in `package.json` `"imports"` field.
|
|
110
|
-
- **`es2025` target**: Set, Iterator, Promise.withResolvers, RegExp v flag
|
|
111
|
-
- **`--noCheck`**: Skip type-checking in CI (when pre-checked) for faster builds
|
|
112
|
-
- **`--stableTypeOrdering`**: Deterministic declaration output for reproducible builds
|
|
113
|
-
- **`isolatedDeclarations`**: Generate `.d.ts` without type-checking — enables parallel builds
|
|
114
|
-
|
|
115
|
-
## Advanced Types
|
|
116
|
-
|
|
117
|
-
### Constrained Generics
|
|
118
|
-
```tsx
|
|
119
|
-
// Constrain to ensure property exists
|
|
120
|
-
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
|
|
121
|
-
return obj[key];
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Constrain with interface
|
|
125
|
-
function merge<T extends Record<string, unknown>>(target: T, source: Partial<T>): T {
|
|
126
|
-
return { ...target, ...source };
|
|
127
|
-
}
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### Conditional Types
|
|
131
|
-
```tsx
|
|
132
|
-
// Extract return type from async function
|
|
133
|
-
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
|
|
134
|
-
|
|
135
|
-
// Distributive conditional
|
|
136
|
-
type NonNullableDeep<T> = T extends null | undefined ? never : T extends object ? { [K in keyof T]: NonNullableDeep<T[K]> } : T;
|
|
137
|
-
|
|
138
|
-
// Infer from function
|
|
139
|
-
type Parameters<T> = T extends (...args: infer P) => unknown ? P : never;
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### Mapped Types
|
|
143
|
-
```tsx
|
|
144
|
-
// Make all properties optional recursively
|
|
145
|
-
type DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P] };
|
|
146
|
-
|
|
147
|
-
// Remove readonly
|
|
148
|
-
type Mutable<T> = { -readonly [P in keyof T]: T[P] };
|
|
149
|
-
|
|
150
|
-
// Key remapping
|
|
151
|
-
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] };
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### Template Literal Types
|
|
155
|
-
```tsx
|
|
156
|
-
// Event name typing
|
|
157
|
-
type EventName<T extends string> = `on${Capitalize<T>}`;
|
|
158
|
-
type Events = EventName<"click" | "focus" | "blur">; // "onClick" | "onFocus" | "onBlur"
|
|
159
|
-
|
|
160
|
-
// Route parameter extraction
|
|
161
|
-
type ExtractParams<T extends string> =
|
|
162
|
-
T extends `${string}:${infer Param}/${infer Rest}`
|
|
163
|
-
? Param | ExtractParams<Rest>
|
|
164
|
-
: T extends `${string}:${infer Param}`
|
|
165
|
-
? Param
|
|
166
|
-
: never;
|
|
167
|
-
type Params = ExtractParams<"/users/:id/posts/:postId">; // "id" | "postId"
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
### Utility Type Patterns
|
|
171
|
-
```tsx
|
|
172
|
-
// Deep readonly
|
|
173
|
-
type DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P] };
|
|
174
|
-
|
|
175
|
-
// Make specific keys required
|
|
176
|
-
type RequireKeys<T, K extends keyof T> = T & Required<Pick<T, K>>;
|
|
177
|
-
|
|
178
|
-
// Object paths as union
|
|
179
|
-
type Paths<T, D extends number = 3> = [D] extends [never]
|
|
180
|
-
? never
|
|
181
|
-
: T extends object
|
|
182
|
-
? { [K in keyof T]-?: K extends string ? `${K}` | `${K}.${Paths<T[K]>}` : never }[keyof T]
|
|
183
|
-
: never;
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
## Type Safety Patterns
|
|
187
|
-
|
|
188
|
-
### Branded Types
|
|
189
|
-
```tsx
|
|
190
|
-
// Prevent mixing IDs of different entities
|
|
191
|
-
type UserId = string & { readonly __brand: "UserId" };
|
|
192
|
-
type OrderId = string & { readonly __brand: "OrderId" };
|
|
193
|
-
|
|
194
|
-
function createUserId(id: string): UserId { return id as UserId; }
|
|
195
|
-
function createOrderId(id: string): OrderId { return id as OrderId; }
|
|
196
|
-
|
|
197
|
-
function getUser(id: UserId) { /* ... */ }
|
|
198
|
-
getUser(createOrderId("123")); // TS Error! OrderId is not UserId
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### Discriminated Unions with Exhaustive Matching
|
|
202
|
-
```tsx
|
|
203
|
-
type Shape =
|
|
204
|
-
| { kind: "circle"; radius: number }
|
|
205
|
-
| { kind: "rect"; width: number; height: number }
|
|
206
|
-
| { kind: "triangle"; base: number; height: number };
|
|
207
|
-
|
|
208
|
-
function area(shape: Shape): number {
|
|
209
|
-
switch (shape.kind) {
|
|
210
|
-
case "circle": return Math.PI * shape.radius ** 2;
|
|
211
|
-
case "rect": return shape.width * shape.height;
|
|
212
|
-
case "triangle": return 0.5 * shape.base * shape.height;
|
|
213
|
-
default: {
|
|
214
|
-
const _exhaustive: never = shape; // Compile error if case missed
|
|
215
|
-
return _exhaustive;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
### Result Pattern
|
|
222
|
-
```tsx
|
|
223
|
-
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
|
|
224
|
-
|
|
225
|
-
function ok<T>(value: T): Result<T, never> { return { ok: true, value }; }
|
|
226
|
-
function err<E>(error: E): Result<never, E> { return { ok: false, error }; }
|
|
227
|
-
|
|
228
|
-
async function parseConfig(path: string): Promise<Result<Config, ParseError>> {
|
|
229
|
-
try {
|
|
230
|
-
const raw = await readFile(path, "utf-8");
|
|
231
|
-
return ok(JSON.parse(raw));
|
|
232
|
-
} catch (e) {
|
|
233
|
-
return err(new ParseError(`Failed to parse ${path}`, { cause: e }));
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Usage — forces error handling
|
|
238
|
-
const result = await parseConfig("config.json");
|
|
239
|
-
if (!result.ok) {
|
|
240
|
-
console.error(result.error.message);
|
|
241
|
-
process.exit(1);
|
|
242
|
-
}
|
|
243
|
-
const config = result.value; // Type-narrowed to Config
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
## Async Patterns
|
|
247
|
-
|
|
248
|
-
### Parallel Execution
|
|
249
|
-
```tsx
|
|
250
|
-
// Promise.all — fail-fast on first rejection
|
|
251
|
-
const [users, posts] = await Promise.all([getUsers(), getPosts()]);
|
|
252
|
-
|
|
253
|
-
// Promise.allSettled — get all results regardless of failures
|
|
254
|
-
const results = await Promise.allSettled([getUsers(), getPosts(), getComments()]);
|
|
255
|
-
for (const r of results) {
|
|
256
|
-
if (r.status === "fulfilled") process(r.value);
|
|
257
|
-
else logError(r.reason);
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
### AbortController
|
|
262
|
-
```tsx
|
|
263
|
-
async function fetchWithTimeout(url: string, timeoutMs: number): Promise<Response> {
|
|
264
|
-
const controller = new AbortController();
|
|
265
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
266
|
-
try {
|
|
267
|
-
return await fetch(url, { signal: controller.signal });
|
|
268
|
-
} finally {
|
|
269
|
-
clearTimeout(timeout);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
### Async Iterators
|
|
275
|
-
```tsx
|
|
276
|
-
async function* paginate<T>(fetcher: (page: number) => Promise<T[]>): AsyncGenerator<T> {
|
|
277
|
-
let page = 0;
|
|
278
|
-
while (true) {
|
|
279
|
-
const items = await fetcher(page++);
|
|
280
|
-
if (items.length === 0) break;
|
|
281
|
-
yield* items;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Usage
|
|
286
|
-
for await (const user of paginate(fetchUsersPage)) {
|
|
287
|
-
processUser(user);
|
|
288
|
-
}
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
## Module Organization
|
|
292
|
-
|
|
293
|
-
### Barrel File Performance
|
|
294
|
-
```tsx
|
|
295
|
-
// BAD — barrel files cause entire module tree to load
|
|
296
|
-
// index.ts
|
|
297
|
-
export * from "./userService";
|
|
298
|
-
export * from "./orderService";
|
|
299
|
-
export * from "./paymentService";
|
|
300
|
-
|
|
301
|
-
// GOOD — direct imports skip barrel overhead
|
|
302
|
-
import { UserService } from "./services/userService";
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
### Type-Only Imports
|
|
306
|
-
```tsx
|
|
307
|
-
// Always use type-only imports for types
|
|
308
|
-
import type { User, CreateUserDto } from "./types";
|
|
309
|
-
import { UserService } from "./userService";
|
|
310
|
-
// With verbatimModuleSyntax, this is enforced
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
### Re-export Patterns
|
|
314
|
-
```tsx
|
|
315
|
-
// Public API surface — explicit re-exports
|
|
316
|
-
export { UserService } from "./userService";
|
|
317
|
-
export type { User, CreateUserDto } from "./types";
|
|
318
|
-
// Don't export internal implementation details
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
## Memory & Runtime
|
|
322
|
-
|
|
323
|
-
### WeakRef for Caches
|
|
324
|
-
```tsx
|
|
325
|
-
class WeakCache<K extends object, V> {
|
|
326
|
-
private cache = new Map<K, WeakRef<V & object>>();
|
|
327
|
-
private registry = new FinalizationRegistry<K>(key => this.cache.delete(key));
|
|
328
|
-
|
|
329
|
-
set(key: K, value: V & object): void {
|
|
330
|
-
this.cache.set(key, new WeakRef(value));
|
|
331
|
-
this.registry.register(value, key);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
get(key: K): V | undefined {
|
|
335
|
-
return this.cache.get(key)?.deref();
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
### Efficient Data Structures
|
|
341
|
-
```tsx
|
|
342
|
-
// Use Map/Set for frequent lookups
|
|
343
|
-
const userIndex = new Map<string, User>(); // O(1) lookup
|
|
344
|
-
const activeIds = new Set<string>(); // O(1) has/add/delete
|
|
345
|
-
|
|
346
|
-
// NOT plain objects for dynamic keys
|
|
347
|
-
const userIndex: Record<string, User> = {}; // Slower for frequent mutations
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
### Runtime Rules
|
|
351
|
-
- Minimize `Reflect.metadata` and decorator reflection — adds runtime overhead
|
|
352
|
-
- Prefer `structuredClone()` over `JSON.parse(JSON.stringify())` for deep cloning
|
|
353
|
-
- Use `Object.freeze()` for truly immutable constants
|
|
354
|
-
- Avoid `eval()`, `new Function()`, and dynamic `require()` — security + performance issues
|
|
355
|
-
|
|
356
|
-
## Modern JavaScript Patterns
|
|
357
|
-
|
|
358
|
-
### Nullish Handling
|
|
359
|
-
```tsx
|
|
360
|
-
const port = config.port ?? 3000; // Only for null/undefined
|
|
361
|
-
const name = user?.profile?.displayName; // Optional chaining
|
|
362
|
-
const normalized = (input ??= defaultValue); // Nullish assignment
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
### Immutable Updates
|
|
366
|
-
```tsx
|
|
367
|
-
// Object spread for shallow updates
|
|
368
|
-
const updated = { ...user, name: "New Name" };
|
|
369
|
-
|
|
370
|
-
// structuredClone for deep copy
|
|
371
|
-
const deep = structuredClone(complexObject);
|
|
372
|
-
|
|
373
|
-
// Array immutable operations
|
|
374
|
-
const added = [...items, newItem];
|
|
375
|
-
const removed = items.filter(i => i.id !== targetId);
|
|
376
|
-
const updated = items.map(i => i.id === targetId ? { ...i, ...changes } : i);
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
### Pipe Pattern
|
|
380
|
-
```tsx
|
|
381
|
-
function pipe<T>(value: T, ...fns: Array<(v: unknown) => unknown>): unknown {
|
|
382
|
-
return fns.reduce((acc, fn) => fn(acc), value as unknown);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Usage
|
|
386
|
-
const result = pipe(
|
|
387
|
-
rawData,
|
|
388
|
-
validate,
|
|
389
|
-
normalize,
|
|
390
|
-
transform,
|
|
391
|
-
serialize
|
|
392
|
-
);
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
## Decision Flow
|
|
396
|
-
|
|
397
|
-
When writing TypeScript:
|
|
398
|
-
|
|
399
|
-
1. **Interface or Type?** → Object shape = interface. Union/intersection/mapped = type.
|
|
400
|
-
2. **Generic needed?** → If the function works with multiple types and you need to preserve the type → yes. Otherwise keep it simple.
|
|
401
|
-
3. **Strict enough?** → Enable all strict flags. Use branded types for domain IDs. Use discriminated unions for variants.
|
|
402
|
-
4. **Performance?** → Check: Are there deep conditional types? Circular references? Massive unions? Simplify if so.
|
|
403
|
-
5. **Import style?** → `import type` for types. Direct imports, not barrel files. Named exports, not default.
|
|
404
|
-
6. **Error handling?** → Result pattern for expected errors. throw for unexpected/programmer errors.
|
|
405
|
-
7. **Async?** → Promise.all for parallel. AbortController for timeouts. AsyncGenerator for streams.
|
|
1
|
+
---
|
|
2
|
+
name: typescript
|
|
3
|
+
description: "Comprehensive TypeScript development patterns — type system performance, compiler configuration, advanced types, type safety, async patterns, module organization, and runtime optimization."
|
|
4
|
+
metadata:
|
|
5
|
+
category: domain
|
|
6
|
+
domain: typescript
|
|
7
|
+
applicability: on-demand
|
|
8
|
+
inputs: [codebase, requirements]
|
|
9
|
+
outputs: [type-patterns, code]
|
|
10
|
+
relatedSkills: [react]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# TypeScript
|
|
14
|
+
|
|
15
|
+
> 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.
|
|
16
|
+
|
|
17
|
+
## When to Use
|
|
18
|
+
|
|
19
|
+
**MANDATORY** for the Implementer agent on every TypeScript task. Also use when:
|
|
20
|
+
- Writing or reviewing TypeScript code
|
|
21
|
+
- Configuring `tsconfig.json` or compiler options
|
|
22
|
+
- Designing type-safe APIs, libraries, or utilities
|
|
23
|
+
- Optimizing type-check performance or build times
|
|
24
|
+
|
|
25
|
+
## Type System Performance
|
|
26
|
+
|
|
27
|
+
These rules prevent slow type-checking and IDE lag:
|
|
28
|
+
|
|
29
|
+
### Prefer Flat Unions Over Deep Conditionals
|
|
30
|
+
```tsx
|
|
31
|
+
// BAD — O(n²) type checking with deep conditionals
|
|
32
|
+
type DeepResolve<T> = T extends Promise<infer U> ? DeepResolve<U> : T extends Array<infer V> ? DeepResolve<V>[] : T;
|
|
33
|
+
|
|
34
|
+
// GOOD — flat unions check in O(n)
|
|
35
|
+
type Status = "idle" | "loading" | "success" | "error";
|
|
36
|
+
type Result = SuccessResult | ErrorResult | PendingResult;
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Interface Extends Over Intersection
|
|
40
|
+
```tsx
|
|
41
|
+
// BAD — intersection types are expensive to compute
|
|
42
|
+
type Extended = BaseType & { extra: string } & { more: number };
|
|
43
|
+
|
|
44
|
+
// GOOD — interface extends is cached by the compiler
|
|
45
|
+
interface Extended extends BaseType {
|
|
46
|
+
extra: string;
|
|
47
|
+
more: number;
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Rules
|
|
52
|
+
- **Avoid recursive conditional types** deeper than 3 levels — use iterative mapped types instead
|
|
53
|
+
- **Minimize computed types** in hot paths (function parameters, return types used in many places)
|
|
54
|
+
- **Use `interface` for object shapes**, `type` for unions, intersections, and mapped types
|
|
55
|
+
- **Annotate complex return types** explicitly — don't force the compiler to infer through 5 layers
|
|
56
|
+
- **Avoid generic defaults that trigger deep inference** — provide explicit type arguments at call sites
|
|
57
|
+
- **Break circular type references** — extract shared shapes into separate interfaces
|
|
58
|
+
|
|
59
|
+
## Compiler Configuration
|
|
60
|
+
|
|
61
|
+
### Strict Mode (all projects)
|
|
62
|
+
```jsonc
|
|
63
|
+
{
|
|
64
|
+
"compilerOptions": {
|
|
65
|
+
"strict": true, // Enables ALL strict checks
|
|
66
|
+
"noUncheckedIndexedAccess": true, // arr[0] is T | undefined
|
|
67
|
+
"exactOptionalProperties": true, // undefined ≠ missing
|
|
68
|
+
"noFallthroughCasesInSwitch": true,
|
|
69
|
+
"forceConsistentCasingInFileNames": true,
|
|
70
|
+
"isolatedModules": true, // Required for most bundlers
|
|
71
|
+
"verbatimModuleSyntax": true // Explicit import/export type
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Build Performance
|
|
77
|
+
```jsonc
|
|
78
|
+
{
|
|
79
|
+
"compilerOptions": {
|
|
80
|
+
"incremental": true, // Cache type-check results
|
|
81
|
+
"tsBuildInfoFile": ".tsbuildinfo",
|
|
82
|
+
"skipLibCheck": true // Skip .d.ts checking (safe for apps)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Project References (monorepos)
|
|
88
|
+
```jsonc
|
|
89
|
+
// tsconfig.json (root)
|
|
90
|
+
{
|
|
91
|
+
"references": [
|
|
92
|
+
{ "path": "packages/core" },
|
|
93
|
+
{ "path": "packages/server" },
|
|
94
|
+
{ "path": "packages/cli" }
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// packages/core/tsconfig.json
|
|
99
|
+
{
|
|
100
|
+
"compilerOptions": {
|
|
101
|
+
"composite": true,
|
|
102
|
+
"declaration": true,
|
|
103
|
+
"declarationMap": true
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### TypeScript 6.0+ Features
|
|
109
|
+
- **`#/` subpath imports**: `import { utils } from "#/lib/utils"` — no more `../../../` relative paths. Configure in `package.json` `"imports"` field.
|
|
110
|
+
- **`es2025` target**: Set, Iterator, Promise.withResolvers, RegExp v flag
|
|
111
|
+
- **`--noCheck`**: Skip type-checking in CI (when pre-checked) for faster builds
|
|
112
|
+
- **`--stableTypeOrdering`**: Deterministic declaration output for reproducible builds
|
|
113
|
+
- **`isolatedDeclarations`**: Generate `.d.ts` without type-checking — enables parallel builds
|
|
114
|
+
|
|
115
|
+
## Advanced Types
|
|
116
|
+
|
|
117
|
+
### Constrained Generics
|
|
118
|
+
```tsx
|
|
119
|
+
// Constrain to ensure property exists
|
|
120
|
+
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
|
|
121
|
+
return obj[key];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Constrain with interface
|
|
125
|
+
function merge<T extends Record<string, unknown>>(target: T, source: Partial<T>): T {
|
|
126
|
+
return { ...target, ...source };
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Conditional Types
|
|
131
|
+
```tsx
|
|
132
|
+
// Extract return type from async function
|
|
133
|
+
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
|
|
134
|
+
|
|
135
|
+
// Distributive conditional
|
|
136
|
+
type NonNullableDeep<T> = T extends null | undefined ? never : T extends object ? { [K in keyof T]: NonNullableDeep<T[K]> } : T;
|
|
137
|
+
|
|
138
|
+
// Infer from function
|
|
139
|
+
type Parameters<T> = T extends (...args: infer P) => unknown ? P : never;
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Mapped Types
|
|
143
|
+
```tsx
|
|
144
|
+
// Make all properties optional recursively
|
|
145
|
+
type DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P] };
|
|
146
|
+
|
|
147
|
+
// Remove readonly
|
|
148
|
+
type Mutable<T> = { -readonly [P in keyof T]: T[P] };
|
|
149
|
+
|
|
150
|
+
// Key remapping
|
|
151
|
+
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] };
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Template Literal Types
|
|
155
|
+
```tsx
|
|
156
|
+
// Event name typing
|
|
157
|
+
type EventName<T extends string> = `on${Capitalize<T>}`;
|
|
158
|
+
type Events = EventName<"click" | "focus" | "blur">; // "onClick" | "onFocus" | "onBlur"
|
|
159
|
+
|
|
160
|
+
// Route parameter extraction
|
|
161
|
+
type ExtractParams<T extends string> =
|
|
162
|
+
T extends `${string}:${infer Param}/${infer Rest}`
|
|
163
|
+
? Param | ExtractParams<Rest>
|
|
164
|
+
: T extends `${string}:${infer Param}`
|
|
165
|
+
? Param
|
|
166
|
+
: never;
|
|
167
|
+
type Params = ExtractParams<"/users/:id/posts/:postId">; // "id" | "postId"
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Utility Type Patterns
|
|
171
|
+
```tsx
|
|
172
|
+
// Deep readonly
|
|
173
|
+
type DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P] };
|
|
174
|
+
|
|
175
|
+
// Make specific keys required
|
|
176
|
+
type RequireKeys<T, K extends keyof T> = T & Required<Pick<T, K>>;
|
|
177
|
+
|
|
178
|
+
// Object paths as union
|
|
179
|
+
type Paths<T, D extends number = 3> = [D] extends [never]
|
|
180
|
+
? never
|
|
181
|
+
: T extends object
|
|
182
|
+
? { [K in keyof T]-?: K extends string ? `${K}` | `${K}.${Paths<T[K]>}` : never }[keyof T]
|
|
183
|
+
: never;
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Type Safety Patterns
|
|
187
|
+
|
|
188
|
+
### Branded Types
|
|
189
|
+
```tsx
|
|
190
|
+
// Prevent mixing IDs of different entities
|
|
191
|
+
type UserId = string & { readonly __brand: "UserId" };
|
|
192
|
+
type OrderId = string & { readonly __brand: "OrderId" };
|
|
193
|
+
|
|
194
|
+
function createUserId(id: string): UserId { return id as UserId; }
|
|
195
|
+
function createOrderId(id: string): OrderId { return id as OrderId; }
|
|
196
|
+
|
|
197
|
+
function getUser(id: UserId) { /* ... */ }
|
|
198
|
+
getUser(createOrderId("123")); // TS Error! OrderId is not UserId
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Discriminated Unions with Exhaustive Matching
|
|
202
|
+
```tsx
|
|
203
|
+
type Shape =
|
|
204
|
+
| { kind: "circle"; radius: number }
|
|
205
|
+
| { kind: "rect"; width: number; height: number }
|
|
206
|
+
| { kind: "triangle"; base: number; height: number };
|
|
207
|
+
|
|
208
|
+
function area(shape: Shape): number {
|
|
209
|
+
switch (shape.kind) {
|
|
210
|
+
case "circle": return Math.PI * shape.radius ** 2;
|
|
211
|
+
case "rect": return shape.width * shape.height;
|
|
212
|
+
case "triangle": return 0.5 * shape.base * shape.height;
|
|
213
|
+
default: {
|
|
214
|
+
const _exhaustive: never = shape; // Compile error if case missed
|
|
215
|
+
return _exhaustive;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Result Pattern
|
|
222
|
+
```tsx
|
|
223
|
+
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
|
|
224
|
+
|
|
225
|
+
function ok<T>(value: T): Result<T, never> { return { ok: true, value }; }
|
|
226
|
+
function err<E>(error: E): Result<never, E> { return { ok: false, error }; }
|
|
227
|
+
|
|
228
|
+
async function parseConfig(path: string): Promise<Result<Config, ParseError>> {
|
|
229
|
+
try {
|
|
230
|
+
const raw = await readFile(path, "utf-8");
|
|
231
|
+
return ok(JSON.parse(raw));
|
|
232
|
+
} catch (e) {
|
|
233
|
+
return err(new ParseError(`Failed to parse ${path}`, { cause: e }));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Usage — forces error handling
|
|
238
|
+
const result = await parseConfig("config.json");
|
|
239
|
+
if (!result.ok) {
|
|
240
|
+
console.error(result.error.message);
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
const config = result.value; // Type-narrowed to Config
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Async Patterns
|
|
247
|
+
|
|
248
|
+
### Parallel Execution
|
|
249
|
+
```tsx
|
|
250
|
+
// Promise.all — fail-fast on first rejection
|
|
251
|
+
const [users, posts] = await Promise.all([getUsers(), getPosts()]);
|
|
252
|
+
|
|
253
|
+
// Promise.allSettled — get all results regardless of failures
|
|
254
|
+
const results = await Promise.allSettled([getUsers(), getPosts(), getComments()]);
|
|
255
|
+
for (const r of results) {
|
|
256
|
+
if (r.status === "fulfilled") process(r.value);
|
|
257
|
+
else logError(r.reason);
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### AbortController
|
|
262
|
+
```tsx
|
|
263
|
+
async function fetchWithTimeout(url: string, timeoutMs: number): Promise<Response> {
|
|
264
|
+
const controller = new AbortController();
|
|
265
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
266
|
+
try {
|
|
267
|
+
return await fetch(url, { signal: controller.signal });
|
|
268
|
+
} finally {
|
|
269
|
+
clearTimeout(timeout);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Async Iterators
|
|
275
|
+
```tsx
|
|
276
|
+
async function* paginate<T>(fetcher: (page: number) => Promise<T[]>): AsyncGenerator<T> {
|
|
277
|
+
let page = 0;
|
|
278
|
+
while (true) {
|
|
279
|
+
const items = await fetcher(page++);
|
|
280
|
+
if (items.length === 0) break;
|
|
281
|
+
yield* items;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Usage
|
|
286
|
+
for await (const user of paginate(fetchUsersPage)) {
|
|
287
|
+
processUser(user);
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Module Organization
|
|
292
|
+
|
|
293
|
+
### Barrel File Performance
|
|
294
|
+
```tsx
|
|
295
|
+
// BAD — barrel files cause entire module tree to load
|
|
296
|
+
// index.ts
|
|
297
|
+
export * from "./userService";
|
|
298
|
+
export * from "./orderService";
|
|
299
|
+
export * from "./paymentService";
|
|
300
|
+
|
|
301
|
+
// GOOD — direct imports skip barrel overhead
|
|
302
|
+
import { UserService } from "./services/userService";
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Type-Only Imports
|
|
306
|
+
```tsx
|
|
307
|
+
// Always use type-only imports for types
|
|
308
|
+
import type { User, CreateUserDto } from "./types";
|
|
309
|
+
import { UserService } from "./userService";
|
|
310
|
+
// With verbatimModuleSyntax, this is enforced
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Re-export Patterns
|
|
314
|
+
```tsx
|
|
315
|
+
// Public API surface — explicit re-exports
|
|
316
|
+
export { UserService } from "./userService";
|
|
317
|
+
export type { User, CreateUserDto } from "./types";
|
|
318
|
+
// Don't export internal implementation details
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Memory & Runtime
|
|
322
|
+
|
|
323
|
+
### WeakRef for Caches
|
|
324
|
+
```tsx
|
|
325
|
+
class WeakCache<K extends object, V> {
|
|
326
|
+
private cache = new Map<K, WeakRef<V & object>>();
|
|
327
|
+
private registry = new FinalizationRegistry<K>(key => this.cache.delete(key));
|
|
328
|
+
|
|
329
|
+
set(key: K, value: V & object): void {
|
|
330
|
+
this.cache.set(key, new WeakRef(value));
|
|
331
|
+
this.registry.register(value, key);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
get(key: K): V | undefined {
|
|
335
|
+
return this.cache.get(key)?.deref();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Efficient Data Structures
|
|
341
|
+
```tsx
|
|
342
|
+
// Use Map/Set for frequent lookups
|
|
343
|
+
const userIndex = new Map<string, User>(); // O(1) lookup
|
|
344
|
+
const activeIds = new Set<string>(); // O(1) has/add/delete
|
|
345
|
+
|
|
346
|
+
// NOT plain objects for dynamic keys
|
|
347
|
+
const userIndex: Record<string, User> = {}; // Slower for frequent mutations
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Runtime Rules
|
|
351
|
+
- Minimize `Reflect.metadata` and decorator reflection — adds runtime overhead
|
|
352
|
+
- Prefer `structuredClone()` over `JSON.parse(JSON.stringify())` for deep cloning
|
|
353
|
+
- Use `Object.freeze()` for truly immutable constants
|
|
354
|
+
- Avoid `eval()`, `new Function()`, and dynamic `require()` — security + performance issues
|
|
355
|
+
|
|
356
|
+
## Modern JavaScript Patterns
|
|
357
|
+
|
|
358
|
+
### Nullish Handling
|
|
359
|
+
```tsx
|
|
360
|
+
const port = config.port ?? 3000; // Only for null/undefined
|
|
361
|
+
const name = user?.profile?.displayName; // Optional chaining
|
|
362
|
+
const normalized = (input ??= defaultValue); // Nullish assignment
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Immutable Updates
|
|
366
|
+
```tsx
|
|
367
|
+
// Object spread for shallow updates
|
|
368
|
+
const updated = { ...user, name: "New Name" };
|
|
369
|
+
|
|
370
|
+
// structuredClone for deep copy
|
|
371
|
+
const deep = structuredClone(complexObject);
|
|
372
|
+
|
|
373
|
+
// Array immutable operations
|
|
374
|
+
const added = [...items, newItem];
|
|
375
|
+
const removed = items.filter(i => i.id !== targetId);
|
|
376
|
+
const updated = items.map(i => i.id === targetId ? { ...i, ...changes } : i);
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Pipe Pattern
|
|
380
|
+
```tsx
|
|
381
|
+
function pipe<T>(value: T, ...fns: Array<(v: unknown) => unknown>): unknown {
|
|
382
|
+
return fns.reduce((acc, fn) => fn(acc), value as unknown);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Usage
|
|
386
|
+
const result = pipe(
|
|
387
|
+
rawData,
|
|
388
|
+
validate,
|
|
389
|
+
normalize,
|
|
390
|
+
transform,
|
|
391
|
+
serialize
|
|
392
|
+
);
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Decision Flow
|
|
396
|
+
|
|
397
|
+
When writing TypeScript:
|
|
398
|
+
|
|
399
|
+
1. **Interface or Type?** → Object shape = interface. Union/intersection/mapped = type.
|
|
400
|
+
2. **Generic needed?** → If the function works with multiple types and you need to preserve the type → yes. Otherwise keep it simple.
|
|
401
|
+
3. **Strict enough?** → Enable all strict flags. Use branded types for domain IDs. Use discriminated unions for variants.
|
|
402
|
+
4. **Performance?** → Check: Are there deep conditional types? Circular references? Massive unions? Simplify if so.
|
|
403
|
+
5. **Import style?** → `import type` for types. Direct imports, not barrel files. Named exports, not default.
|
|
404
|
+
6. **Error handling?** → Result pattern for expected errors. throw for unexpected/programmer errors.
|
|
405
|
+
7. **Async?** → Promise.all for parallel. AbortController for timeouts. AsyncGenerator for streams.
|