@aicgen/aicgen 1.0.0-beta.1 → 1.0.0-beta.2
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/.vs/ProjectSettings.json +2 -2
- package/.vs/VSWorkspaceState.json +15 -15
- package/.vs/aicgen.slnx/v18/DocumentLayout.json +53 -53
- package/assets/icon.svg +33 -33
- package/bun.lock +0 -43
- package/data/architecture/microservices/api-gateway.md +56 -56
- package/data/devops/observability.md +73 -73
- package/dist/index.js +9299 -9299
- package/package.json +2 -2
- package/.claude/agents/architecture-reviewer.md +0 -88
- package/.claude/agents/guideline-checker.md +0 -73
- package/.claude/agents/security-auditor.md +0 -108
- package/.claude/guidelines/api-design.md +0 -645
- package/.claude/guidelines/architecture.md +0 -2503
- package/.claude/guidelines/best-practices.md +0 -618
- package/.claude/guidelines/code-style.md +0 -304
- package/.claude/guidelines/design-patterns.md +0 -573
- package/.claude/guidelines/devops.md +0 -226
- package/.claude/guidelines/error-handling.md +0 -413
- package/.claude/guidelines/language.md +0 -782
- package/.claude/guidelines/performance.md +0 -706
- package/.claude/guidelines/security.md +0 -583
- package/.claude/guidelines/testing.md +0 -568
- package/.claude/settings.json +0 -98
- package/.claude/settings.local.json +0 -8
- package/.eslintrc.json +0 -28
- package/.github/workflows/release.yml +0 -180
- package/.github/workflows/test.yml +0 -81
- package/CONTRIBUTING.md +0 -821
- package/dist/commands/init.d.ts +0 -8
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -46
- package/dist/commands/init.js.map +0 -1
- package/dist/config/profiles.d.ts +0 -4
- package/dist/config/profiles.d.ts.map +0 -1
- package/dist/config/profiles.js +0 -30
- package/dist/config/profiles.js.map +0 -1
- package/dist/config/settings.d.ts +0 -7
- package/dist/config/settings.d.ts.map +0 -1
- package/dist/config/settings.js +0 -7
- package/dist/config/settings.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/models/guideline.d.ts +0 -15
- package/dist/models/guideline.d.ts.map +0 -1
- package/dist/models/guideline.js +0 -2
- package/dist/models/guideline.js.map +0 -1
- package/dist/models/preference.d.ts +0 -9
- package/dist/models/preference.d.ts.map +0 -1
- package/dist/models/preference.js +0 -2
- package/dist/models/preference.js.map +0 -1
- package/dist/models/profile.d.ts +0 -9
- package/dist/models/profile.d.ts.map +0 -1
- package/dist/models/profile.js +0 -2
- package/dist/models/profile.js.map +0 -1
- package/dist/models/project.d.ts +0 -13
- package/dist/models/project.d.ts.map +0 -1
- package/dist/models/project.js +0 -2
- package/dist/models/project.js.map +0 -1
- package/dist/services/ai/anthropic.d.ts +0 -7
- package/dist/services/ai/anthropic.d.ts.map +0 -1
- package/dist/services/ai/anthropic.js +0 -39
- package/dist/services/ai/anthropic.js.map +0 -1
- package/dist/services/generator.d.ts +0 -2
- package/dist/services/generator.d.ts.map +0 -1
- package/dist/services/generator.js +0 -4
- package/dist/services/generator.js.map +0 -1
- package/dist/services/learner.d.ts +0 -2
- package/dist/services/learner.d.ts.map +0 -1
- package/dist/services/learner.js +0 -4
- package/dist/services/learner.js.map +0 -1
- package/dist/services/scanner.d.ts +0 -3
- package/dist/services/scanner.d.ts.map +0 -1
- package/dist/services/scanner.js +0 -54
- package/dist/services/scanner.js.map +0 -1
- package/dist/utils/errors.d.ts +0 -15
- package/dist/utils/errors.d.ts.map +0 -1
- package/dist/utils/errors.js +0 -27
- package/dist/utils/errors.js.map +0 -1
- package/dist/utils/file.d.ts +0 -7
- package/dist/utils/file.d.ts.map +0 -1
- package/dist/utils/file.js +0 -32
- package/dist/utils/file.js.map +0 -1
- package/dist/utils/logger.d.ts +0 -6
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js +0 -17
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/path.d.ts +0 -6
- package/dist/utils/path.d.ts.map +0 -1
- package/dist/utils/path.js +0 -14
- package/dist/utils/path.js.map +0 -1
- package/docs/planning/memory-lane.md +0 -83
- package/packaging/linux/aicgen.spec +0 -23
- package/packaging/linux/control +0 -9
- package/packaging/macos/scripts/postinstall +0 -12
- package/packaging/windows/setup.nsi +0 -92
- package/scripts/add-categories.ts +0 -87
- package/scripts/build-binary.ts +0 -46
- package/scripts/embed-data.ts +0 -105
- package/scripts/generate-version.ts +0 -150
- package/scripts/test-decompress.ts +0 -27
- package/scripts/test-extract.ts +0 -31
- package/src/__tests__/services/assistant-file-writer.test.ts +0 -400
- package/src/__tests__/services/guideline-loader.test.ts +0 -281
- package/src/__tests__/services/tarball-extraction.test.ts +0 -125
- package/src/commands/add-guideline.ts +0 -296
- package/src/commands/clear.ts +0 -61
- package/src/commands/guideline-selector.ts +0 -123
- package/src/commands/init.ts +0 -645
- package/src/commands/quick-add.ts +0 -586
- package/src/commands/remove-guideline.ts +0 -152
- package/src/commands/stats.ts +0 -49
- package/src/commands/update.ts +0 -240
- package/src/config.ts +0 -82
- package/src/embedded-data.ts +0 -1492
- package/src/index.ts +0 -67
- package/src/models/profile.ts +0 -24
- package/src/models/project.ts +0 -43
- package/src/services/assistant-file-writer.ts +0 -612
- package/src/services/config-generator.ts +0 -150
- package/src/services/config-manager.ts +0 -70
- package/src/services/data-source.ts +0 -248
- package/src/services/first-run-init.ts +0 -148
- package/src/services/guideline-loader.ts +0 -311
- package/src/services/hook-generator.ts +0 -178
- package/src/services/subagent-generator.ts +0 -310
- package/src/utils/banner.ts +0 -66
- package/src/utils/errors.ts +0 -27
- package/src/utils/file.ts +0 -67
- package/src/utils/formatting.ts +0 -172
- package/src/utils/logger.ts +0 -89
- package/src/utils/path.ts +0 -17
- package/src/utils/wizard-state.ts +0 -132
- package/tsconfig.json +0 -25
- /package/{CLAUDE.md → claude.md} +0 -0
|
@@ -1,782 +0,0 @@
|
|
|
1
|
-
# Language
|
|
2
|
-
|
|
3
|
-
# TypeScript Fundamentals
|
|
4
|
-
|
|
5
|
-
## Strict Mode (Required)
|
|
6
|
-
|
|
7
|
-
Always use strict mode in `tsconfig.json`:
|
|
8
|
-
|
|
9
|
-
```json
|
|
10
|
-
{
|
|
11
|
-
"compilerOptions": {
|
|
12
|
-
"strict": true,
|
|
13
|
-
"noImplicitAny": true,
|
|
14
|
-
"strictNullChecks": true,
|
|
15
|
-
"strictFunctionTypes": true
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Type Annotations
|
|
21
|
-
|
|
22
|
-
Use explicit types for clarity:
|
|
23
|
-
|
|
24
|
-
```typescript
|
|
25
|
-
// Function signatures
|
|
26
|
-
function calculateTotal(items: CartItem[], taxRate: number): number {
|
|
27
|
-
const subtotal = items.reduce((sum, item) => sum + item.price, 0);
|
|
28
|
-
return subtotal * (1 + taxRate);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Variable declarations
|
|
32
|
-
const userName: string = "Alice";
|
|
33
|
-
const age: number = 30;
|
|
34
|
-
const isActive: boolean = true;
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## Avoid `any`
|
|
38
|
-
|
|
39
|
-
Never use `any` - use `unknown` with type guards:
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
// ❌ Bad
|
|
43
|
-
function processData(data: any) {
|
|
44
|
-
return data.value;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ✅ Good
|
|
48
|
-
function processData(data: unknown): string {
|
|
49
|
-
if (typeof data === 'object' && data !== null && 'value' in data) {
|
|
50
|
-
return String(data.value);
|
|
51
|
-
}
|
|
52
|
-
throw new Error('Invalid data structure');
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Type Guards
|
|
57
|
-
|
|
58
|
-
Implement custom type guards:
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
interface User {
|
|
62
|
-
id: string;
|
|
63
|
-
email: string;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function isUser(value: unknown): value is User {
|
|
67
|
-
return (
|
|
68
|
-
typeof value === 'object' &&
|
|
69
|
-
value !== null &&
|
|
70
|
-
'id' in value &&
|
|
71
|
-
'email' in value &&
|
|
72
|
-
typeof value.id === 'string' &&
|
|
73
|
-
typeof value.email === 'string'
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Usage
|
|
78
|
-
if (isUser(data)) {
|
|
79
|
-
console.log(data.email); // Type: User
|
|
80
|
-
}
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
## Naming Conventions
|
|
84
|
-
|
|
85
|
-
- Classes/Interfaces: `PascalCase`
|
|
86
|
-
- Functions/Variables: `camelCase`
|
|
87
|
-
- Constants: `UPPER_SNAKE_CASE`
|
|
88
|
-
- Files: `kebab-case.ts`
|
|
89
|
-
- No `I` prefix for interfaces
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
# Async/Await Patterns
|
|
95
|
-
|
|
96
|
-
## Prefer async/await
|
|
97
|
-
|
|
98
|
-
Always use async/await over promise chains:
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
// ✅ Good
|
|
102
|
-
async function fetchUser(id: string): Promise<User> {
|
|
103
|
-
const response = await fetch(`/api/users/${id}`);
|
|
104
|
-
if (!response.ok) {
|
|
105
|
-
throw new Error(`HTTP ${response.status}`);
|
|
106
|
-
}
|
|
107
|
-
return await response.json();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// ❌ Avoid
|
|
111
|
-
function fetchUser(id: string): Promise<User> {
|
|
112
|
-
return fetch(`/api/users/${id}`)
|
|
113
|
-
.then(res => res.json());
|
|
114
|
-
}
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
## Error Handling
|
|
118
|
-
|
|
119
|
-
Always wrap async operations in try/catch:
|
|
120
|
-
|
|
121
|
-
```typescript
|
|
122
|
-
async function safeOperation(): Promise<Result> {
|
|
123
|
-
try {
|
|
124
|
-
const data = await riskyOperation();
|
|
125
|
-
return { success: true, data };
|
|
126
|
-
} catch (error) {
|
|
127
|
-
logger.error('Operation failed', error);
|
|
128
|
-
return { success: false, error: error.message };
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
## Parallel Execution
|
|
134
|
-
|
|
135
|
-
Use `Promise.all()` for independent operations:
|
|
136
|
-
|
|
137
|
-
```typescript
|
|
138
|
-
// ✅ Good - parallel (fast)
|
|
139
|
-
const [users, posts, comments] = await Promise.all([
|
|
140
|
-
fetchUsers(),
|
|
141
|
-
fetchPosts(),
|
|
142
|
-
fetchComments()
|
|
143
|
-
]);
|
|
144
|
-
|
|
145
|
-
// ❌ Bad - sequential (slow)
|
|
146
|
-
const users = await fetchUsers();
|
|
147
|
-
const posts = await fetchPosts();
|
|
148
|
-
const comments = await fetchComments();
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
## Handling Failures
|
|
152
|
-
|
|
153
|
-
Use `Promise.allSettled()` when some failures are acceptable:
|
|
154
|
-
|
|
155
|
-
```typescript
|
|
156
|
-
const results = await Promise.allSettled([
|
|
157
|
-
fetchData1(),
|
|
158
|
-
fetchData2(),
|
|
159
|
-
fetchData3()
|
|
160
|
-
]);
|
|
161
|
-
|
|
162
|
-
results.forEach((result, index) => {
|
|
163
|
-
if (result.status === 'fulfilled') {
|
|
164
|
-
console.log(`Success ${index}:`, result.value);
|
|
165
|
-
} else {
|
|
166
|
-
console.error(`Failed ${index}:`, result.reason);
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
## Retry Pattern
|
|
172
|
-
|
|
173
|
-
Implement retry with exponential backoff:
|
|
174
|
-
|
|
175
|
-
```typescript
|
|
176
|
-
async function retryWithBackoff<T>(
|
|
177
|
-
fn: () => Promise<T>,
|
|
178
|
-
maxRetries: number = 3
|
|
179
|
-
): Promise<T> {
|
|
180
|
-
let lastError: Error;
|
|
181
|
-
|
|
182
|
-
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
183
|
-
try {
|
|
184
|
-
return await fn();
|
|
185
|
-
} catch (error) {
|
|
186
|
-
lastError = error as Error;
|
|
187
|
-
if (attempt < maxRetries - 1) {
|
|
188
|
-
const delay = 1000 * Math.pow(2, attempt);
|
|
189
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
throw lastError!;
|
|
195
|
-
}
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
---
|
|
200
|
-
|
|
201
|
-
# TypeScript Types & Interfaces
|
|
202
|
-
|
|
203
|
-
## Prefer Interfaces for Public APIs
|
|
204
|
-
|
|
205
|
-
```typescript
|
|
206
|
-
// ✅ Use interfaces for object shapes
|
|
207
|
-
interface User {
|
|
208
|
-
id: string;
|
|
209
|
-
name: string;
|
|
210
|
-
email: string;
|
|
211
|
-
createdAt: Date;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// ✅ Use type aliases for unions and complex types
|
|
215
|
-
type UserRole = 'admin' | 'editor' | 'viewer';
|
|
216
|
-
type ResponseHandler = (response: Response) => void;
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
## Discriminated Unions
|
|
220
|
-
|
|
221
|
-
```typescript
|
|
222
|
-
// ✅ Use discriminated unions for variant types
|
|
223
|
-
type Result<T> =
|
|
224
|
-
| { success: true; data: T }
|
|
225
|
-
| { success: false; error: string };
|
|
226
|
-
|
|
227
|
-
function handleResult(result: Result<User>) {
|
|
228
|
-
if (result.success) {
|
|
229
|
-
console.log(result.data.name); // TypeScript knows data exists
|
|
230
|
-
} else {
|
|
231
|
-
console.error(result.error); // TypeScript knows error exists
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
## Utility Types
|
|
237
|
-
|
|
238
|
-
```typescript
|
|
239
|
-
// Use built-in utility types
|
|
240
|
-
type PartialUser = Partial<User>; // All fields optional
|
|
241
|
-
type RequiredUser = Required<User>; // All fields required
|
|
242
|
-
type ReadonlyUser = Readonly<User>; // All fields readonly
|
|
243
|
-
type UserKeys = keyof User; // 'id' | 'name' | 'email' | 'createdAt'
|
|
244
|
-
type PickedUser = Pick<User, 'id' | 'name'>; // Only id and name
|
|
245
|
-
type OmittedUser = Omit<User, 'createdAt'>; // Everything except createdAt
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
## Type Guards
|
|
249
|
-
|
|
250
|
-
```typescript
|
|
251
|
-
// ✅ Use type guards for runtime checking
|
|
252
|
-
function isUser(value: unknown): value is User {
|
|
253
|
-
return (
|
|
254
|
-
typeof value === 'object' &&
|
|
255
|
-
value !== null &&
|
|
256
|
-
'id' in value &&
|
|
257
|
-
'email' in value
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Usage
|
|
262
|
-
const data: unknown = fetchData();
|
|
263
|
-
if (isUser(data)) {
|
|
264
|
-
console.log(data.email); // TypeScript knows it's a User
|
|
265
|
-
}
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
## Avoid `any`
|
|
269
|
-
|
|
270
|
-
```typescript
|
|
271
|
-
// ❌ Never use any
|
|
272
|
-
function process(data: any) {
|
|
273
|
-
return data.name; // No type safety
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// ✅ Use unknown with type guards
|
|
277
|
-
function process(data: unknown) {
|
|
278
|
-
if (isUser(data)) {
|
|
279
|
-
return data.name; // Type-safe
|
|
280
|
-
}
|
|
281
|
-
throw new Error('Invalid data');
|
|
282
|
-
}
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
---
|
|
287
|
-
|
|
288
|
-
# TypeScript Generics
|
|
289
|
-
|
|
290
|
-
## Basic Generic Functions
|
|
291
|
-
|
|
292
|
-
```typescript
|
|
293
|
-
// ✅ Generic function for type-safe operations
|
|
294
|
-
function first<T>(array: T[]): T | undefined {
|
|
295
|
-
return array[0];
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const numbers = [1, 2, 3];
|
|
299
|
-
const firstNumber = first(numbers); // Type: number | undefined
|
|
300
|
-
|
|
301
|
-
const users = [{ name: 'John' }];
|
|
302
|
-
const firstUser = first(users); // Type: { name: string } | undefined
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
## Generic Interfaces
|
|
306
|
-
|
|
307
|
-
```typescript
|
|
308
|
-
// ✅ Generic repository pattern
|
|
309
|
-
interface Repository<T> {
|
|
310
|
-
findById(id: string): Promise<T | null>;
|
|
311
|
-
findAll(): Promise<T[]>;
|
|
312
|
-
create(entity: Omit<T, 'id'>): Promise<T>;
|
|
313
|
-
update(id: string, data: Partial<T>): Promise<T>;
|
|
314
|
-
delete(id: string): Promise<void>;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
class UserRepository implements Repository<User> {
|
|
318
|
-
async findById(id: string): Promise<User | null> {
|
|
319
|
-
return this.db.users.findUnique({ where: { id } });
|
|
320
|
-
}
|
|
321
|
-
// ... other methods
|
|
322
|
-
}
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
## Generic Constraints
|
|
326
|
-
|
|
327
|
-
```typescript
|
|
328
|
-
// ✅ Constrain generic types
|
|
329
|
-
interface HasId {
|
|
330
|
-
id: string;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function getById<T extends HasId>(items: T[], id: string): T | undefined {
|
|
334
|
-
return items.find(item => item.id === id);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Works with any type that has an id
|
|
338
|
-
getById(users, '123');
|
|
339
|
-
getById(products, '456');
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
## Mapped Types
|
|
343
|
-
|
|
344
|
-
```typescript
|
|
345
|
-
// ✅ Create transformed types
|
|
346
|
-
type Nullable<T> = {
|
|
347
|
-
[K in keyof T]: T[K] | null;
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
type NullableUser = Nullable<User>;
|
|
351
|
-
// { id: string | null; name: string | null; ... }
|
|
352
|
-
|
|
353
|
-
// ✅ Conditional types
|
|
354
|
-
type ExtractArrayType<T> = T extends Array<infer U> ? U : never;
|
|
355
|
-
|
|
356
|
-
type StringArrayElement = ExtractArrayType<string[]>; // string
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
## Default Generic Parameters
|
|
360
|
-
|
|
361
|
-
```typescript
|
|
362
|
-
// ✅ Provide defaults for flexibility
|
|
363
|
-
interface ApiResponse<T = unknown, E = Error> {
|
|
364
|
-
data?: T;
|
|
365
|
-
error?: E;
|
|
366
|
-
status: number;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// Can use with or without type parameters
|
|
370
|
-
const response1: ApiResponse<User> = { data: user, status: 200 };
|
|
371
|
-
const response2: ApiResponse = { status: 500, error: new Error('Failed') };
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
---
|
|
376
|
-
|
|
377
|
-
# TypeScript Error Handling
|
|
378
|
-
|
|
379
|
-
## Custom Error Classes
|
|
380
|
-
|
|
381
|
-
```typescript
|
|
382
|
-
// ✅ Create structured error hierarchy
|
|
383
|
-
class AppError extends Error {
|
|
384
|
-
constructor(
|
|
385
|
-
message: string,
|
|
386
|
-
public statusCode: number = 500,
|
|
387
|
-
public code: string = 'INTERNAL_ERROR',
|
|
388
|
-
public details?: unknown
|
|
389
|
-
) {
|
|
390
|
-
super(message);
|
|
391
|
-
this.name = this.constructor.name;
|
|
392
|
-
Error.captureStackTrace(this, this.constructor);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
class NotFoundError extends AppError {
|
|
397
|
-
constructor(resource: string, id: string) {
|
|
398
|
-
super(`${resource} with id ${id} not found`, 404, 'NOT_FOUND', { resource, id });
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
class ValidationError extends AppError {
|
|
403
|
-
constructor(message: string, details: unknown) {
|
|
404
|
-
super(message, 400, 'VALIDATION_ERROR', details);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
## Async Error Handling
|
|
410
|
-
|
|
411
|
-
```typescript
|
|
412
|
-
// ✅ Always handle promise rejections
|
|
413
|
-
async function fetchUser(id: string): Promise<User> {
|
|
414
|
-
try {
|
|
415
|
-
const response = await api.get(`/users/${id}`);
|
|
416
|
-
return response.data;
|
|
417
|
-
} catch (error) {
|
|
418
|
-
if (error instanceof ApiError && error.status === 404) {
|
|
419
|
-
throw new NotFoundError('User', id);
|
|
420
|
-
}
|
|
421
|
-
throw new AppError('Failed to fetch user', 500, 'FETCH_ERROR', { userId: id });
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// ✅ Use wrapper for Express async handlers
|
|
426
|
-
const asyncHandler = (fn: RequestHandler) => {
|
|
427
|
-
return (req: Request, res: Response, next: NextFunction) => {
|
|
428
|
-
Promise.resolve(fn(req, res, next)).catch(next);
|
|
429
|
-
};
|
|
430
|
-
};
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
## Result Type Pattern
|
|
434
|
-
|
|
435
|
-
```typescript
|
|
436
|
-
// ✅ Explicit success/failure without exceptions
|
|
437
|
-
type Result<T, E = Error> =
|
|
438
|
-
| { success: true; value: T }
|
|
439
|
-
| { success: false; error: E };
|
|
440
|
-
|
|
441
|
-
function parseJSON<T>(json: string): Result<T, string> {
|
|
442
|
-
try {
|
|
443
|
-
return { success: true, value: JSON.parse(json) };
|
|
444
|
-
} catch {
|
|
445
|
-
return { success: false, error: 'Invalid JSON' };
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// Usage
|
|
450
|
-
const result = parseJSON<User>(data);
|
|
451
|
-
if (result.success) {
|
|
452
|
-
console.log(result.value.name);
|
|
453
|
-
} else {
|
|
454
|
-
console.error(result.error);
|
|
455
|
-
}
|
|
456
|
-
```
|
|
457
|
-
|
|
458
|
-
## Centralized Error Handler
|
|
459
|
-
|
|
460
|
-
```typescript
|
|
461
|
-
// ✅ Express error middleware
|
|
462
|
-
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
|
|
463
|
-
if (err instanceof AppError) {
|
|
464
|
-
return res.status(err.statusCode).json({
|
|
465
|
-
error: { message: err.message, code: err.code, details: err.details }
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
console.error('Unexpected error:', err);
|
|
470
|
-
res.status(500).json({
|
|
471
|
-
error: { message: 'Internal server error', code: 'INTERNAL_ERROR' }
|
|
472
|
-
});
|
|
473
|
-
});
|
|
474
|
-
```
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
---
|
|
478
|
-
|
|
479
|
-
# TypeScript Testing
|
|
480
|
-
|
|
481
|
-
## Test Structure: Arrange-Act-Assert
|
|
482
|
-
|
|
483
|
-
```typescript
|
|
484
|
-
describe('UserService', () => {
|
|
485
|
-
describe('createUser', () => {
|
|
486
|
-
it('should create user with hashed password', async () => {
|
|
487
|
-
// Arrange
|
|
488
|
-
const userData = { email: 'test@example.com', password: 'password123' };
|
|
489
|
-
const mockRepo = { save: jest.fn().mockResolvedValue({ id: '1', ...userData }) };
|
|
490
|
-
const service = new UserService(mockRepo);
|
|
491
|
-
|
|
492
|
-
// Act
|
|
493
|
-
const result = await service.createUser(userData);
|
|
494
|
-
|
|
495
|
-
// Assert
|
|
496
|
-
expect(result.id).toBe('1');
|
|
497
|
-
expect(mockRepo.save).toHaveBeenCalledWith(
|
|
498
|
-
expect.objectContaining({ email: 'test@example.com' })
|
|
499
|
-
);
|
|
500
|
-
});
|
|
501
|
-
});
|
|
502
|
-
});
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
## Test Observable Behavior, Not Implementation
|
|
506
|
-
|
|
507
|
-
```typescript
|
|
508
|
-
// ❌ Testing implementation details
|
|
509
|
-
it('should call validateEmail method', () => {
|
|
510
|
-
const spy = jest.spyOn(service, 'validateEmail');
|
|
511
|
-
service.createUser({ email: 'test@example.com' });
|
|
512
|
-
expect(spy).toHaveBeenCalled(); // Brittle - breaks if refactored
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
// ✅ Testing observable behavior
|
|
516
|
-
it('should reject invalid email', async () => {
|
|
517
|
-
await expect(
|
|
518
|
-
service.createUser({ email: 'invalid' })
|
|
519
|
-
).rejects.toThrow('Invalid email');
|
|
520
|
-
});
|
|
521
|
-
```
|
|
522
|
-
|
|
523
|
-
## Test Doubles
|
|
524
|
-
|
|
525
|
-
```typescript
|
|
526
|
-
// Stub: Returns canned responses
|
|
527
|
-
const stubDatabase = {
|
|
528
|
-
findUser: () => ({ id: '1', name: 'Test User' })
|
|
529
|
-
};
|
|
530
|
-
|
|
531
|
-
// Mock: Pre-programmed with expectations
|
|
532
|
-
const mockPayment = {
|
|
533
|
-
charge: jest.fn()
|
|
534
|
-
.mockResolvedValueOnce({ success: true })
|
|
535
|
-
.mockResolvedValueOnce({ success: false })
|
|
536
|
-
};
|
|
537
|
-
|
|
538
|
-
// Fake: Working implementation (not for production)
|
|
539
|
-
class FakeDatabase implements Database {
|
|
540
|
-
private data = new Map<string, any>();
|
|
541
|
-
|
|
542
|
-
async save(id: string, data: any) { this.data.set(id, data); }
|
|
543
|
-
async find(id: string) { return this.data.get(id); }
|
|
544
|
-
}
|
|
545
|
-
```
|
|
546
|
-
|
|
547
|
-
## One Test Per Condition
|
|
548
|
-
|
|
549
|
-
```typescript
|
|
550
|
-
// ❌ Multiple assertions for different scenarios
|
|
551
|
-
it('should validate user input', () => {
|
|
552
|
-
expect(() => validate({ age: -1 })).toThrow();
|
|
553
|
-
expect(() => validate({ age: 200 })).toThrow();
|
|
554
|
-
expect(() => validate({ name: '' })).toThrow();
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
// ✅ One test per condition
|
|
558
|
-
it('should reject negative age', () => {
|
|
559
|
-
expect(() => validate({ age: -1 })).toThrow('Age must be positive');
|
|
560
|
-
});
|
|
561
|
-
|
|
562
|
-
it('should reject age over 150', () => {
|
|
563
|
-
expect(() => validate({ age: 200 })).toThrow('Age must be under 150');
|
|
564
|
-
});
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
## Keep Tests Independent
|
|
568
|
-
|
|
569
|
-
```typescript
|
|
570
|
-
// ✅ Each test is self-contained
|
|
571
|
-
it('should update user', async () => {
|
|
572
|
-
const user = await service.createUser({ name: 'Test' });
|
|
573
|
-
const updated = await service.updateUser(user.id, { name: 'Updated' });
|
|
574
|
-
expect(updated.name).toBe('Updated');
|
|
575
|
-
});
|
|
576
|
-
```
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
---
|
|
580
|
-
|
|
581
|
-
# TypeScript Configuration
|
|
582
|
-
|
|
583
|
-
## tsconfig.json Best Practices
|
|
584
|
-
|
|
585
|
-
```json
|
|
586
|
-
{
|
|
587
|
-
"compilerOptions": {
|
|
588
|
-
// Strict type checking
|
|
589
|
-
"strict": true,
|
|
590
|
-
"noImplicitAny": true,
|
|
591
|
-
"strictNullChecks": true,
|
|
592
|
-
"strictFunctionTypes": true,
|
|
593
|
-
"strictBindCallApply": true,
|
|
594
|
-
"strictPropertyInitialization": true,
|
|
595
|
-
"noImplicitThis": true,
|
|
596
|
-
"alwaysStrict": true,
|
|
597
|
-
|
|
598
|
-
// Additional checks
|
|
599
|
-
"noUnusedLocals": true,
|
|
600
|
-
"noUnusedParameters": true,
|
|
601
|
-
"noImplicitReturns": true,
|
|
602
|
-
"noFallthroughCasesInSwitch": true,
|
|
603
|
-
"noUncheckedIndexedAccess": true,
|
|
604
|
-
|
|
605
|
-
// Module resolution
|
|
606
|
-
"module": "ESNext",
|
|
607
|
-
"moduleResolution": "bundler",
|
|
608
|
-
"esModuleInterop": true,
|
|
609
|
-
"allowSyntheticDefaultImports": true,
|
|
610
|
-
"resolveJsonModule": true,
|
|
611
|
-
|
|
612
|
-
// Output
|
|
613
|
-
"target": "ES2022",
|
|
614
|
-
"outDir": "./dist",
|
|
615
|
-
"declaration": true,
|
|
616
|
-
"declarationMap": true,
|
|
617
|
-
"sourceMap": true,
|
|
618
|
-
|
|
619
|
-
// Path aliases
|
|
620
|
-
"baseUrl": ".",
|
|
621
|
-
"paths": {
|
|
622
|
-
"@/*": ["src/*"],
|
|
623
|
-
"@services/*": ["src/services/*"],
|
|
624
|
-
"@models/*": ["src/models/*"]
|
|
625
|
-
}
|
|
626
|
-
},
|
|
627
|
-
"include": ["src/**/*"],
|
|
628
|
-
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
629
|
-
}
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
## Path Aliases Setup
|
|
633
|
-
|
|
634
|
-
```typescript
|
|
635
|
-
// With path aliases configured:
|
|
636
|
-
import { UserService } from '@services/user';
|
|
637
|
-
import { User } from '@models/user';
|
|
638
|
-
|
|
639
|
-
// Instead of relative paths:
|
|
640
|
-
import { UserService } from '../../../services/user';
|
|
641
|
-
```
|
|
642
|
-
|
|
643
|
-
## Project References (Monorepo)
|
|
644
|
-
|
|
645
|
-
```json
|
|
646
|
-
// packages/shared/tsconfig.json
|
|
647
|
-
{
|
|
648
|
-
"compilerOptions": {
|
|
649
|
-
"composite": true,
|
|
650
|
-
"outDir": "./dist"
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
// packages/api/tsconfig.json
|
|
655
|
-
{
|
|
656
|
-
"extends": "../../tsconfig.base.json",
|
|
657
|
-
"references": [
|
|
658
|
-
{ "path": "../shared" }
|
|
659
|
-
]
|
|
660
|
-
}
|
|
661
|
-
```
|
|
662
|
-
|
|
663
|
-
## Environment-Specific Configs
|
|
664
|
-
|
|
665
|
-
```json
|
|
666
|
-
// tsconfig.build.json - for production builds
|
|
667
|
-
{
|
|
668
|
-
"extends": "./tsconfig.json",
|
|
669
|
-
"compilerOptions": {
|
|
670
|
-
"sourceMap": false,
|
|
671
|
-
"removeComments": true
|
|
672
|
-
},
|
|
673
|
-
"exclude": ["**/*.test.ts", "**/*.spec.ts"]
|
|
674
|
-
}
|
|
675
|
-
```
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
---
|
|
679
|
-
|
|
680
|
-
# TypeScript Performance
|
|
681
|
-
|
|
682
|
-
## Choose Right Data Structures
|
|
683
|
-
|
|
684
|
-
```typescript
|
|
685
|
-
// ❌ Array for lookups (O(n))
|
|
686
|
-
const users: User[] = [];
|
|
687
|
-
const findUser = (id: string) => users.find(u => u.id === id);
|
|
688
|
-
|
|
689
|
-
// ✅ Map for O(1) lookups
|
|
690
|
-
const users = new Map<string, User>();
|
|
691
|
-
const findUser = (id: string) => users.get(id);
|
|
692
|
-
|
|
693
|
-
// ❌ Array for membership checks
|
|
694
|
-
const hasPermission = (perms: string[], perm: string) => perms.includes(perm);
|
|
695
|
-
|
|
696
|
-
// ✅ Set for O(1) membership
|
|
697
|
-
const hasPermission = (perms: Set<string>, perm: string) => perms.has(perm);
|
|
698
|
-
```
|
|
699
|
-
|
|
700
|
-
## Avoid N+1 Queries
|
|
701
|
-
|
|
702
|
-
```typescript
|
|
703
|
-
// ❌ N+1 queries
|
|
704
|
-
const getOrdersWithCustomers = async () => {
|
|
705
|
-
const orders = await db.query('SELECT * FROM orders');
|
|
706
|
-
for (const order of orders) {
|
|
707
|
-
order.customer = await db.query('SELECT * FROM customers WHERE id = ?', [order.customerId]);
|
|
708
|
-
}
|
|
709
|
-
return orders;
|
|
710
|
-
};
|
|
711
|
-
|
|
712
|
-
// ✅ Single JOIN query
|
|
713
|
-
const getOrdersWithCustomers = async () => {
|
|
714
|
-
return db.query(`
|
|
715
|
-
SELECT orders.*, customers.name as customer_name
|
|
716
|
-
FROM orders
|
|
717
|
-
JOIN customers ON orders.customer_id = customers.id
|
|
718
|
-
`);
|
|
719
|
-
};
|
|
720
|
-
|
|
721
|
-
// ✅ Using ORM with eager loading
|
|
722
|
-
const getOrdersWithCustomers = async () => {
|
|
723
|
-
return orderRepository.find({ relations: ['customer'] });
|
|
724
|
-
};
|
|
725
|
-
```
|
|
726
|
-
|
|
727
|
-
## Parallel Execution
|
|
728
|
-
|
|
729
|
-
```typescript
|
|
730
|
-
// ❌ Sequential (slow)
|
|
731
|
-
const getUserData = async (userId: string) => {
|
|
732
|
-
const user = await fetchUser(userId); // 100ms
|
|
733
|
-
const posts = await fetchPosts(userId); // 150ms
|
|
734
|
-
const comments = await fetchComments(userId); // 120ms
|
|
735
|
-
return { user, posts, comments }; // Total: 370ms
|
|
736
|
-
};
|
|
737
|
-
|
|
738
|
-
// ✅ Parallel (fast)
|
|
739
|
-
const getUserData = async (userId: string) => {
|
|
740
|
-
const [user, posts, comments] = await Promise.all([
|
|
741
|
-
fetchUser(userId),
|
|
742
|
-
fetchPosts(userId),
|
|
743
|
-
fetchComments(userId)
|
|
744
|
-
]);
|
|
745
|
-
return { user, posts, comments }; // Total: 150ms
|
|
746
|
-
};
|
|
747
|
-
```
|
|
748
|
-
|
|
749
|
-
## Memoization
|
|
750
|
-
|
|
751
|
-
```typescript
|
|
752
|
-
const memoize = <T extends (...args: any[]) => any>(fn: T): T => {
|
|
753
|
-
const cache = new Map<string, ReturnType<T>>();
|
|
754
|
-
|
|
755
|
-
return ((...args: any[]) => {
|
|
756
|
-
const key = JSON.stringify(args);
|
|
757
|
-
if (cache.has(key)) return cache.get(key);
|
|
758
|
-
const result = fn(...args);
|
|
759
|
-
cache.set(key, result);
|
|
760
|
-
return result;
|
|
761
|
-
}) as T;
|
|
762
|
-
};
|
|
763
|
-
|
|
764
|
-
const expensiveCalc = memoize((n: number) => {
|
|
765
|
-
// Expensive computation
|
|
766
|
-
return result;
|
|
767
|
-
});
|
|
768
|
-
```
|
|
769
|
-
|
|
770
|
-
## Batch Processing
|
|
771
|
-
|
|
772
|
-
```typescript
|
|
773
|
-
// ✅ Process in batches
|
|
774
|
-
const processUsers = async (userIds: string[]) => {
|
|
775
|
-
const BATCH_SIZE = 50;
|
|
776
|
-
|
|
777
|
-
for (let i = 0; i < userIds.length; i += BATCH_SIZE) {
|
|
778
|
-
const batch = userIds.slice(i, i + BATCH_SIZE);
|
|
779
|
-
await Promise.all(batch.map(id => updateUser(id)));
|
|
780
|
-
}
|
|
781
|
-
};
|
|
782
|
-
```
|