@aicgen/aicgen 1.0.0 → 1.0.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.
@@ -1,4843 +0,0 @@
1
- # Gemini Development Guide
2
-
3
- **Role:** You are an expert software engineer specialized in typescript.
4
- **Objective:** Write clean, efficient, and maintainable code following the guidelines below.
5
-
6
- ## Guiding Principles
7
- 1. **Quality First**: Prioritize code quality and maintainability over speed.
8
- 2. **Step-by-Step**: Think through problems systematically.
9
- 3. **Verify**: Double-check your code against the guidelines.
10
-
11
-
12
-
13
- ## Language
14
-
15
- # TypeScript Fundamentals
16
-
17
- ## Strict Mode (Required)
18
-
19
- Always use strict mode in `tsconfig.json`:
20
-
21
- ```json
22
- {
23
- "compilerOptions": {
24
- "strict": true,
25
- "noImplicitAny": true,
26
- "strictNullChecks": true,
27
- "strictFunctionTypes": true
28
- }
29
- }
30
- ```
31
-
32
- ## Type Annotations
33
-
34
- Use explicit types for clarity:
35
-
36
- ```typescript
37
- // Function signatures
38
- function calculateTotal(items: CartItem[], taxRate: number): number {
39
- const subtotal = items.reduce((sum, item) => sum + item.price, 0);
40
- return subtotal * (1 + taxRate);
41
- }
42
-
43
- // Variable declarations
44
- const userName: string = "Alice";
45
- const age: number = 30;
46
- const isActive: boolean = true;
47
- ```
48
-
49
- ## Avoid `any`
50
-
51
- Never use `any` - use `unknown` with type guards:
52
-
53
- ```typescript
54
- // ❌ Bad
55
- function processData(data: any) {
56
- return data.value;
57
- }
58
-
59
- // ✅ Good
60
- function processData(data: unknown): string {
61
- if (typeof data === 'object' && data !== null && 'value' in data) {
62
- return String(data.value);
63
- }
64
- throw new Error('Invalid data structure');
65
- }
66
- ```
67
-
68
- ## Type Guards
69
-
70
- Implement custom type guards:
71
-
72
- ```typescript
73
- interface User {
74
- id: string;
75
- email: string;
76
- }
77
-
78
- function isUser(value: unknown): value is User {
79
- return (
80
- typeof value === 'object' &&
81
- value !== null &&
82
- 'id' in value &&
83
- 'email' in value &&
84
- typeof value.id === 'string' &&
85
- typeof value.email === 'string'
86
- );
87
- }
88
-
89
- // Usage
90
- if (isUser(data)) {
91
- console.log(data.email); // Type: User
92
- }
93
- ```
94
-
95
- ## Naming Conventions
96
-
97
- - Classes/Interfaces: `PascalCase`
98
- - Functions/Variables: `camelCase`
99
- - Constants: `UPPER_SNAKE_CASE`
100
- - Files: `kebab-case.ts`
101
- - No `I` prefix for interfaces
102
-
103
-
104
- # Async/Await Patterns
105
-
106
- ## Prefer async/await
107
-
108
- Always use async/await over promise chains:
109
-
110
- ```typescript
111
- // ✅ Good
112
- async function fetchUser(id: string): Promise<User> {
113
- const response = await fetch(`/api/users/${id}`);
114
- if (!response.ok) {
115
- throw new Error(`HTTP ${response.status}`);
116
- }
117
- return await response.json();
118
- }
119
-
120
- // ❌ Avoid
121
- function fetchUser(id: string): Promise<User> {
122
- return fetch(`/api/users/${id}`)
123
- .then(res => res.json());
124
- }
125
- ```
126
-
127
- ## Error Handling
128
-
129
- Always wrap async operations in try/catch:
130
-
131
- ```typescript
132
- async function safeOperation(): Promise<Result> {
133
- try {
134
- const data = await riskyOperation();
135
- return { success: true, data };
136
- } catch (error) {
137
- logger.error('Operation failed', error);
138
- return { success: false, error: error.message };
139
- }
140
- }
141
- ```
142
-
143
- ## Parallel Execution
144
-
145
- Use `Promise.all()` for independent operations:
146
-
147
- ```typescript
148
- // ✅ Good - parallel (fast)
149
- const [users, posts, comments] = await Promise.all([
150
- fetchUsers(),
151
- fetchPosts(),
152
- fetchComments()
153
- ]);
154
-
155
- // ❌ Bad - sequential (slow)
156
- const users = await fetchUsers();
157
- const posts = await fetchPosts();
158
- const comments = await fetchComments();
159
- ```
160
-
161
- ## Handling Failures
162
-
163
- Use `Promise.allSettled()` when some failures are acceptable:
164
-
165
- ```typescript
166
- const results = await Promise.allSettled([
167
- fetchData1(),
168
- fetchData2(),
169
- fetchData3()
170
- ]);
171
-
172
- results.forEach((result, index) => {
173
- if (result.status === 'fulfilled') {
174
- console.log(`Success ${index}:`, result.value);
175
- } else {
176
- console.error(`Failed ${index}:`, result.reason);
177
- }
178
- });
179
- ```
180
-
181
- ## Retry Pattern
182
-
183
- Implement retry with exponential backoff:
184
-
185
- ```typescript
186
- async function retryWithBackoff<T>(
187
- fn: () => Promise<T>,
188
- maxRetries: number = 3
189
- ): Promise<T> {
190
- let lastError: Error;
191
-
192
- for (let attempt = 0; attempt < maxRetries; attempt++) {
193
- try {
194
- return await fn();
195
- } catch (error) {
196
- lastError = error as Error;
197
- if (attempt < maxRetries - 1) {
198
- const delay = 1000 * Math.pow(2, attempt);
199
- await new Promise(resolve => setTimeout(resolve, delay));
200
- }
201
- }
202
- }
203
-
204
- throw lastError!;
205
- }
206
- ```
207
-
208
-
209
- # TypeScript Types & Interfaces
210
-
211
- ## Prefer Interfaces for Public APIs
212
-
213
- ```typescript
214
- // ✅ Use interfaces for object shapes
215
- interface User {
216
- id: string;
217
- name: string;
218
- email: string;
219
- createdAt: Date;
220
- }
221
-
222
- // ✅ Use type aliases for unions and complex types
223
- type UserRole = 'admin' | 'editor' | 'viewer';
224
- type ResponseHandler = (response: Response) => void;
225
- ```
226
-
227
- ## Discriminated Unions
228
-
229
- ```typescript
230
- // ✅ Use discriminated unions for variant types
231
- type Result<T> =
232
- | { success: true; data: T }
233
- | { success: false; error: string };
234
-
235
- function handleResult(result: Result<User>) {
236
- if (result.success) {
237
- console.log(result.data.name); // TypeScript knows data exists
238
- } else {
239
- console.error(result.error); // TypeScript knows error exists
240
- }
241
- }
242
- ```
243
-
244
- ## Utility Types
245
-
246
- ```typescript
247
- // Use built-in utility types
248
- type PartialUser = Partial<User>; // All fields optional
249
- type RequiredUser = Required<User>; // All fields required
250
- type ReadonlyUser = Readonly<User>; // All fields readonly
251
- type UserKeys = keyof User; // 'id' | 'name' | 'email' | 'createdAt'
252
- type PickedUser = Pick<User, 'id' | 'name'>; // Only id and name
253
- type OmittedUser = Omit<User, 'createdAt'>; // Everything except createdAt
254
- ```
255
-
256
- ## Type Guards
257
-
258
- ```typescript
259
- // ✅ Use type guards for runtime checking
260
- function isUser(value: unknown): value is User {
261
- return (
262
- typeof value === 'object' &&
263
- value !== null &&
264
- 'id' in value &&
265
- 'email' in value
266
- );
267
- }
268
-
269
- // Usage
270
- const data: unknown = fetchData();
271
- if (isUser(data)) {
272
- console.log(data.email); // TypeScript knows it's a User
273
- }
274
- ```
275
-
276
- ## Avoid `any`
277
-
278
- ```typescript
279
- // ❌ Never use any
280
- function process(data: any) {
281
- return data.name; // No type safety
282
- }
283
-
284
- // ✅ Use unknown with type guards
285
- function process(data: unknown) {
286
- if (isUser(data)) {
287
- return data.name; // Type-safe
288
- }
289
- throw new Error('Invalid data');
290
- }
291
- ```
292
-
293
-
294
- # TypeScript Error Handling
295
-
296
- ## Custom Error Classes
297
-
298
- ```typescript
299
- // ✅ Create structured error hierarchy
300
- class AppError extends Error {
301
- constructor(
302
- message: string,
303
- public statusCode: number = 500,
304
- public code: string = 'INTERNAL_ERROR',
305
- public details?: unknown
306
- ) {
307
- super(message);
308
- this.name = this.constructor.name;
309
- Error.captureStackTrace(this, this.constructor);
310
- }
311
- }
312
-
313
- class NotFoundError extends AppError {
314
- constructor(resource: string, id: string) {
315
- super(`${resource} with id ${id} not found`, 404, 'NOT_FOUND', { resource, id });
316
- }
317
- }
318
-
319
- class ValidationError extends AppError {
320
- constructor(message: string, details: unknown) {
321
- super(message, 400, 'VALIDATION_ERROR', details);
322
- }
323
- }
324
- ```
325
-
326
- ## Async Error Handling
327
-
328
- ```typescript
329
- // ✅ Always handle promise rejections
330
- async function fetchUser(id: string): Promise<User> {
331
- try {
332
- const response = await api.get(`/users/${id}`);
333
- return response.data;
334
- } catch (error) {
335
- if (error instanceof ApiError && error.status === 404) {
336
- throw new NotFoundError('User', id);
337
- }
338
- throw new AppError('Failed to fetch user', 500, 'FETCH_ERROR', { userId: id });
339
- }
340
- }
341
-
342
- // ✅ Use wrapper for Express async handlers
343
- const asyncHandler = (fn: RequestHandler) => {
344
- return (req: Request, res: Response, next: NextFunction) => {
345
- Promise.resolve(fn(req, res, next)).catch(next);
346
- };
347
- };
348
- ```
349
-
350
- ## Result Type Pattern
351
-
352
- ```typescript
353
- // ✅ Explicit success/failure without exceptions
354
- type Result<T, E = Error> =
355
- | { success: true; value: T }
356
- | { success: false; error: E };
357
-
358
- function parseJSON<T>(json: string): Result<T, string> {
359
- try {
360
- return { success: true, value: JSON.parse(json) };
361
- } catch {
362
- return { success: false, error: 'Invalid JSON' };
363
- }
364
- }
365
-
366
- // Usage
367
- const result = parseJSON<User>(data);
368
- if (result.success) {
369
- console.log(result.value.name);
370
- } else {
371
- console.error(result.error);
372
- }
373
- ```
374
-
375
- ## Centralized Error Handler
376
-
377
- ```typescript
378
- // ✅ Express error middleware
379
- app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
380
- if (err instanceof AppError) {
381
- return res.status(err.statusCode).json({
382
- error: { message: err.message, code: err.code, details: err.details }
383
- });
384
- }
385
-
386
- console.error('Unexpected error:', err);
387
- res.status(500).json({
388
- error: { message: 'Internal server error', code: 'INTERNAL_ERROR' }
389
- });
390
- });
391
- ```
392
-
393
-
394
- # TypeScript Testing
395
-
396
- ## Test Structure: Arrange-Act-Assert
397
-
398
- ```typescript
399
- describe('UserService', () => {
400
- describe('createUser', () => {
401
- it('should create user with hashed password', async () => {
402
- // Arrange
403
- const userData = { email: 'test@example.com', password: 'password123' };
404
- const mockRepo = { save: jest.fn().mockResolvedValue({ id: '1', ...userData }) };
405
- const service = new UserService(mockRepo);
406
-
407
- // Act
408
- const result = await service.createUser(userData);
409
-
410
- // Assert
411
- expect(result.id).toBe('1');
412
- expect(mockRepo.save).toHaveBeenCalledWith(
413
- expect.objectContaining({ email: 'test@example.com' })
414
- );
415
- });
416
- });
417
- });
418
- ```
419
-
420
- ## Test Observable Behavior, Not Implementation
421
-
422
- ```typescript
423
- // ❌ Testing implementation details
424
- it('should call validateEmail method', () => {
425
- const spy = jest.spyOn(service, 'validateEmail');
426
- service.createUser({ email: 'test@example.com' });
427
- expect(spy).toHaveBeenCalled(); // Brittle - breaks if refactored
428
- });
429
-
430
- // ✅ Testing observable behavior
431
- it('should reject invalid email', async () => {
432
- await expect(
433
- service.createUser({ email: 'invalid' })
434
- ).rejects.toThrow('Invalid email');
435
- });
436
- ```
437
-
438
- ## Test Doubles
439
-
440
- ```typescript
441
- // Stub: Returns canned responses
442
- const stubDatabase = {
443
- findUser: () => ({ id: '1', name: 'Test User' })
444
- };
445
-
446
- // Mock: Pre-programmed with expectations
447
- const mockPayment = {
448
- charge: jest.fn()
449
- .mockResolvedValueOnce({ success: true })
450
- .mockResolvedValueOnce({ success: false })
451
- };
452
-
453
- // Fake: Working implementation (not for production)
454
- class FakeDatabase implements Database {
455
- private data = new Map<string, any>();
456
-
457
- async save(id: string, data: any) { this.data.set(id, data); }
458
- async find(id: string) { return this.data.get(id); }
459
- }
460
- ```
461
-
462
- ## One Test Per Condition
463
-
464
- ```typescript
465
- // ❌ Multiple assertions for different scenarios
466
- it('should validate user input', () => {
467
- expect(() => validate({ age: -1 })).toThrow();
468
- expect(() => validate({ age: 200 })).toThrow();
469
- expect(() => validate({ name: '' })).toThrow();
470
- });
471
-
472
- // ✅ One test per condition
473
- it('should reject negative age', () => {
474
- expect(() => validate({ age: -1 })).toThrow('Age must be positive');
475
- });
476
-
477
- it('should reject age over 150', () => {
478
- expect(() => validate({ age: 200 })).toThrow('Age must be under 150');
479
- });
480
- ```
481
-
482
- ## Keep Tests Independent
483
-
484
- ```typescript
485
- // ✅ Each test is self-contained
486
- it('should update user', async () => {
487
- const user = await service.createUser({ name: 'Test' });
488
- const updated = await service.updateUser(user.id, { name: 'Updated' });
489
- expect(updated.name).toBe('Updated');
490
- });
491
- ```
492
-
493
-
494
- # TypeScript Configuration
495
-
496
- ## tsconfig.json Best Practices
497
-
498
- ```json
499
- {
500
- "compilerOptions": {
501
- // Strict type checking
502
- "strict": true,
503
- "noImplicitAny": true,
504
- "strictNullChecks": true,
505
- "strictFunctionTypes": true,
506
- "strictBindCallApply": true,
507
- "strictPropertyInitialization": true,
508
- "noImplicitThis": true,
509
- "alwaysStrict": true,
510
-
511
- // Additional checks
512
- "noUnusedLocals": true,
513
- "noUnusedParameters": true,
514
- "noImplicitReturns": true,
515
- "noFallthroughCasesInSwitch": true,
516
- "noUncheckedIndexedAccess": true,
517
-
518
- // Module resolution
519
- "module": "ESNext",
520
- "moduleResolution": "bundler",
521
- "esModuleInterop": true,
522
- "allowSyntheticDefaultImports": true,
523
- "resolveJsonModule": true,
524
-
525
- // Output
526
- "target": "ES2022",
527
- "outDir": "./dist",
528
- "declaration": true,
529
- "declarationMap": true,
530
- "sourceMap": true,
531
-
532
- // Path aliases
533
- "baseUrl": ".",
534
- "paths": {
535
- "@/*": ["src/*"],
536
- "@services/*": ["src/services/*"],
537
- "@models/*": ["src/models/*"]
538
- }
539
- },
540
- "include": ["src/**/*"],
541
- "exclude": ["node_modules", "dist", "**/*.test.ts"]
542
- }
543
- ```
544
-
545
- ## Path Aliases Setup
546
-
547
- ```typescript
548
- // With path aliases configured:
549
- import { UserService } from '@services/user';
550
- import { User } from '@models/user';
551
-
552
- // Instead of relative paths:
553
- import { UserService } from '../../../services/user';
554
- ```
555
-
556
- ## Project References (Monorepo)
557
-
558
- ```json
559
- // packages/shared/tsconfig.json
560
- {
561
- "compilerOptions": {
562
- "composite": true,
563
- "outDir": "./dist"
564
- }
565
- }
566
-
567
- // packages/api/tsconfig.json
568
- {
569
- "extends": "../../tsconfig.base.json",
570
- "references": [
571
- { "path": "../shared" }
572
- ]
573
- }
574
- ```
575
-
576
- ## Environment-Specific Configs
577
-
578
- ```json
579
- // tsconfig.build.json - for production builds
580
- {
581
- "extends": "./tsconfig.json",
582
- "compilerOptions": {
583
- "sourceMap": false,
584
- "removeComments": true
585
- },
586
- "exclude": ["**/*.test.ts", "**/*.spec.ts"]
587
- }
588
- ```
589
-
590
-
591
- ---
592
-
593
-
594
- ## Testing
595
-
596
- # Unit Testing Fundamentals
597
-
598
- ## Arrange-Act-Assert Pattern
599
-
600
- ```typescript
601
- describe('UserService', () => {
602
- it('should create user with hashed password', async () => {
603
- // Arrange - Set up test data and dependencies
604
- const userData = { email: 'test@example.com', password: 'secret123' };
605
- const mockRepo = { save: jest.fn().mockResolvedValue({ id: '1', ...userData }) };
606
- const service = new UserService(mockRepo);
607
-
608
- // Act - Execute the behavior being tested
609
- const result = await service.createUser(userData);
610
-
611
- // Assert - Verify the outcomes
612
- expect(result.id).toBe('1');
613
- expect(mockRepo.save).toHaveBeenCalledWith(
614
- expect.objectContaining({ email: 'test@example.com' })
615
- );
616
- });
617
- });
618
- ```
619
-
620
- ## Test Observable Behavior, Not Implementation
621
-
622
- ```typescript
623
- // ❌ Bad: Testing implementation details
624
- it('should call validateEmail method', () => {
625
- const spy = jest.spyOn(service, 'validateEmail');
626
- service.createUser({ email: 'test@example.com' });
627
- expect(spy).toHaveBeenCalled();
628
- });
629
-
630
- // ✅ Good: Testing observable behavior
631
- it('should reject invalid email', async () => {
632
- await expect(
633
- service.createUser({ email: 'invalid-email' })
634
- ).rejects.toThrow('Invalid email format');
635
- });
636
-
637
- it('should accept valid email', async () => {
638
- const result = await service.createUser({ email: 'valid@example.com' });
639
- expect(result.email).toBe('valid@example.com');
640
- });
641
- ```
642
-
643
- ## One Assertion Per Test Concept
644
-
645
- ```typescript
646
- // ❌ Bad: Multiple unrelated assertions
647
- it('should validate user input', () => {
648
- expect(() => validate({ age: -1 })).toThrow();
649
- expect(() => validate({ age: 200 })).toThrow();
650
- expect(() => validate({ name: '' })).toThrow();
651
- });
652
-
653
- // ✅ Good: One test per scenario
654
- it('should reject negative age', () => {
655
- expect(() => validate({ age: -1 })).toThrow('Age must be positive');
656
- });
657
-
658
- it('should reject age over 150', () => {
659
- expect(() => validate({ age: 200 })).toThrow('Age must be under 150');
660
- });
661
-
662
- it('should reject empty name', () => {
663
- expect(() => validate({ name: '' })).toThrow('Name is required');
664
- });
665
- ```
666
-
667
- ## Descriptive Test Names
668
-
669
- ```typescript
670
- // ❌ Vague names
671
- it('should work correctly', () => {});
672
- it('handles edge case', () => {});
673
-
674
- // ✅ Descriptive names - describe the scenario and expected outcome
675
- it('should return empty array when no users match filter', () => {});
676
- it('should throw ValidationError when email is empty', () => {});
677
- it('should retry failed payment up to 3 times before giving up', () => {});
678
- ```
679
-
680
- ## Tests Should Be Independent
681
-
682
- ```typescript
683
- // ❌ Bad: Tests depend on each other
684
- let userId: string;
685
-
686
- it('should create user', async () => {
687
- const user = await service.createUser(data);
688
- userId = user.id; // Shared state!
689
- });
690
-
691
- it('should update user', async () => {
692
- await service.updateUser(userId, newData); // Depends on previous test
693
- });
694
-
695
- // ✅ Good: Each test is self-contained
696
- it('should update user', async () => {
697
- const user = await service.createUser(data);
698
- const updated = await service.updateUser(user.id, newData);
699
- expect(updated.name).toBe(newData.name);
700
- });
701
- ```
702
-
703
- ## Test Edge Cases
704
-
705
- ```typescript
706
- describe('divide', () => {
707
- it('should divide two positive numbers', () => {
708
- expect(divide(10, 2)).toBe(5);
709
- });
710
-
711
- it('should throw when dividing by zero', () => {
712
- expect(() => divide(10, 0)).toThrow('Division by zero');
713
- });
714
-
715
- it('should handle negative numbers', () => {
716
- expect(divide(-10, 2)).toBe(-5);
717
- });
718
-
719
- it('should return zero when numerator is zero', () => {
720
- expect(divide(0, 5)).toBe(0);
721
- });
722
- });
723
- ```
724
-
725
-
726
- # Test Doubles and Mocking
727
-
728
- ## Types of Test Doubles
729
-
730
- ```typescript
731
- // STUB: Returns canned responses
732
- const stubUserRepo = {
733
- findById: () => ({ id: '1', name: 'Test User' })
734
- };
735
-
736
- // MOCK: Pre-programmed with expectations
737
- const mockPaymentGateway = {
738
- charge: jest.fn()
739
- .mockResolvedValueOnce({ success: true, transactionId: 'tx1' })
740
- .mockResolvedValueOnce({ success: false, error: 'Declined' })
741
- };
742
-
743
- // SPY: Records calls for verification
744
- const spy = jest.spyOn(emailService, 'send');
745
-
746
- // FAKE: Working implementation (not for production)
747
- class FakeDatabase implements Database {
748
- private data = new Map<string, any>();
749
-
750
- async save(id: string, entity: any) { this.data.set(id, entity); }
751
- async find(id: string) { return this.data.get(id); }
752
- }
753
- ```
754
-
755
- ## When to Mock
756
-
757
- ```typescript
758
- // ✅ Mock external services (APIs, databases)
759
- const mockHttpClient = {
760
- get: jest.fn().mockResolvedValue({ data: userData })
761
- };
762
-
763
- // ✅ Mock time-dependent operations
764
- jest.useFakeTimers();
765
- jest.setSystemTime(new Date('2024-01-15'));
766
-
767
- // ✅ Mock random/non-deterministic functions
768
- jest.spyOn(Math, 'random').mockReturnValue(0.5);
769
-
770
- // ❌ Don't mock the code you're testing
771
- // ❌ Don't mock simple data structures
772
- ```
773
-
774
- ## Mock Verification
775
-
776
- ```typescript
777
- it('should send welcome email after registration', async () => {
778
- const mockEmail = { send: jest.fn().mockResolvedValue(true) };
779
- const service = new UserService({ emailService: mockEmail });
780
-
781
- await service.register({ email: 'new@example.com' });
782
-
783
- expect(mockEmail.send).toHaveBeenCalledWith({
784
- to: 'new@example.com',
785
- template: 'welcome',
786
- subject: 'Welcome!'
787
- });
788
- expect(mockEmail.send).toHaveBeenCalledTimes(1);
789
- });
790
- ```
791
-
792
- ## Partial Mocks
793
-
794
- ```typescript
795
- // Mock only specific methods
796
- const service = new OrderService();
797
-
798
- jest.spyOn(service, 'validateOrder').mockReturnValue(true);
799
- jest.spyOn(service, 'calculateTotal').mockReturnValue(100);
800
- // Other methods use real implementation
801
-
802
- const result = await service.processOrder(orderData);
803
- expect(result.total).toBe(100);
804
- ```
805
-
806
- ## Resetting Mocks
807
-
808
- ```typescript
809
- describe('PaymentService', () => {
810
- const mockGateway = { charge: jest.fn() };
811
- const service = new PaymentService(mockGateway);
812
-
813
- beforeEach(() => {
814
- jest.clearAllMocks(); // Clear call history
815
- // or jest.resetAllMocks() to also reset return values
816
- });
817
-
818
- it('should process payment', async () => {
819
- mockGateway.charge.mockResolvedValue({ success: true });
820
- await service.charge(100);
821
- expect(mockGateway.charge).toHaveBeenCalledTimes(1);
822
- });
823
- });
824
- ```
825
-
826
- ## Mock Modules
827
-
828
- ```typescript
829
- // Mock entire module
830
- jest.mock('./email-service', () => ({
831
- EmailService: jest.fn().mockImplementation(() => ({
832
- send: jest.fn().mockResolvedValue(true)
833
- }))
834
- }));
835
-
836
- // Mock with partial implementation
837
- jest.mock('./config', () => ({
838
- ...jest.requireActual('./config'),
839
- API_KEY: 'test-key'
840
- }));
841
- ```
842
-
843
-
844
- # Testing Basics
845
-
846
- ## Your First Unit Test
847
-
848
- A unit test verifies that a small piece of code works correctly.
849
-
850
- ```pseudocode
851
- // Function to test
852
- function add(a, b):
853
- return a + b
854
-
855
- // Test for the function
856
- test "add should sum two numbers":
857
- result = add(2, 3)
858
- expect result equals 5
859
- ```
860
-
861
- ## Test Structure
862
-
863
- Every test has three parts:
864
-
865
- 1. **Setup** - Prepare what you need
866
- 2. **Execute** - Run the code
867
- 3. **Verify** - Check the result
868
-
869
- ```pseudocode
870
- test "should create user with name":
871
- // 1. Setup
872
- userName = "Alice"
873
-
874
- // 2. Execute
875
- user = createUser(userName)
876
-
877
- // 3. Verify
878
- expect user.name equals "Alice"
879
- ```
880
-
881
- ## Common Assertions
882
-
883
- ```pseudocode
884
- // Equality
885
- expect value equals 5
886
- expect value equals { id: 1 }
887
-
888
- // Truthiness
889
- expect value is truthy
890
- expect value is falsy
891
- expect value is null
892
- expect value is undefined
893
-
894
- // Numbers
895
- expect value > 3
896
- expect value < 10
897
-
898
- // Strings
899
- expect text contains "hello"
900
-
901
- // Arrays/Lists
902
- expect array contains item
903
- expect array length equals 3
904
- ```
905
-
906
- ## Testing Expected Errors
907
-
908
- ```pseudocode
909
- test "should throw error for invalid input":
910
- expect error when:
911
- divide(10, 0)
912
- with message "Cannot divide by zero"
913
- ```
914
-
915
- ## Async Tests
916
-
917
- ```pseudocode
918
- test "should fetch user data" async:
919
- user = await fetchUser(123)
920
- expect user.id equals 123
921
- ```
922
-
923
- ## Test Naming
924
-
925
- Use clear, descriptive names:
926
-
927
- ```pseudocode
928
- // ❌ Bad
929
- test "test1"
930
- test "it works"
931
-
932
- // ✅ Good
933
- test "should return user when ID exists"
934
- test "should throw error when ID is invalid"
935
- ```
936
-
937
- ## Running Tests
938
-
939
- ```bash
940
- # Run all tests
941
- run-tests
942
-
943
- # Run specific test file
944
- run-tests user-test
945
-
946
- # Watch mode (re-run on changes)
947
- run-tests --watch
948
- ```
949
-
950
- ## Best Practices
951
-
952
- 1. **One test, one thing** - Test one behavior per test
953
- 2. **Independent tests** - Tests should not depend on each other
954
- 3. **Clear names** - Name should describe what is being tested
955
- 4. **Fast tests** - Tests should run quickly
956
-
957
- ```pseudocode
958
- // ❌ Bad: Testing multiple things
959
- test "user operations":
960
- expect createUser("Bob").name equals "Bob"
961
- expect deleteUser(1) equals true
962
- expect listUsers().length equals 0
963
-
964
- // ✅ Good: One test per operation
965
- test "should create user with given name":
966
- user = createUser("Bob")
967
- expect user.name equals "Bob"
968
-
969
- test "should delete user by ID":
970
- result = deleteUser(1)
971
- expect result equals true
972
-
973
- test "should return empty list when no users":
974
- users = listUsers()
975
- expect users.length equals 0
976
- ```
977
-
978
- ## When to Write Tests
979
-
980
- - **Before fixing bugs** - Write test that fails, then fix
981
- - **For new features** - Test expected behavior
982
- - **For edge cases** - Empty input, null values, large numbers
983
-
984
- ## What to Test
985
-
986
- ✅ **Do test:**
987
- - Public functions and methods
988
- - Edge cases (empty, null, zero, negative)
989
- - Error conditions
990
-
991
- ❌ **Don't test:**
992
- - Private implementation details
993
- - Third-party libraries (they're already tested)
994
- - Getters/setters with no logic
995
-
996
-
997
- ---
998
-
999
-
1000
- ## Security
1001
-
1002
- # Injection Prevention
1003
-
1004
- ## SQL Injection Prevention
1005
-
1006
- ```typescript
1007
- // ❌ DANGEROUS: String concatenation
1008
- const getUserByEmail = async (email: string) => {
1009
- const query = `SELECT * FROM users WHERE email = '${email}'`;
1010
- // Input: ' OR '1'='1
1011
- // Result: SELECT * FROM users WHERE email = '' OR '1'='1'
1012
- return db.query(query);
1013
- };
1014
-
1015
- // ✅ SAFE: Parameterized queries
1016
- const getUserByEmail = async (email: string) => {
1017
- return db.query('SELECT * FROM users WHERE email = ?', [email]);
1018
- };
1019
-
1020
- // ✅ SAFE: Using ORM
1021
- const getUserByEmail = async (email: string) => {
1022
- return userRepository.findOne({ where: { email } });
1023
- };
1024
-
1025
- // ✅ SAFE: Query builder
1026
- const getUsers = async (minAge: number) => {
1027
- return db
1028
- .select('*')
1029
- .from('users')
1030
- .where('age', '>', minAge); // Automatically parameterized
1031
- };
1032
- ```
1033
-
1034
- ## NoSQL Injection Prevention
1035
-
1036
- ```typescript
1037
- // ❌ DANGEROUS: Accepting objects from user input
1038
- app.post('/login', (req, res) => {
1039
- const { username, password } = req.body;
1040
- // If password = {$gt: ""}, it bypasses password check!
1041
- db.users.findOne({ username, password });
1042
- });
1043
-
1044
- // ✅ SAFE: Validate input types
1045
- app.post('/login', (req, res) => {
1046
- const { username, password } = req.body;
1047
-
1048
- if (typeof username !== 'string' || typeof password !== 'string') {
1049
- throw new Error('Invalid input types');
1050
- }
1051
-
1052
- db.users.findOne({ username, password });
1053
- });
1054
- ```
1055
-
1056
- ## Command Injection Prevention
1057
-
1058
- ```typescript
1059
- // ❌ DANGEROUS: Shell command with user input
1060
- const convertImage = async (filename: string) => {
1061
- exec(`convert ${filename} output.jpg`);
1062
- // Input: "file.png; rm -rf /"
1063
- };
1064
-
1065
- // ✅ SAFE: Use arrays, avoid shell
1066
- import { execFile } from 'child_process';
1067
-
1068
- const convertImage = async (filename: string) => {
1069
- execFile('convert', [filename, 'output.jpg']);
1070
- };
1071
-
1072
- // ✅ SAFE: Validate input against whitelist
1073
- const allowedFilename = /^[a-zA-Z0-9_-]+\.(png|jpg|gif)$/;
1074
- if (!allowedFilename.test(filename)) {
1075
- throw new Error('Invalid filename');
1076
- }
1077
- ```
1078
-
1079
- ## Path Traversal Prevention
1080
-
1081
- ```typescript
1082
- // ❌ DANGEROUS: Direct path usage
1083
- app.get('/files/:filename', (req, res) => {
1084
- res.sendFile(`/uploads/${req.params.filename}`);
1085
- // Input: ../../etc/passwd
1086
- });
1087
-
1088
- // ✅ SAFE: Validate and normalize path
1089
- import path from 'path';
1090
-
1091
- app.get('/files/:filename', (req, res) => {
1092
- const safeName = path.basename(req.params.filename);
1093
- const filePath = path.join('/uploads', safeName);
1094
- const normalizedPath = path.normalize(filePath);
1095
-
1096
- if (!normalizedPath.startsWith('/uploads/')) {
1097
- return res.status(400).json({ error: 'Invalid filename' });
1098
- }
1099
-
1100
- res.sendFile(normalizedPath);
1101
- });
1102
- ```
1103
-
1104
- ## Input Validation
1105
-
1106
- ```typescript
1107
- // ✅ Whitelist validation
1108
- import { z } from 'zod';
1109
-
1110
- const userSchema = z.object({
1111
- email: z.string().email(),
1112
- password: z.string().min(12).max(160),
1113
- age: z.number().int().min(0).max(150),
1114
- role: z.enum(['user', 'admin'])
1115
- });
1116
-
1117
- const validateUser = (data: unknown) => {
1118
- return userSchema.parse(data);
1119
- };
1120
- ```
1121
-
1122
-
1123
- # Authentication & JWT Security
1124
-
1125
- ## Password Storage
1126
-
1127
- ```typescript
1128
- import bcrypt from 'bcrypt';
1129
-
1130
- const SALT_ROUNDS = 12; // Work factor
1131
-
1132
- // ✅ Hash password with bcrypt
1133
- async function hashPassword(password: string): Promise<string> {
1134
- return bcrypt.hash(password, SALT_ROUNDS);
1135
- }
1136
-
1137
- async function verifyPassword(password: string, hash: string): Promise<boolean> {
1138
- return bcrypt.compare(password, hash);
1139
- }
1140
-
1141
- // ✅ Validate password strength
1142
- function validatePassword(password: string): void {
1143
- if (password.length < 12) {
1144
- throw new Error('Password must be at least 12 characters');
1145
- }
1146
- if (password.length > 160) {
1147
- throw new Error('Password too long'); // Prevent DoS via bcrypt
1148
- }
1149
- }
1150
- ```
1151
-
1152
- ## JWT Best Practices
1153
-
1154
- ```typescript
1155
- import jwt from 'jsonwebtoken';
1156
-
1157
- const JWT_SECRET = process.env.JWT_SECRET!;
1158
- const ACCESS_TOKEN_EXPIRY = '15m';
1159
- const REFRESH_TOKEN_EXPIRY = '7d';
1160
-
1161
- // ✅ Generate tokens
1162
- function generateTokens(userId: string) {
1163
- const accessToken = jwt.sign(
1164
- { sub: userId, type: 'access' },
1165
- JWT_SECRET,
1166
- { expiresIn: ACCESS_TOKEN_EXPIRY }
1167
- );
1168
-
1169
- const refreshToken = jwt.sign(
1170
- { sub: userId, type: 'refresh' },
1171
- JWT_SECRET,
1172
- { expiresIn: REFRESH_TOKEN_EXPIRY }
1173
- );
1174
-
1175
- return { accessToken, refreshToken };
1176
- }
1177
-
1178
- // ✅ Verify and decode token
1179
- function verifyToken(token: string) {
1180
- try {
1181
- return jwt.verify(token, JWT_SECRET);
1182
- } catch (error) {
1183
- if (error instanceof jwt.TokenExpiredError) {
1184
- throw new UnauthorizedError('Token expired');
1185
- }
1186
- throw new UnauthorizedError('Invalid token');
1187
- }
1188
- }
1189
- ```
1190
-
1191
- ## Login Protection
1192
-
1193
- ```typescript
1194
- import rateLimit from 'express-rate-limit';
1195
-
1196
- // ✅ Rate limit login attempts
1197
- const loginLimiter = rateLimit({
1198
- windowMs: 15 * 60 * 1000, // 15 minutes
1199
- max: 5, // 5 attempts
1200
- message: 'Too many login attempts, please try again later',
1201
- });
1202
-
1203
- app.post('/login', loginLimiter, async (req, res) => {
1204
- const { email, password } = req.body;
1205
-
1206
- const user = await userService.findByEmail(email);
1207
-
1208
- // ✅ Generic error message (don't reveal if user exists)
1209
- if (!user || !await verifyPassword(password, user.passwordHash)) {
1210
- return res.status(401).json({ error: 'Invalid email or password' });
1211
- }
1212
-
1213
- const tokens = generateTokens(user.id);
1214
-
1215
- // Regenerate session to prevent fixation
1216
- req.session.regenerate(() => {
1217
- res.json({ ...tokens });
1218
- });
1219
- });
1220
- ```
1221
-
1222
- ## Session Security
1223
-
1224
- ```typescript
1225
- app.use(session({
1226
- secret: process.env.SESSION_SECRET!,
1227
- name: 'sessionId', // Don't use default 'connect.sid'
1228
-
1229
- cookie: {
1230
- secure: true, // HTTPS only
1231
- httpOnly: true, // Prevent XSS access
1232
- sameSite: 'strict', // CSRF protection
1233
- maxAge: 30 * 60 * 1000, // 30 minutes
1234
- },
1235
-
1236
- resave: false,
1237
- saveUninitialized: false,
1238
- store: new RedisStore({ client: redisClient })
1239
- }));
1240
-
1241
- // ✅ Session regeneration after login
1242
- app.post('/login', async (req, res, next) => {
1243
- // ... authenticate user ...
1244
-
1245
- req.session.regenerate((err) => {
1246
- req.session.userId = user.id;
1247
- res.json({ success: true });
1248
- });
1249
- });
1250
- ```
1251
-
1252
- ## Authorization Middleware
1253
-
1254
- ```typescript
1255
- // ✅ Require authentication
1256
- const requireAuth = async (req: Request, res: Response, next: NextFunction) => {
1257
- const token = req.headers.authorization?.replace('Bearer ', '');
1258
-
1259
- if (!token) {
1260
- return res.status(401).json({ error: 'Authentication required' });
1261
- }
1262
-
1263
- try {
1264
- const payload = verifyToken(token);
1265
- req.user = await userService.findById(payload.sub);
1266
- next();
1267
- } catch (error) {
1268
- res.status(401).json({ error: 'Invalid token' });
1269
- }
1270
- };
1271
-
1272
- // ✅ Require specific role
1273
- const requireRole = (...roles: string[]) => {
1274
- return (req: Request, res: Response, next: NextFunction) => {
1275
- if (!roles.includes(req.user.role)) {
1276
- return res.status(403).json({ error: 'Forbidden' });
1277
- }
1278
- next();
1279
- };
1280
- };
1281
- ```
1282
-
1283
-
1284
- # Secrets Management
1285
-
1286
- ## Environment Variables
1287
-
1288
- ```typescript
1289
- // ❌ NEVER hardcode secrets
1290
- const config = {
1291
- dbPassword: 'super_secret_password',
1292
- apiKey: 'sk-1234567890abcdef'
1293
- };
1294
-
1295
- // ✅ Use environment variables
1296
- import dotenv from 'dotenv';
1297
- dotenv.config();
1298
-
1299
- const config = {
1300
- dbPassword: process.env.DB_PASSWORD,
1301
- apiKey: process.env.API_KEY,
1302
- sessionSecret: process.env.SESSION_SECRET
1303
- };
1304
- ```
1305
-
1306
- ## Validate Required Secrets
1307
-
1308
- ```typescript
1309
- // ✅ Fail fast if secrets missing
1310
- const requiredEnvVars = [
1311
- 'DB_PASSWORD',
1312
- 'API_KEY',
1313
- 'SESSION_SECRET',
1314
- 'JWT_SECRET'
1315
- ];
1316
-
1317
- requiredEnvVars.forEach(varName => {
1318
- if (!process.env[varName]) {
1319
- throw new Error(`Missing required environment variable: ${varName}`);
1320
- }
1321
- });
1322
-
1323
- // ✅ Type-safe config
1324
- interface Config {
1325
- dbPassword: string;
1326
- apiKey: string;
1327
- sessionSecret: string;
1328
- }
1329
-
1330
- function loadConfig(): Config {
1331
- const dbPassword = process.env.DB_PASSWORD;
1332
- if (!dbPassword) throw new Error('DB_PASSWORD required');
1333
-
1334
- // ... validate all required vars
1335
-
1336
- return { dbPassword, apiKey, sessionSecret };
1337
- }
1338
- ```
1339
-
1340
- ## Generate Strong Secrets
1341
-
1342
- ```bash
1343
- # Generate cryptographically secure secrets
1344
- node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
1345
-
1346
- # Or using OpenSSL
1347
- openssl rand -base64 32
1348
-
1349
- # Or using head
1350
- head -c32 /dev/urandom | base64
1351
- ```
1352
-
1353
- ## .gitignore Configuration
1354
-
1355
- ```bash
1356
- # .gitignore - NEVER commit secrets
1357
- .env
1358
- .env.local
1359
- .env.*.local
1360
- *.key
1361
- *.pem
1362
- secrets/
1363
- credentials.json
1364
- ```
1365
-
1366
- ## Environment Example File
1367
-
1368
- ```bash
1369
- # .env.example - commit this to show required variables
1370
- DB_HOST=localhost
1371
- DB_PORT=5432
1372
- DB_NAME=myapp
1373
- DB_USER=
1374
- DB_PASSWORD=
1375
-
1376
- API_KEY=
1377
- SESSION_SECRET=
1378
- JWT_SECRET=
1379
-
1380
- # Copy to .env and fill in actual values
1381
- ```
1382
-
1383
- ## Secrets in CI/CD
1384
-
1385
- ```yaml
1386
- # GitHub Actions
1387
- - name: Deploy
1388
- env:
1389
- DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
1390
- API_KEY: ${{ secrets.API_KEY }}
1391
- run: ./deploy.sh
1392
-
1393
- # ❌ Never echo secrets in logs
1394
- - name: Configure
1395
- run: |
1396
- echo "Configuring application..."
1397
- # echo "DB_PASSWORD=$DB_PASSWORD" # NEVER do this!
1398
- ```
1399
-
1400
- ## Secrets Rotation
1401
-
1402
- ```typescript
1403
- // ✅ Support for rotating secrets
1404
- class SecretManager {
1405
- async getSecret(name: string): Promise<string> {
1406
- // Check for new secret first (during rotation)
1407
- const newSecret = process.env[`${name}_NEW`];
1408
- if (newSecret) {
1409
- return newSecret;
1410
- }
1411
-
1412
- const secret = process.env[name];
1413
- if (!secret) {
1414
- throw new Error(`Secret ${name} not found`);
1415
- }
1416
- return secret;
1417
- }
1418
- }
1419
-
1420
- // ✅ Accept multiple JWT signing keys during rotation
1421
- function verifyToken(token: string) {
1422
- const keys = [process.env.JWT_SECRET, process.env.JWT_SECRET_OLD].filter(Boolean);
1423
-
1424
- for (const key of keys) {
1425
- try {
1426
- return jwt.verify(token, key);
1427
- } catch {}
1428
- }
1429
- throw new Error('Invalid token');
1430
- }
1431
- ```
1432
-
1433
-
1434
- ---
1435
-
1436
-
1437
- ## Performance
1438
-
1439
- # Performance Basics
1440
-
1441
- ## Choose the Right Data Structure
1442
-
1443
- Different data structures have different speeds for different operations.
1444
-
1445
- ### Arrays vs Objects vs Maps
1446
-
1447
- ```pseudocode
1448
- // ❌ Slow: Looking up in array (O(n))
1449
- users = [
1450
- { id: 1, name: 'Alice' },
1451
- { id: 2, name: 'Bob' },
1452
- // ... 1000 more
1453
- ]
1454
- user = users.find(u => u.id == 500) // Checks 500 items
1455
-
1456
- // ✅ Fast: Looking up in Map (O(1))
1457
- users = Map()
1458
- users.set(1, { id: 1, name: 'Alice' })
1459
- users.set(2, { id: 2, name: 'Bob' })
1460
-
1461
- user = users.get(500) // Instant lookup
1462
- ```
1463
-
1464
- ### Arrays vs Sets
1465
-
1466
- ```pseudocode
1467
- // ❌ Slow: Checking if item exists in array
1468
- items = [1, 2, 3, 4, 5, ... 1000 more]
1469
- if items.contains(500): // Checks every item
1470
-
1471
- // ✅ Fast: Checking if item exists in Set
1472
- items = Set([1, 2, 3, 4, 5, ... 1000 more])
1473
- if items.has(500): // Instant check
1474
- ```
1475
-
1476
- ### When to Use Each
1477
-
1478
- | Data Structure | Good For |
1479
- |----------------|----------|
1480
- | **Array/List** | Ordered items, iteration |
1481
- | **Object/Dict** | Key-value pairs (string keys) |
1482
- | **Map** | Key-value pairs (any type keys), frequent lookups |
1483
- | **Set** | Unique values, membership checks |
1484
-
1485
- ## Avoid N+1 Queries
1486
-
1487
- One of the most common performance problems.
1488
-
1489
- ```pseudocode
1490
- // ❌ BAD: N+1 queries (1 + N database calls)
1491
- orders = database.query("SELECT * FROM orders")
1492
-
1493
- for each order in orders:
1494
- // Separate query for EACH order! 😱
1495
- customer = database.query(
1496
- "SELECT * FROM customers WHERE id = ?",
1497
- [order.customer_id]
1498
- )
1499
- order.customer = customer
1500
- // If 100 orders = 101 database calls!
1501
-
1502
- // ✅ GOOD: Single query with JOIN
1503
- ordersWithCustomers = database.query("
1504
- SELECT
1505
- orders.*,
1506
- customers.name as customer_name,
1507
- customers.email as customer_email
1508
- FROM orders
1509
- JOIN customers ON orders.customer_id = customers.id
1510
- ")
1511
- // Only 1 database call!
1512
- ```
1513
-
1514
- ## Don't Load What You Don't Need
1515
-
1516
- ```pseudocode
1517
- // ❌ Bad: Fetching entire object when you only need one field
1518
- user = database.query("SELECT * FROM users WHERE id = ?", [id])
1519
- print(user.email)
1520
-
1521
- // ✅ Good: Fetch only what you need
1522
- result = database.query(
1523
- "SELECT email FROM users WHERE id = ?",
1524
- [id]
1525
- )
1526
- print(result.email)
1527
-
1528
- // ❌ Bad: Loading all records
1529
- users = database.query("SELECT * FROM users")
1530
-
1531
- // ✅ Good: Add LIMIT
1532
- users = database.query("SELECT * FROM users LIMIT 100")
1533
- ```
1534
-
1535
- ## Use Async for I/O Operations
1536
-
1537
- Don't block the program waiting for slow operations.
1538
-
1539
- ```pseudocode
1540
- // ❌ Slow: Blocking operations (synchronous)
1541
- file1 = readFileSync("file1.txt")
1542
- file2 = readFileSync("file2.txt")
1543
- file3 = readFileSync("file3.txt")
1544
- // Total: 300ms (100ms each, one after another)
1545
-
1546
- // ✅ Fast: Async operations (parallel)
1547
- files = await Promise.all([
1548
- readFile("file1.txt"),
1549
- readFile("file2.txt"),
1550
- readFile("file3.txt")
1551
- ])
1552
- // Total: 100ms (all at once)
1553
- ```
1554
-
1555
- ## Avoid Unnecessary Work in Loops
1556
-
1557
- ```pseudocode
1558
- // ❌ Bad: Work done every iteration
1559
- for i in 0 to items.length:
1560
- total = calculateTotal(items) // Recalculated each time!
1561
- if items[i].price > total * 0.1:
1562
- // ...
1563
-
1564
- // ✅ Good: Work done once
1565
- total = calculateTotal(items)
1566
- for i in 0 to items.length:
1567
- if items[i].price > total * 0.1:
1568
- // ...
1569
-
1570
- // ❌ Bad: Array length calculated each time
1571
- for i in 0 to items.length:
1572
- // ...
1573
-
1574
- // ✅ Good: Length cached (minor improvement)
1575
- len = items.length
1576
- for i in 0 to len:
1577
- // ...
1578
-
1579
- // ✅ Best: Modern for-each loop
1580
- for each item in items:
1581
- // ...
1582
- ```
1583
-
1584
- ## Index Your Database
1585
-
1586
- Indexes make lookups fast, but slow down writes.
1587
-
1588
- ```sql
1589
- -- Without index: Checks every row
1590
- SELECT * FROM users WHERE email = 'alice@example.com';
1591
- -- With 1 million users: ~1 second
1592
-
1593
- -- Add index
1594
- CREATE INDEX idx_users_email ON users(email);
1595
-
1596
- -- Now same query is instant
1597
- SELECT * FROM users WHERE email = 'alice@example.com';
1598
- -- With 1 million users: ~1 millisecond
1599
- ```
1600
-
1601
- ### When to Add Indexes
1602
-
1603
- - Columns used in WHERE clauses
1604
- - Columns used in JOIN conditions
1605
- - Columns used in ORDER BY
1606
-
1607
- ```sql
1608
- -- Frequently queried
1609
- CREATE INDEX idx_orders_status ON orders(status);
1610
- CREATE INDEX idx_users_email ON users(email);
1611
-
1612
- -- Used in joins
1613
- CREATE INDEX idx_orders_customer_id ON orders(customer_id);
1614
- ```
1615
-
1616
- ## Cache Expensive Results
1617
-
1618
- Don't recalculate the same thing repeatedly.
1619
-
1620
- ```pseudocode
1621
- // ❌ Bad: Calculating every time
1622
- function getReport(userId):
1623
- data = expensiveCalculation(userId) // 5 seconds
1624
- return data
1625
-
1626
- // Called 100 times = 500 seconds!
1627
-
1628
- // ✅ Good: Cache results
1629
- cache = Map()
1630
-
1631
- function getReport(userId):
1632
- if cache.has(userId):
1633
- return cache.get(userId) // Instant
1634
-
1635
- data = expensiveCalculation(userId)
1636
- cache.set(userId, data)
1637
- return data
1638
-
1639
- // First call: 5 seconds, next 99 calls: instant
1640
- ```
1641
-
1642
- ## Batch Operations
1643
-
1644
- Process multiple items together instead of one at a time.
1645
-
1646
- ```pseudocode
1647
- // ❌ Bad: Individual database calls
1648
- for each user in users:
1649
- database.execute("INSERT INTO users (name) VALUES (?)", [user.name])
1650
- // 100 users = 100 database calls
1651
-
1652
- // ✅ Good: Batch insert
1653
- database.execute("
1654
- INSERT INTO users (name)
1655
- VALUES " + users.map(u => "(?)").join(", "),
1656
- users.map(u => u.name)
1657
- )
1658
- // 100 users = 1 database call
1659
- ```
1660
-
1661
- ## Profile Before Optimizing
1662
-
1663
- Don't guess where the problem is - measure!
1664
-
1665
- ```pseudocode
1666
- // Measure execution time
1667
- startTime = currentTime()
1668
- result = await slowOperation()
1669
- endTime = currentTime()
1670
- print("Operation took:", endTime - startTime, "ms")
1671
-
1672
- // Measure specific parts
1673
- startDB = currentTime()
1674
- data = await database.query("...")
1675
- print("Database:", currentTime() - startDB, "ms")
1676
-
1677
- startProcess = currentTime()
1678
- processed = processData(data)
1679
- print("Processing:", currentTime() - startProcess, "ms")
1680
- ```
1681
-
1682
- ## Common Performance Mistakes
1683
-
1684
- ### Mistake 1: Nested Loops with Database Queries
1685
-
1686
- ```pseudocode
1687
- // ❌ TERRIBLE: Nested queries
1688
- for each user in users:
1689
- for each order in orders:
1690
- product = await database.query(
1691
- "SELECT * FROM products WHERE id = ?",
1692
- [order.product_id]
1693
- )
1694
- // 100 users × 50 orders = 5000 database calls!
1695
-
1696
- // ✅ GOOD: Load all data first
1697
- products = await database.query("SELECT * FROM products")
1698
- productMap = Map()
1699
- for each p in products:
1700
- productMap.set(p.id, p)
1701
-
1702
- for each user in users:
1703
- for each order in orders:
1704
- product = productMap.get(order.product_id) // Instant
1705
- ```
1706
-
1707
- ### Mistake 2: Loading Everything into Memory
1708
-
1709
- ```pseudocode
1710
- // ❌ Bad: Loading 1 million records
1711
- allUsers = await database.query("SELECT * FROM users")
1712
- // Crashes with out of memory!
1713
-
1714
- // ✅ Good: Process in batches
1715
- BATCH_SIZE = 1000
1716
- offset = 0
1717
-
1718
- while true:
1719
- users = await database.query(
1720
- "SELECT * FROM users LIMIT ? OFFSET ?",
1721
- [BATCH_SIZE, offset]
1722
- )
1723
-
1724
- if users.length == 0:
1725
- break
1726
-
1727
- await processUsers(users)
1728
- offset = offset + BATCH_SIZE
1729
- ```
1730
-
1731
- ### Mistake 3: Not Using Indexes
1732
-
1733
- ```sql
1734
- -- ❌ Slow: No index on email column
1735
- SELECT * FROM users WHERE email = 'alice@example.com';
1736
- -- 1 million rows: ~2 seconds
1737
-
1738
- -- ✅ Fast: Add index
1739
- CREATE INDEX idx_users_email ON users(email);
1740
- -- Same query: ~2 milliseconds
1741
- ```
1742
-
1743
- ## Quick Performance Checklist
1744
-
1745
- - [ ] Use Map/Set for lookups instead of Array
1746
- - [ ] Avoid N+1 queries (use JOINs)
1747
- - [ ] Add database indexes on frequently queried columns
1748
- - [ ] Don't load all records (use LIMIT)
1749
- - [ ] Cache expensive calculations
1750
- - [ ] Run independent operations in parallel
1751
- - [ ] Move work outside of loops
1752
- - [ ] Batch database operations
1753
- - [ ] Profile before optimizing
1754
-
1755
- ## When to Optimize
1756
-
1757
- 1. **Measure first** - Is there actually a problem?
1758
- 2. **Find the bottleneck** - What's slow?
1759
- 3. **Fix the biggest problem** - Don't waste time on small gains
1760
- 4. **Measure again** - Did it help?
1761
-
1762
- Don't optimize prematurely - write clear code first, optimize when needed!
1763
-
1764
-
1765
- # Async Performance Patterns
1766
-
1767
- ## Parallel Execution
1768
-
1769
- ```typescript
1770
- // ❌ Sequential - slow
1771
- async function getUserData(userId: string) {
1772
- const user = await fetchUser(userId); // 100ms
1773
- const posts = await fetchPosts(userId); // 150ms
1774
- const comments = await fetchComments(userId); // 120ms
1775
- return { user, posts, comments }; // Total: 370ms
1776
- }
1777
-
1778
- // ✅ Parallel - fast
1779
- async function getUserData(userId: string) {
1780
- const [user, posts, comments] = await Promise.all([
1781
- fetchUser(userId),
1782
- fetchPosts(userId),
1783
- fetchComments(userId)
1784
- ]);
1785
- return { user, posts, comments }; // Total: 150ms
1786
- }
1787
-
1788
- // ✅ Partial parallel with dependencies
1789
- async function getOrderDetails(orderId: string) {
1790
- const order = await fetchOrder(orderId); // Must fetch first
1791
-
1792
- const [customer, items, shipping] = await Promise.all([
1793
- fetchCustomer(order.customerId),
1794
- fetchOrderItems(orderId),
1795
- fetchShippingInfo(orderId)
1796
- ]);
1797
-
1798
- return { order, customer, items, shipping };
1799
- }
1800
- ```
1801
-
1802
- ## Promise.allSettled for Partial Failures
1803
-
1804
- ```typescript
1805
- // Return partial data instead of complete failure
1806
- async function getDashboard(userId: string) {
1807
- const [user, orders, stats] = await Promise.allSettled([
1808
- getUser(userId),
1809
- getOrders(userId),
1810
- getStats(userId)
1811
- ]);
1812
-
1813
- return {
1814
- user: user.status === 'fulfilled' ? user.value : null,
1815
- orders: orders.status === 'fulfilled' ? orders.value : [],
1816
- stats: stats.status === 'fulfilled' ? stats.value : null,
1817
- errors: {
1818
- user: user.status === 'rejected' ? user.reason.message : null,
1819
- orders: orders.status === 'rejected' ? orders.reason.message : null,
1820
- stats: stats.status === 'rejected' ? stats.reason.message : null
1821
- }
1822
- };
1823
- }
1824
- ```
1825
-
1826
- ## Batch Processing
1827
-
1828
- ```typescript
1829
- // ❌ One at a time - slow
1830
- async function processUsers(userIds: string[]) {
1831
- for (const id of userIds) {
1832
- await updateUser(id);
1833
- }
1834
- }
1835
-
1836
- // ✅ Batch processing
1837
- async function processUsers(userIds: string[]) {
1838
- const BATCH_SIZE = 50;
1839
-
1840
- for (let i = 0; i < userIds.length; i += BATCH_SIZE) {
1841
- const batch = userIds.slice(i, i + BATCH_SIZE);
1842
- await Promise.all(batch.map(id => updateUser(id)));
1843
- }
1844
- }
1845
-
1846
- // ✅ Bulk database operations
1847
- async function createUsers(users: User[]) {
1848
- await db.query(`
1849
- INSERT INTO users (name, email)
1850
- VALUES ${users.map(() => '(?, ?)').join(', ')}
1851
- `, users.flatMap(u => [u.name, u.email]));
1852
- }
1853
- ```
1854
-
1855
- ## Debouncing and Throttling
1856
-
1857
- ```typescript
1858
- // Debounce: Wait until user stops typing
1859
- const debounce = <T extends (...args: any[]) => any>(
1860
- fn: T,
1861
- delay: number
1862
- ): ((...args: Parameters<T>) => void) => {
1863
- let timeoutId: NodeJS.Timeout;
1864
-
1865
- return (...args: Parameters<T>) => {
1866
- clearTimeout(timeoutId);
1867
- timeoutId = setTimeout(() => fn(...args), delay);
1868
- };
1869
- };
1870
-
1871
- // Throttle: Execute at most once per interval
1872
- const throttle = <T extends (...args: any[]) => any>(
1873
- fn: T,
1874
- limit: number
1875
- ): ((...args: Parameters<T>) => void) => {
1876
- let inThrottle: boolean;
1877
-
1878
- return (...args: Parameters<T>) => {
1879
- if (!inThrottle) {
1880
- fn(...args);
1881
- inThrottle = true;
1882
- setTimeout(() => (inThrottle = false), limit);
1883
- }
1884
- };
1885
- };
1886
-
1887
- // Usage
1888
- const searchUsers = debounce(query => api.search(query), 300);
1889
- const handleScroll = throttle(() => console.log('scroll'), 100);
1890
- ```
1891
-
1892
- ## Rate Limiting Concurrent Operations
1893
-
1894
- ```typescript
1895
- async function processWithLimit<T>(
1896
- items: T[],
1897
- fn: (item: T) => Promise<void>,
1898
- concurrency: number
1899
- ): Promise<void> {
1900
- const chunks = [];
1901
- for (let i = 0; i < items.length; i += concurrency) {
1902
- chunks.push(items.slice(i, i + concurrency));
1903
- }
1904
-
1905
- for (const chunk of chunks) {
1906
- await Promise.all(chunk.map(fn));
1907
- }
1908
- }
1909
-
1910
- // Usage: Process 100 items, max 10 at a time
1911
- await processWithLimit(users, updateUser, 10);
1912
- ```
1913
-
1914
-
1915
- ---
1916
-
1917
-
1918
- ## API Design
1919
-
1920
- # API Basics
1921
-
1922
- ## HTTP Methods
1923
-
1924
- Use the right method for each operation:
1925
-
1926
- | Method | Purpose | Example |
1927
- |--------|---------|---------|
1928
- | GET | Read data | Get list of users |
1929
- | POST | Create new resource | Create a new user |
1930
- | PUT | Replace entire resource | Update all user fields |
1931
- | PATCH | Update part of resource | Update user's email only |
1932
- | DELETE | Remove resource | Delete a user |
1933
-
1934
- ## GET - Reading Data
1935
-
1936
- ```pseudocode
1937
- // Get all items
1938
- route GET "/api/users":
1939
- users = getAllUsers()
1940
- return JSON(users)
1941
-
1942
- // Get single item by ID
1943
- route GET "/api/users/:id":
1944
- user = getUserById(params.id)
1945
- if user is null:
1946
- return status 404, JSON({ error: "User not found" })
1947
- return JSON(user)
1948
- ```
1949
-
1950
- ## POST - Creating Data
1951
-
1952
- ```pseudocode
1953
- route POST "/api/users":
1954
- name = request.body.name
1955
- email = request.body.email
1956
-
1957
- // Validate input
1958
- if name is empty or email is empty:
1959
- return status 400, JSON({ error: "Name and email required" })
1960
-
1961
- newUser = createUser({ name, email })
1962
-
1963
- // Return 201 Created with new resource
1964
- return status 201, JSON(newUser)
1965
- ```
1966
-
1967
- ## PUT - Replacing Data
1968
-
1969
- ```pseudocode
1970
- route PUT "/api/users/:id":
1971
- id = params.id
1972
- name = request.body.name
1973
- email = request.body.email
1974
-
1975
- user = getUserById(id)
1976
- if user is null:
1977
- return status 404, JSON({ error: "User not found" })
1978
-
1979
- updated = replaceUser(id, { name, email })
1980
- return JSON(updated)
1981
- ```
1982
-
1983
- ## PATCH - Updating Data
1984
-
1985
- ```pseudocode
1986
- route PATCH "/api/users/:id":
1987
- id = params.id
1988
- updates = request.body // Only fields to update
1989
-
1990
- user = getUserById(id)
1991
- if user is null:
1992
- return status 404, JSON({ error: "User not found" })
1993
-
1994
- updated = updateUser(id, updates)
1995
- return JSON(updated)
1996
- ```
1997
-
1998
- ## DELETE - Removing Data
1999
-
2000
- ```pseudocode
2001
- route DELETE "/api/users/:id":
2002
- id = params.id
2003
-
2004
- user = getUserById(id)
2005
- if user is null:
2006
- return status 404, JSON({ error: "User not found" })
2007
-
2008
- deleteUser(id)
2009
-
2010
- // 204 No Content - successful deletion
2011
- return status 204
2012
- ```
2013
-
2014
- ## HTTP Status Codes
2015
-
2016
- ### Success Codes (2xx)
2017
-
2018
- ```pseudocode
2019
- // 200 OK - Request succeeded
2020
- return status 200, JSON(data)
2021
-
2022
- // 201 Created - New resource created
2023
- return status 201, JSON(newResource)
2024
-
2025
- // 204 No Content - Success with no response body
2026
- return status 204
2027
- ```
2028
-
2029
- ### Client Error Codes (4xx)
2030
-
2031
- ```pseudocode
2032
- // 400 Bad Request - Invalid input
2033
- return status 400, JSON({ error: "Invalid email format" })
2034
-
2035
- // 401 Unauthorized - Not authenticated
2036
- return status 401, JSON({ error: "Login required" })
2037
-
2038
- // 403 Forbidden - Authenticated but not allowed
2039
- return status 403, JSON({ error: "Admin access required" })
2040
-
2041
- // 404 Not Found - Resource doesn't exist
2042
- return status 404, JSON({ error: "User not found" })
2043
-
2044
- // 409 Conflict - Resource already exists
2045
- return status 409, JSON({ error: "Email already registered" })
2046
- ```
2047
-
2048
- ### Server Error Codes (5xx)
2049
-
2050
- ```pseudocode
2051
- // 500 Internal Server Error - Unexpected error
2052
- return status 500, JSON({ error: "Internal server error" })
2053
-
2054
- // 503 Service Unavailable - Temporary issue
2055
- return status 503, JSON({ error: "Database unavailable" })
2056
- ```
2057
-
2058
- ## URL Structure
2059
-
2060
- Use clear, hierarchical URLs:
2061
-
2062
- ```
2063
- ✅ Good
2064
- GET /api/users # List all users
2065
- GET /api/users/123 # Get user 123
2066
- POST /api/users # Create user
2067
- GET /api/users/123/posts # Get posts by user 123
2068
-
2069
- ❌ Bad
2070
- GET /api/getUsers
2071
- POST /api/createUser
2072
- GET /api/user?id=123
2073
- ```
2074
-
2075
- ## Request and Response Format
2076
-
2077
- ### JSON Request Body
2078
-
2079
- ```
2080
- // Client sends
2081
- POST /api/users
2082
- Content-Type: application/json
2083
-
2084
- {
2085
- "name": "Alice",
2086
- "email": "alice@example.com"
2087
- }
2088
- ```
2089
-
2090
- ### JSON Response
2091
-
2092
- ```
2093
- // Server responds
2094
- HTTP/1.1 201 Created
2095
- Content-Type: application/json
2096
-
2097
- {
2098
- "id": 123,
2099
- "name": "Alice",
2100
- "email": "alice@example.com",
2101
- "createdAt": "2024-01-15T10:30:00Z"
2102
- }
2103
- ```
2104
-
2105
- ## Query Parameters
2106
-
2107
- Use query parameters for filtering, sorting, and pagination:
2108
-
2109
- ```pseudocode
2110
- // Filter by status
2111
- // GET /api/orders?status=pending
2112
- route GET "/api/orders":
2113
- status = query.status
2114
- orders = getOrders({ status })
2115
- return JSON(orders)
2116
-
2117
- // Sort by field
2118
- // GET /api/users?sort=name
2119
-
2120
- // Pagination
2121
- // GET /api/users?page=2&limit=20
2122
- ```
2123
-
2124
- ## Error Responses
2125
-
2126
- Always return consistent error format:
2127
-
2128
- ```pseudocode
2129
- // ✅ Good: Structured error
2130
- return status 400, JSON({
2131
- error: {
2132
- code: "VALIDATION_ERROR",
2133
- message: "Invalid input",
2134
- details: {
2135
- email: "Email format is invalid"
2136
- }
2137
- }
2138
- })
2139
-
2140
- // ❌ Bad: Inconsistent
2141
- return status 400, "Bad request"
2142
- return status 400, JSON({ msg: "Error" })
2143
- ```
2144
-
2145
- ## Best Practices
2146
-
2147
- 1. **Use correct HTTP methods** - GET for reading, POST for creating, etc.
2148
- 2. **Use appropriate status codes** - 200 for success, 404 for not found, etc.
2149
- 3. **Return JSON** - Standard format for APIs
2150
- 4. **Validate input** - Check data before processing
2151
- 5. **Handle errors** - Return clear error messages
2152
-
2153
- ```pseudocode
2154
- // Complete example
2155
- route POST "/api/products":
2156
- name = request.body.name
2157
- price = request.body.price
2158
-
2159
- // Validate
2160
- if name is empty or price is empty:
2161
- return status 400, JSON({
2162
- error: "Name and price are required"
2163
- })
2164
-
2165
- if price < 0:
2166
- return status 400, JSON({
2167
- error: "Price cannot be negative"
2168
- })
2169
-
2170
- // Check for duplicates
2171
- if productExists(name):
2172
- return status 409, JSON({
2173
- error: "Product already exists"
2174
- })
2175
-
2176
- // Create
2177
- product = createProduct({ name, price })
2178
-
2179
- // Return success
2180
- return status 201, JSON(product)
2181
- ```
2182
-
2183
- ## Common Mistakes
2184
-
2185
- ```pseudocode
2186
- // ❌ Wrong method for operation
2187
- route GET "/api/users/delete/:id" // Should be DELETE
2188
-
2189
- // ❌ Wrong status code
2190
- route POST "/api/users":
2191
- user = createUser(body)
2192
- return status 200, JSON(user) // Should be 201
2193
-
2194
- // ❌ Not handling missing resources
2195
- route GET "/api/users/:id":
2196
- user = getUserById(params.id)
2197
- return JSON(user) // What if user is null?
2198
-
2199
- // ✅ Correct
2200
- route DELETE "/api/users/:id"
2201
-
2202
- route POST "/api/users":
2203
- user = createUser(body)
2204
- return status 201, JSON(user)
2205
-
2206
- route GET "/api/users/:id":
2207
- user = getUserById(params.id)
2208
- if user is null:
2209
- return status 404, JSON({ error: "User not found" })
2210
- return JSON(user)
2211
- ```
2212
-
2213
-
2214
- # REST API Design
2215
-
2216
- ## Resource-Oriented URLs
2217
-
2218
- ```
2219
- ✅ Good (nouns, plural)
2220
- GET /api/v1/books # List books
2221
- GET /api/v1/books/123 # Get book
2222
- POST /api/v1/books # Create book
2223
- PUT /api/v1/books/123 # Replace book
2224
- PATCH /api/v1/books/123 # Update book
2225
- DELETE /api/v1/books/123 # Delete book
2226
-
2227
- ❌ Bad (verbs, actions)
2228
- POST /api/v1/createBook
2229
- GET /api/v1/getBookById/123
2230
- POST /api/v1/updateBook/123
2231
- ```
2232
-
2233
- ## HTTP Methods
2234
-
2235
- ```typescript
2236
- // GET - Read (safe, idempotent)
2237
- app.get('/api/v1/users/:id', async (req, res) => {
2238
- const user = await userService.findById(req.params.id);
2239
- res.json({ data: user });
2240
- });
2241
-
2242
- // POST - Create (not idempotent)
2243
- app.post('/api/v1/users', async (req, res) => {
2244
- const user = await userService.create(req.body);
2245
- res.status(201)
2246
- .location(`/api/v1/users/${user.id}`)
2247
- .json({ data: user });
2248
- });
2249
-
2250
- // PUT - Replace entire resource (idempotent)
2251
- app.put('/api/v1/users/:id', async (req, res) => {
2252
- const user = await userService.replace(req.params.id, req.body);
2253
- res.json({ data: user });
2254
- });
2255
-
2256
- // PATCH - Partial update (idempotent)
2257
- app.patch('/api/v1/users/:id', async (req, res) => {
2258
- const user = await userService.update(req.params.id, req.body);
2259
- res.json({ data: user });
2260
- });
2261
-
2262
- // DELETE - Remove (idempotent)
2263
- app.delete('/api/v1/users/:id', async (req, res) => {
2264
- await userService.delete(req.params.id);
2265
- res.status(204).end();
2266
- });
2267
- ```
2268
-
2269
- ## Status Codes
2270
-
2271
- ```typescript
2272
- // Success
2273
- 200 OK // GET, PUT, PATCH succeeded
2274
- 201 Created // POST succeeded
2275
- 204 No Content // DELETE succeeded
2276
-
2277
- // Client errors
2278
- 400 Bad Request // Validation failed
2279
- 401 Unauthorized // Not authenticated
2280
- 403 Forbidden // Authenticated but not allowed
2281
- 404 Not Found // Resource doesn't exist
2282
- 409 Conflict // Duplicate, version conflict
2283
- 422 Unprocessable // Business rule violation
2284
-
2285
- // Server errors
2286
- 500 Internal Server Error
2287
- ```
2288
-
2289
- ## Response Format
2290
-
2291
- ```typescript
2292
- // Single resource
2293
- {
2294
- "data": {
2295
- "id": 123,
2296
- "name": "John Doe",
2297
- "email": "john@example.com"
2298
- }
2299
- }
2300
-
2301
- // Collection with pagination
2302
- {
2303
- "data": [
2304
- { "id": 1, "name": "Item 1" },
2305
- { "id": 2, "name": "Item 2" }
2306
- ],
2307
- "pagination": {
2308
- "page": 1,
2309
- "limit": 20,
2310
- "total": 150,
2311
- "totalPages": 8
2312
- }
2313
- }
2314
-
2315
- // Error response
2316
- {
2317
- "error": {
2318
- "code": "VALIDATION_ERROR",
2319
- "message": "The request contains invalid data",
2320
- "details": [
2321
- { "field": "email", "message": "Invalid email format" }
2322
- ]
2323
- }
2324
- }
2325
- ```
2326
-
2327
- ## Hierarchical Resources
2328
-
2329
- ```
2330
- ✅ Limit nesting to 2-3 levels
2331
- GET /api/v1/authors/456/books # Books by author
2332
- GET /api/v1/orders/789/items # Items in order
2333
-
2334
- ❌ Too deep
2335
- GET /api/v1/publishers/1/authors/2/books/3/reviews/4
2336
-
2337
- ✅ Use query parameters instead
2338
- GET /api/v1/reviews?bookId=3
2339
- ```
2340
-
2341
- ## API Versioning
2342
-
2343
- ```
2344
- ✅ Always version from the start
2345
- /api/v1/books
2346
- /api/v2/books
2347
-
2348
- ❌ No version
2349
- /api/books
2350
- ```
2351
-
2352
-
2353
- # API Pagination
2354
-
2355
- ## Always Paginate Collections
2356
-
2357
- ```typescript
2358
- // ✅ Paginated endpoint
2359
- app.get('/api/v1/books', async (req, res) => {
2360
- const page = parseInt(req.query.page as string) || 1;
2361
- const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
2362
-
2363
- const { data, total } = await bookService.findAll({ page, limit });
2364
-
2365
- res.json({
2366
- data,
2367
- pagination: {
2368
- page,
2369
- limit,
2370
- total,
2371
- totalPages: Math.ceil(total / limit),
2372
- hasNext: page * limit < total,
2373
- hasPrevious: page > 1
2374
- }
2375
- });
2376
- });
2377
- ```
2378
-
2379
- ## Offset-Based Pagination
2380
-
2381
- ```typescript
2382
- // Simple but has issues with large datasets
2383
- GET /api/v1/books?page=1&limit=20
2384
- GET /api/v1/books?page=2&limit=20
2385
-
2386
- // Implementation
2387
- const getBooks = async (page: number, limit: number) => {
2388
- const offset = (page - 1) * limit;
2389
-
2390
- const [data, total] = await Promise.all([
2391
- db.query('SELECT * FROM books ORDER BY id LIMIT ? OFFSET ?', [limit, offset]),
2392
- db.query('SELECT COUNT(*) FROM books')
2393
- ]);
2394
-
2395
- return { data, total };
2396
- };
2397
- ```
2398
-
2399
- ## Cursor-Based Pagination
2400
-
2401
- ```typescript
2402
- // Better for large datasets and real-time data
2403
- GET /api/v1/books?cursor=eyJpZCI6MTIzfQ&limit=20
2404
-
2405
- // Response includes next cursor
2406
- {
2407
- "data": [...],
2408
- "pagination": {
2409
- "nextCursor": "eyJpZCI6MTQzfQ",
2410
- "hasMore": true
2411
- }
2412
- }
2413
-
2414
- // Implementation
2415
- const getBooks = async (cursor: string | null, limit: number) => {
2416
- let query = 'SELECT * FROM books';
2417
-
2418
- if (cursor) {
2419
- const { id } = decodeCursor(cursor);
2420
- query += ` WHERE id > ${id}`;
2421
- }
2422
-
2423
- query += ` ORDER BY id LIMIT ${limit + 1}`;
2424
- const data = await db.query(query);
2425
-
2426
- const hasMore = data.length > limit;
2427
- const items = hasMore ? data.slice(0, limit) : data;
2428
-
2429
- return {
2430
- data: items,
2431
- pagination: {
2432
- nextCursor: hasMore ? encodeCursor({ id: items[items.length - 1].id }) : null,
2433
- hasMore
2434
- }
2435
- };
2436
- };
2437
- ```
2438
-
2439
- ## Keyset Pagination
2440
-
2441
- ```sql
2442
- -- Most efficient for large tables
2443
- -- First page
2444
- SELECT * FROM products
2445
- ORDER BY created_at DESC, id DESC
2446
- LIMIT 20;
2447
-
2448
- -- Next page (using last item's values)
2449
- SELECT * FROM products
2450
- WHERE (created_at, id) < ('2024-01-15 10:00:00', 12345)
2451
- ORDER BY created_at DESC, id DESC
2452
- LIMIT 20;
2453
- ```
2454
-
2455
- ## HATEOAS Links
2456
-
2457
- ```typescript
2458
- // Include navigation links
2459
- {
2460
- "data": [...],
2461
- "pagination": {
2462
- "page": 2,
2463
- "limit": 20,
2464
- "total": 150
2465
- },
2466
- "links": {
2467
- "self": "/api/v1/books?page=2&limit=20",
2468
- "first": "/api/v1/books?page=1&limit=20",
2469
- "prev": "/api/v1/books?page=1&limit=20",
2470
- "next": "/api/v1/books?page=3&limit=20",
2471
- "last": "/api/v1/books?page=8&limit=20"
2472
- }
2473
- }
2474
- ```
2475
-
2476
- ## Pagination Best Practices
2477
-
2478
- ```typescript
2479
- // ✅ Set reasonable defaults and limits
2480
- const page = parseInt(req.query.page) || 1;
2481
- const limit = Math.min(parseInt(req.query.limit) || 20, 100);
2482
-
2483
- // ✅ Include total count (when practical)
2484
- const total = await db.count('books');
2485
-
2486
- // ✅ Use consistent response structure
2487
- {
2488
- "data": [],
2489
- "pagination": { ... }
2490
- }
2491
-
2492
- // ❌ Don't return unlimited results
2493
- // ❌ Don't allow page < 1 or limit < 1
2494
- ```
2495
-
2496
-
2497
- # API Versioning
2498
-
2499
- ## Versioning Strategies
2500
-
2501
- ### URL Path Versioning
2502
- ```
2503
- GET /api/v1/users
2504
- GET /api/v2/users
2505
- ```
2506
-
2507
- ### Header Versioning
2508
- ```
2509
- GET /api/users
2510
- Accept: application/vnd.api+json; version=2
2511
- ```
2512
-
2513
- ### Query Parameter
2514
- ```
2515
- GET /api/users?version=2
2516
- ```
2517
-
2518
- ## Implementation
2519
-
2520
- ```typescript
2521
- // URL path versioning
2522
- app.use('/api/v1', v1Router);
2523
- app.use('/api/v2', v2Router);
2524
-
2525
- // Header versioning middleware
2526
- function versionMiddleware(req, res, next) {
2527
- const version = req.headers['api-version'] || '1';
2528
- req.apiVersion = parseInt(version);
2529
- next();
2530
- }
2531
-
2532
- app.get('/users', versionMiddleware, (req, res) => {
2533
- if (req.apiVersion >= 2) {
2534
- return handleV2(req, res);
2535
- }
2536
- return handleV1(req, res);
2537
- });
2538
- ```
2539
-
2540
- ## Deprecation Strategy
2541
-
2542
- ```typescript
2543
- // Add deprecation headers
2544
- res.setHeader('Deprecation', 'true');
2545
- res.setHeader('Sunset', 'Sat, 01 Jan 2025 00:00:00 GMT');
2546
- res.setHeader('Link', '</api/v2/users>; rel="successor-version"');
2547
- ```
2548
-
2549
- ## Best Practices
2550
-
2551
- - Version from the start
2552
- - Support at least N-1 versions
2553
- - Document deprecation timeline
2554
- - Provide migration guides
2555
- - Use semantic versioning for breaking changes
2556
- - Consider backwards-compatible changes first
2557
-
2558
-
2559
- ---
2560
-
2561
-
2562
- ## Code Style
2563
-
2564
- # Naming Conventions
2565
-
2566
- ## Variables and Functions
2567
-
2568
- ```typescript
2569
- // camelCase for variables and functions
2570
- const userName = 'John';
2571
- const isActive = true;
2572
- const itemCount = 42;
2573
-
2574
- function calculateTotal(items: Item[]): number {
2575
- return items.reduce((sum, item) => sum + item.price, 0);
2576
- }
2577
-
2578
- // Boolean variables: use is/has/can/should prefix
2579
- const isValid = validate(input);
2580
- const hasPermission = checkPermission(user);
2581
- const canEdit = user.role === 'admin';
2582
- const shouldRetry = error.code === 'TIMEOUT';
2583
-
2584
- // Collections: use plural names
2585
- const users = getUsers();
2586
- const activeOrders = orders.filter(o => o.status === 'active');
2587
- ```
2588
-
2589
- ## Constants
2590
-
2591
- ```typescript
2592
- // UPPER_SNAKE_CASE for constants
2593
- const MAX_RETRY_ATTEMPTS = 3;
2594
- const DEFAULT_TIMEOUT_MS = 5000;
2595
- const API_BASE_URL = 'https://api.example.com';
2596
-
2597
- // Enum-like objects
2598
- const ORDER_STATUS = {
2599
- PENDING: 'pending',
2600
- PROCESSING: 'processing',
2601
- SHIPPED: 'shipped',
2602
- DELIVERED: 'delivered',
2603
- CANCELLED: 'cancelled'
2604
- } as const;
2605
-
2606
- const HTTP_STATUS = {
2607
- OK: 200,
2608
- CREATED: 201,
2609
- BAD_REQUEST: 400,
2610
- NOT_FOUND: 404
2611
- } as const;
2612
- ```
2613
-
2614
- ## Classes and Types
2615
-
2616
- ```typescript
2617
- // PascalCase for classes and types
2618
- class UserService {
2619
- constructor(private userRepository: UserRepository) {}
2620
- }
2621
-
2622
- interface User {
2623
- id: string;
2624
- name: string;
2625
- email: string;
2626
- }
2627
-
2628
- type UserRole = 'admin' | 'editor' | 'viewer';
2629
-
2630
- // Avoid prefixes
2631
- // ❌ IUser, CUser, TUser
2632
- // ✅ User
2633
- ```
2634
-
2635
- ## Files and Modules
2636
-
2637
- ```typescript
2638
- // kebab-case for files
2639
- user-service.ts
2640
- order-repository.ts
2641
- create-user.dto.ts
2642
-
2643
- // Match file name to primary export
2644
- // user-service.ts exports UserService
2645
- // order-repository.ts exports OrderRepository
2646
- ```
2647
-
2648
- ## Avoid Bad Names
2649
-
2650
- ```typescript
2651
- // ❌ Bad - unclear
2652
- const d = Date.now();
2653
- const tmp = user.name;
2654
- const data = fetchData();
2655
- const flag = true;
2656
-
2657
- // ✅ Good - descriptive
2658
- const currentDate = Date.now();
2659
- const originalUserName = user.name;
2660
- const customerOrders = fetchCustomerOrders();
2661
- const isEmailVerified = true;
2662
- ```
2663
-
2664
- ## Avoid Magic Numbers
2665
-
2666
- ```typescript
2667
- // ❌ Magic numbers
2668
- if (user.age >= 18) { ... }
2669
- if (items.length > 100) { ... }
2670
- setTimeout(callback, 5000);
2671
-
2672
- // ✅ Named constants
2673
- const LEGAL_AGE = 18;
2674
- const MAX_BATCH_SIZE = 100;
2675
- const DEFAULT_TIMEOUT_MS = 5000;
2676
-
2677
- if (user.age >= LEGAL_AGE) { ... }
2678
- if (items.length > MAX_BATCH_SIZE) { ... }
2679
- setTimeout(callback, DEFAULT_TIMEOUT_MS);
2680
- ```
2681
-
2682
- ## Consistency
2683
-
2684
- ```typescript
2685
- // Pick ONE style and stick with it across the project
2686
-
2687
- // ✅ Consistent camelCase in APIs
2688
- {
2689
- "userId": 123,
2690
- "firstName": "John",
2691
- "createdAt": "2024-01-01"
2692
- }
2693
-
2694
- // ❌ Mixed styles
2695
- {
2696
- "user_id": 123, // snake_case
2697
- "firstName": "John", // camelCase - inconsistent!
2698
- }
2699
- ```
2700
-
2701
-
2702
- # Code Organization
2703
-
2704
- ## Function Length
2705
-
2706
- ```typescript
2707
- // ❌ Function too long (>50 lines)
2708
- function processOrder(orderId: string) {
2709
- // 200 lines of validation, payment, inventory, shipping...
2710
- }
2711
-
2712
- // ✅ Extract into smaller, focused functions
2713
- function processOrder(orderId: string) {
2714
- const order = fetchOrder(orderId);
2715
-
2716
- validateOrder(order);
2717
- reserveInventory(order.items);
2718
- processPayment(order);
2719
- scheduleShipping(order);
2720
- sendConfirmation(order.customer.email);
2721
-
2722
- return order;
2723
- }
2724
- ```
2725
-
2726
- ## Nesting Depth
2727
-
2728
- ```typescript
2729
- // ❌ Too much nesting (>3 levels)
2730
- if (user) {
2731
- if (user.isActive) {
2732
- if (user.hasPermission('edit')) {
2733
- if (resource.isAvailable) {
2734
- // Deep nesting is hard to follow
2735
- }
2736
- }
2737
- }
2738
- }
2739
-
2740
- // ✅ Guard clauses to reduce nesting
2741
- if (!user) return;
2742
- if (!user.isActive) return;
2743
- if (!user.hasPermission('edit')) return;
2744
- if (!resource.isAvailable) return;
2745
-
2746
- // Clear logic at top level
2747
-
2748
- // ✅ Extract complex conditions
2749
- function canEditResource(user: User, resource: Resource): boolean {
2750
- return user &&
2751
- user.isActive &&
2752
- user.hasPermission('edit') &&
2753
- resource.isAvailable;
2754
- }
2755
-
2756
- if (canEditResource(user, resource)) {
2757
- // Single level of nesting
2758
- }
2759
- ```
2760
-
2761
- ## File Length
2762
-
2763
- ```typescript
2764
- // ❌ God file (1000+ lines)
2765
- // user-service.ts with 50 methods handling users, auth, permissions...
2766
-
2767
- // ✅ Split into focused modules (~200-300 lines each)
2768
- // user-service.ts - CRUD operations
2769
- // auth-service.ts - login, logout, tokens
2770
- // permission-service.ts - role checks
2771
- ```
2772
-
2773
- ## File Organization
2774
-
2775
- ```typescript
2776
- // Consistent structure within files:
2777
-
2778
- // 1. Imports (grouped and ordered)
2779
- import fs from 'fs'; // Standard library
2780
- import express from 'express'; // External dependencies
2781
- import { UserService } from './user'; // Internal modules
2782
-
2783
- // 2. Constants and type definitions
2784
- const MAX_RETRIES = 3;
2785
-
2786
- interface UserDTO {
2787
- id: string;
2788
- name: string;
2789
- }
2790
-
2791
- // 3. Helper functions (if needed)
2792
- function validateInput(input: unknown): boolean {
2793
- // ...
2794
- }
2795
-
2796
- // 4. Main exports/classes
2797
- export class OrderService {
2798
- // ...
2799
- }
2800
-
2801
- // 5. Module initialization (if applicable)
2802
- export default new OrderService();
2803
- ```
2804
-
2805
- ## Single Responsibility
2806
-
2807
- ```typescript
2808
- // ❌ Class doing too much
2809
- class UserManager {
2810
- createUser() {}
2811
- updateUser() {}
2812
- sendEmail() {}
2813
- hashPassword() {}
2814
- generateToken() {}
2815
- }
2816
-
2817
- // ✅ Split by responsibility
2818
- class UserRepository {
2819
- create(user: User) {}
2820
- update(id: string, data: Partial<User>) {}
2821
- }
2822
-
2823
- class EmailService {
2824
- send(to: string, template: string) {}
2825
- }
2826
-
2827
- class PasswordService {
2828
- hash(password: string): string {}
2829
- verify(password: string, hash: string): boolean {}
2830
- }
2831
-
2832
- class AuthService {
2833
- generateToken(userId: string): string {}
2834
- }
2835
- ```
2836
-
2837
- ## DRY (Don't Repeat Yourself)
2838
-
2839
- ```typescript
2840
- // ❌ Duplicated logic
2841
- function processUserOrder(order: Order) {
2842
- const total = order.items.reduce((sum, i) => sum + i.price * i.quantity, 0);
2843
- const tax = total * 0.08;
2844
- return total + tax;
2845
- }
2846
-
2847
- function processGuestOrder(order: Order) {
2848
- const total = order.items.reduce((sum, i) => sum + i.price * i.quantity, 0);
2849
- const tax = total * 0.08;
2850
- return total + tax;
2851
- }
2852
-
2853
- // ✅ Extract common logic
2854
- function calculateOrderTotal(items: Item[]): number {
2855
- const subtotal = items.reduce((sum, i) => sum + i.price * i.quantity, 0);
2856
- const tax = subtotal * 0.08;
2857
- return subtotal + tax;
2858
- }
2859
-
2860
- function processUserOrder(order: Order) {
2861
- return calculateOrderTotal(order.items);
2862
- }
2863
- ```
2864
-
2865
-
2866
- ---
2867
-
2868
-
2869
- ## Error Handling
2870
-
2871
- # Error Handling Strategy
2872
-
2873
- ## Custom Error Classes
2874
-
2875
- ```typescript
2876
- class AppError extends Error {
2877
- constructor(
2878
- message: string,
2879
- public statusCode: number = 500,
2880
- public code: string = 'INTERNAL_ERROR',
2881
- public details?: unknown
2882
- ) {
2883
- super(message);
2884
- this.name = this.constructor.name;
2885
- Error.captureStackTrace(this, this.constructor);
2886
- }
2887
- }
2888
-
2889
- class NotFoundError extends AppError {
2890
- constructor(resource: string, id: string) {
2891
- super(`${resource} with id ${id} not found`, 404, 'NOT_FOUND', { resource, id });
2892
- }
2893
- }
2894
-
2895
- class ValidationError extends AppError {
2896
- constructor(message: string, details: unknown) {
2897
- super(message, 400, 'VALIDATION_ERROR', details);
2898
- }
2899
- }
2900
-
2901
- // Usage
2902
- if (!user) {
2903
- throw new NotFoundError('User', userId);
2904
- }
2905
- ```
2906
-
2907
- ## Never Swallow Errors
2908
-
2909
- ```typescript
2910
- // ❌ Silent failure - dangerous!
2911
- try {
2912
- await criticalOperation();
2913
- } catch (error) {
2914
- // Error ignored
2915
- }
2916
-
2917
- // ✅ Log and handle appropriately
2918
- try {
2919
- await criticalOperation();
2920
- } catch (error) {
2921
- logger.error('Critical operation failed', { error });
2922
- throw error; // Or handle gracefully
2923
- }
2924
- ```
2925
-
2926
- ## Specific Error Messages
2927
-
2928
- ```typescript
2929
- // ❌ Not actionable
2930
- throw new Error('Something went wrong');
2931
-
2932
- // ✅ Specific and actionable
2933
- throw new ValidationError('Email must be a valid email address', {
2934
- field: 'email',
2935
- value: userInput.email
2936
- });
2937
- ```
2938
-
2939
- ## Centralized Error Handler
2940
-
2941
- ```typescript
2942
- // Express error middleware
2943
- app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
2944
- if (err instanceof AppError) {
2945
- return res.status(err.statusCode).json({
2946
- error: {
2947
- message: err.message,
2948
- code: err.code,
2949
- details: err.details
2950
- }
2951
- });
2952
- }
2953
-
2954
- // Log unexpected errors
2955
- console.error('Unexpected error:', err);
2956
-
2957
- // Don't expose internal details
2958
- res.status(500).json({
2959
- error: {
2960
- message: 'Internal server error',
2961
- code: 'INTERNAL_ERROR'
2962
- }
2963
- });
2964
- });
2965
- ```
2966
-
2967
- ## Async Error Wrapper
2968
-
2969
- ```typescript
2970
- // Wrap async handlers to catch errors automatically
2971
- const asyncHandler = (fn: RequestHandler) => {
2972
- return (req: Request, res: Response, next: NextFunction) => {
2973
- Promise.resolve(fn(req, res, next)).catch(next);
2974
- };
2975
- };
2976
-
2977
- // Usage
2978
- app.get('/users/:id', asyncHandler(async (req, res) => {
2979
- const user = await getUser(req.params.id);
2980
- res.json(user);
2981
- }));
2982
- ```
2983
-
2984
- ## Result Type Pattern
2985
-
2986
- ```typescript
2987
- type Result<T, E = Error> =
2988
- | { success: true; value: T }
2989
- | { success: false; error: E };
2990
-
2991
- function parseJSON<T>(json: string): Result<T, string> {
2992
- try {
2993
- return { success: true, value: JSON.parse(json) };
2994
- } catch {
2995
- return { success: false, error: 'Invalid JSON' };
2996
- }
2997
- }
2998
-
2999
- // Usage - forces explicit error handling
3000
- const result = parseJSON<User>(data);
3001
- if (result.success) {
3002
- console.log(result.value.name);
3003
- } else {
3004
- console.error(result.error);
3005
- }
3006
- ```
3007
-
3008
- ## Error Boundaries (React)
3009
-
3010
- ```typescript
3011
- class ErrorBoundary extends React.Component {
3012
- state = { hasError: false };
3013
-
3014
- static getDerivedStateFromError(error: Error) {
3015
- return { hasError: true };
3016
- }
3017
-
3018
- componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
3019
- logger.error('UI Error', { error, errorInfo });
3020
- }
3021
-
3022
- render() {
3023
- if (this.state.hasError) {
3024
- return <FallbackUI />;
3025
- }
3026
- return this.props.children;
3027
- }
3028
- }
3029
- ```
3030
-
3031
- ## Retry with Limits
3032
-
3033
- ```typescript
3034
- // ❌ Infinite retries
3035
- while (true) {
3036
- try {
3037
- await operation();
3038
- break;
3039
- } catch (error) {
3040
- // Retry forever - exhausts resources
3041
- }
3042
- }
3043
-
3044
- // ✅ Limited retries with backoff
3045
- const maxRetries = 3;
3046
- for (let i = 0; i < maxRetries; i++) {
3047
- try {
3048
- await operation();
3049
- break;
3050
- } catch (error) {
3051
- if (i === maxRetries - 1) throw error;
3052
- await sleep(Math.pow(2, i) * 1000); // Exponential backoff
3053
- }
3054
- }
3055
- ```
3056
-
3057
-
3058
- # Error Handling Basics
3059
-
3060
- ## Throwing Errors
3061
-
3062
- Use `throw` to signal when something goes wrong.
3063
-
3064
- ```pseudocode
3065
- function divide(a, b):
3066
- if b == 0:
3067
- throw Error("Cannot divide by zero")
3068
- return a / b
3069
- ```
3070
-
3071
- ## Try-Catch
3072
-
3073
- Use `try-catch` to handle errors gracefully.
3074
-
3075
- ```pseudocode
3076
- try:
3077
- result = divide(10, 0)
3078
- print(result)
3079
- catch error:
3080
- print("Error:", error.message)
3081
- // Continue with fallback behavior
3082
- ```
3083
-
3084
- ## Always Provide Error Messages
3085
-
3086
- Make error messages clear and actionable.
3087
-
3088
- ```pseudocode
3089
- // ❌ Bad: Vague error
3090
- throw Error("Invalid")
3091
-
3092
- // ✅ Good: Specific error
3093
- throw Error("Email must be a valid email address")
3094
-
3095
- // ✅ Good: Include context
3096
- throw Error("User with ID " + userId + " not found")
3097
- ```
3098
-
3099
- ## Async Error Handling
3100
-
3101
- Always use try-catch with async operations.
3102
-
3103
- ```pseudocode
3104
- async function loadUser(id):
3105
- try:
3106
- user = await fetchUser(id)
3107
- return user
3108
- catch error:
3109
- log("Failed to load user:", error)
3110
- throw error // Re-throw if caller should know
3111
- ```
3112
-
3113
- ## When to Catch Errors
3114
-
3115
- ### Catch When You Can Handle It
3116
-
3117
- ```pseudocode
3118
- // ✅ Good: Can provide fallback
3119
- async function getUserName(id):
3120
- try:
3121
- user = await fetchUser(id)
3122
- return user.name
3123
- catch error:
3124
- return "Unknown User" // Fallback value
3125
- ```
3126
-
3127
- ### Don't Catch If You Can't Handle
3128
-
3129
- ```pseudocode
3130
- // ❌ Bad: Catching but doing nothing
3131
- try:
3132
- await criticalOperation()
3133
- catch error:
3134
- // Empty catch - error lost!
3135
-
3136
- // ✅ Good: Let it propagate
3137
- await criticalOperation() // Caller will handle
3138
- ```
3139
-
3140
- ## Validate Input Early
3141
-
3142
- Check for problems before they cause errors.
3143
-
3144
- ```pseudocode
3145
- function createUser(email, age):
3146
- // Validate inputs first
3147
- if email is empty:
3148
- throw Error("Email is required")
3149
-
3150
- if not email.contains("@"):
3151
- throw Error("Email must be valid")
3152
-
3153
- if age < 0:
3154
- throw Error("Age cannot be negative")
3155
-
3156
- // Now proceed with creation
3157
- return { email: email, age: age }
3158
- ```
3159
-
3160
- ## Logging Errors
3161
-
3162
- Always log errors for debugging.
3163
-
3164
- ```pseudocode
3165
- try:
3166
- await processPayment(orderId)
3167
- catch error:
3168
- // Log with context
3169
- log("Payment processing failed:", {
3170
- orderId: orderId,
3171
- error: error.message,
3172
- timestamp: currentTime()
3173
- })
3174
- throw error
3175
- ```
3176
-
3177
- ## Error Handling Patterns
3178
-
3179
- ### Return Error Status
3180
-
3181
- ```pseudocode
3182
- function parseNumber(input):
3183
- num = parseInt(input)
3184
- if isNaN(num):
3185
- return null // Indicate failure without throwing
3186
- return num
3187
-
3188
- // Usage
3189
- result = parseNumber(userInput)
3190
- if result is null:
3191
- print("Invalid number")
3192
- else:
3193
- print("Number:", result)
3194
- ```
3195
-
3196
- ### Return Success/Error Object
3197
-
3198
- ```pseudocode
3199
- function safeDivide(a, b):
3200
- if b == 0:
3201
- return { success: false, error: "Cannot divide by zero" }
3202
- return { success: true, data: a / b }
3203
-
3204
- // Usage
3205
- result = safeDivide(10, 2)
3206
- if result.success:
3207
- print("Result:", result.data)
3208
- else:
3209
- print("Error:", result.error)
3210
- ```
3211
-
3212
- ## Common Mistakes
3213
-
3214
- ### Don't Swallow Errors
3215
-
3216
- ```pseudocode
3217
- // ❌ Bad: Error disappears
3218
- try:
3219
- await importantOperation()
3220
- catch error:
3221
- // Nothing here - error lost!
3222
-
3223
- // ✅ Good: Log at minimum
3224
- try:
3225
- await importantOperation()
3226
- catch error:
3227
- log("Operation failed:", error)
3228
- throw error // Or handle appropriately
3229
- ```
3230
-
3231
- ### Don't Use Errors for Flow Control
3232
-
3233
- ```pseudocode
3234
- // ❌ Bad: Using errors for normal logic
3235
- try:
3236
- user = findUser(id)
3237
- // ...
3238
- catch error:
3239
- // User not found - this is expected, not an error!
3240
-
3241
- // ✅ Good: Return null for expected cases
3242
- user = findUser(id)
3243
- if user is null:
3244
- print("User not found")
3245
- return
3246
- ```
3247
-
3248
- ### Don't Throw Non-Error Objects
3249
-
3250
- ```pseudocode
3251
- // ❌ Bad
3252
- throw "Something went wrong" // String
3253
- throw { code: 500 } // Plain object
3254
-
3255
- // ✅ Good
3256
- throw Error("Something went wrong")
3257
- ```
3258
-
3259
- ## Best Practices
3260
-
3261
- 1. **Fail fast** - Validate input early and throw immediately
3262
- 2. **Be specific** - Provide detailed, actionable error messages
3263
- 3. **Log errors** - Always log for debugging
3264
- 4. **Don't hide errors** - Let them propagate if you can't handle them
3265
- 5. **Clean up resources** - Close connections, files in finally blocks
3266
-
3267
- ```pseudocode
3268
- file = null
3269
- try:
3270
- file = await openFile("data.txt")
3271
- await processFile(file)
3272
- catch error:
3273
- log("File processing failed:", error)
3274
- throw error
3275
- finally:
3276
- // Always runs, even if error thrown
3277
- if file is not null:
3278
- await file.close()
3279
- ```
3280
-
3281
-
3282
- ---
3283
-
3284
-
3285
- ## Architecture
3286
-
3287
- # SOLID Principles
3288
-
3289
- ## Single Responsibility Principle (SRP)
3290
-
3291
- A class should have only one reason to change.
3292
-
3293
- **Bad:**
3294
- ```typescript
3295
- class UserService {
3296
- createUser(data: UserData): User { /* ... */ }
3297
- sendWelcomeEmail(user: User): void { /* ... */ }
3298
- generateReport(users: User[]): Report { /* ... */ }
3299
- }
3300
- ```
3301
-
3302
- **Good:**
3303
- ```typescript
3304
- class UserService {
3305
- createUser(data: UserData): User { /* ... */ }
3306
- }
3307
-
3308
- class EmailService {
3309
- sendWelcomeEmail(user: User): void { /* ... */ }
3310
- }
3311
-
3312
- class ReportService {
3313
- generateUserReport(users: User[]): Report { /* ... */ }
3314
- }
3315
- ```
3316
-
3317
- ## Open/Closed Principle (OCP)
3318
-
3319
- Open for extension, closed for modification.
3320
-
3321
- **Bad:**
3322
- ```typescript
3323
- class PaymentProcessor {
3324
- process(payment: Payment): void {
3325
- if (payment.type === 'credit') { /* credit logic */ }
3326
- else if (payment.type === 'paypal') { /* paypal logic */ }
3327
- // Must modify class to add new payment types
3328
- }
3329
- }
3330
- ```
3331
-
3332
- **Good:**
3333
- ```typescript
3334
- interface PaymentHandler {
3335
- process(payment: Payment): void;
3336
- }
3337
-
3338
- class CreditCardHandler implements PaymentHandler {
3339
- process(payment: Payment): void { /* credit logic */ }
3340
- }
3341
-
3342
- class PayPalHandler implements PaymentHandler {
3343
- process(payment: Payment): void { /* paypal logic */ }
3344
- }
3345
-
3346
- class PaymentProcessor {
3347
- constructor(private handlers: Map<string, PaymentHandler>) {}
3348
-
3349
- process(payment: Payment): void {
3350
- this.handlers.get(payment.type)?.process(payment);
3351
- }
3352
- }
3353
- ```
3354
-
3355
- ## Liskov Substitution Principle (LSP)
3356
-
3357
- Subtypes must be substitutable for their base types.
3358
-
3359
- **Bad:**
3360
- ```typescript
3361
- class Bird {
3362
- fly(): void { /* flying logic */ }
3363
- }
3364
-
3365
- class Penguin extends Bird {
3366
- fly(): void {
3367
- throw new Error("Penguins can't fly!"); // Violates LSP
3368
- }
3369
- }
3370
- ```
3371
-
3372
- **Good:**
3373
- ```typescript
3374
- interface Bird {
3375
- move(): void;
3376
- }
3377
-
3378
- class FlyingBird implements Bird {
3379
- move(): void { this.fly(); }
3380
- private fly(): void { /* flying logic */ }
3381
- }
3382
-
3383
- class Penguin implements Bird {
3384
- move(): void { this.swim(); }
3385
- private swim(): void { /* swimming logic */ }
3386
- }
3387
- ```
3388
-
3389
- ## Interface Segregation Principle (ISP)
3390
-
3391
- Clients shouldn't depend on interfaces they don't use.
3392
-
3393
- **Bad:**
3394
- ```typescript
3395
- interface Worker {
3396
- work(): void;
3397
- eat(): void;
3398
- sleep(): void;
3399
- }
3400
-
3401
- class Robot implements Worker {
3402
- work(): void { /* ... */ }
3403
- eat(): void { throw new Error("Robots don't eat"); }
3404
- sleep(): void { throw new Error("Robots don't sleep"); }
3405
- }
3406
- ```
3407
-
3408
- **Good:**
3409
- ```typescript
3410
- interface Workable {
3411
- work(): void;
3412
- }
3413
-
3414
- interface Eatable {
3415
- eat(): void;
3416
- }
3417
-
3418
- interface Sleepable {
3419
- sleep(): void;
3420
- }
3421
-
3422
- class Human implements Workable, Eatable, Sleepable {
3423
- work(): void { /* ... */ }
3424
- eat(): void { /* ... */ }
3425
- sleep(): void { /* ... */ }
3426
- }
3427
-
3428
- class Robot implements Workable {
3429
- work(): void { /* ... */ }
3430
- }
3431
- ```
3432
-
3433
- ## Dependency Inversion Principle (DIP)
3434
-
3435
- Depend on abstractions, not concretions.
3436
-
3437
- **Bad:**
3438
- ```typescript
3439
- class UserService {
3440
- private database = new MySQLDatabase();
3441
-
3442
- getUser(id: string): User {
3443
- return this.database.query(`SELECT * FROM users WHERE id = '${id}'`);
3444
- }
3445
- }
3446
- ```
3447
-
3448
- **Good:**
3449
- ```typescript
3450
- interface Database {
3451
- query(sql: string): any;
3452
- }
3453
-
3454
- class UserService {
3455
- constructor(private database: Database) {}
3456
-
3457
- getUser(id: string): User {
3458
- return this.database.query(`SELECT * FROM users WHERE id = '${id}'`);
3459
- }
3460
- }
3461
-
3462
- // Can inject any database implementation
3463
- const userService = new UserService(new MySQLDatabase());
3464
- const testService = new UserService(new InMemoryDatabase());
3465
- ```
3466
-
3467
- ## Best Practices
3468
-
3469
- - Apply SRP at class, method, and module levels
3470
- - Use interfaces and dependency injection for flexibility
3471
- - Prefer composition over inheritance
3472
- - Design small, focused interfaces
3473
- - Inject dependencies rather than creating them internally
3474
-
3475
-
3476
- # GUI Architecture Patterns
3477
-
3478
- ## MVC (Model-View-Controller)
3479
-
3480
- ```typescript
3481
- // Model - data and business logic
3482
- class UserModel {
3483
- private users: User[] = [];
3484
-
3485
- getUsers(): User[] { return this.users; }
3486
- addUser(user: User): void { this.users.push(user); }
3487
- }
3488
-
3489
- // View - presentation
3490
- class UserView {
3491
- render(users: User[]): void {
3492
- console.log('Users:', users);
3493
- }
3494
- }
3495
-
3496
- // Controller - handles input, coordinates
3497
- class UserController {
3498
- constructor(
3499
- private model: UserModel,
3500
- private view: UserView
3501
- ) {}
3502
-
3503
- handleAddUser(userData: UserData): void {
3504
- const user = new User(userData);
3505
- this.model.addUser(user);
3506
- this.view.render(this.model.getUsers());
3507
- }
3508
- }
3509
- ```
3510
-
3511
- ## MVP (Model-View-Presenter)
3512
-
3513
- ```typescript
3514
- // View interface - defines what presenter can call
3515
- interface UserView {
3516
- showUsers(users: User[]): void;
3517
- showError(message: string): void;
3518
- }
3519
-
3520
- // Presenter - all presentation logic
3521
- class UserPresenter {
3522
- constructor(
3523
- private view: UserView,
3524
- private model: UserModel
3525
- ) {}
3526
-
3527
- loadUsers(): void {
3528
- try {
3529
- const users = this.model.getUsers();
3530
- this.view.showUsers(users);
3531
- } catch (error) {
3532
- this.view.showError('Failed to load users');
3533
- }
3534
- }
3535
- }
3536
-
3537
- // View implementation - passive, no logic
3538
- class UserListView implements UserView {
3539
- showUsers(users: User[]): void { /* render list */ }
3540
- showError(message: string): void { /* show error */ }
3541
- }
3542
- ```
3543
-
3544
- ## MVVM (Model-View-ViewModel)
3545
-
3546
- ```typescript
3547
- // ViewModel - exposes observable state
3548
- class UserViewModel {
3549
- users = observable<User[]>([]);
3550
- isLoading = observable(false);
3551
-
3552
- async loadUsers(): Promise<void> {
3553
- this.isLoading.set(true);
3554
- const users = await this.userService.getUsers();
3555
- this.users.set(users);
3556
- this.isLoading.set(false);
3557
- }
3558
- }
3559
-
3560
- // View binds to ViewModel
3561
- const UserList = observer(({ viewModel }: { viewModel: UserViewModel }) => (
3562
- <div>
3563
- {viewModel.isLoading.get() ? (
3564
- <Spinner />
3565
- ) : (
3566
- viewModel.users.get().map(user => <UserItem key={user.id} user={user} />)
3567
- )}
3568
- </div>
3569
- ));
3570
- ```
3571
-
3572
- ## Component Architecture (React/Vue)
3573
-
3574
- ```typescript
3575
- // Presentational component - no state, just props
3576
- const UserCard = ({ user, onDelete }: UserCardProps) => (
3577
- <div className="user-card">
3578
- <h3>{user.name}</h3>
3579
- <button onClick={() => onDelete(user.id)}>Delete</button>
3580
- </div>
3581
- );
3582
-
3583
- // Container component - manages state
3584
- const UserListContainer = () => {
3585
- const [users, setUsers] = useState<User[]>([]);
3586
-
3587
- useEffect(() => {
3588
- userService.getUsers().then(setUsers);
3589
- }, []);
3590
-
3591
- const handleDelete = (id: string) => {
3592
- userService.deleteUser(id).then(() => {
3593
- setUsers(users.filter(u => u.id !== id));
3594
- });
3595
- };
3596
-
3597
- return <UserList users={users} onDelete={handleDelete} />;
3598
- };
3599
- ```
3600
-
3601
- ## Best Practices
3602
-
3603
- - Separate UI logic from business logic
3604
- - Keep views as simple as possible
3605
- - Use unidirectional data flow when possible
3606
- - Make components reusable and testable
3607
- - Choose pattern based on framework and team familiarity
3608
-
3609
-
3610
- # Feature Toggles
3611
-
3612
- ## Toggle Types
3613
-
3614
- ### Release Toggles
3615
- Hide incomplete features in production.
3616
-
3617
- ```typescript
3618
- if (featureFlags.isEnabled('new-checkout')) {
3619
- return <NewCheckout />;
3620
- }
3621
- return <LegacyCheckout />;
3622
- ```
3623
-
3624
- ### Experiment Toggles
3625
- A/B testing and gradual rollouts.
3626
-
3627
- ```typescript
3628
- const variant = featureFlags.getVariant('pricing-experiment', userId);
3629
- if (variant === 'new-pricing') {
3630
- return calculateNewPricing(cart);
3631
- }
3632
- return calculateLegacyPricing(cart);
3633
- ```
3634
-
3635
- ### Ops Toggles
3636
- Runtime operational control.
3637
-
3638
- ```typescript
3639
- if (featureFlags.isEnabled('enable-caching')) {
3640
- return cache.get(key) || fetchFromDatabase(key);
3641
- }
3642
- return fetchFromDatabase(key);
3643
- ```
3644
-
3645
- ## Implementation
3646
-
3647
- ```typescript
3648
- interface FeatureFlags {
3649
- isEnabled(flag: string, context?: Context): boolean;
3650
- getVariant(flag: string, userId: string): string;
3651
- }
3652
-
3653
- class FeatureFlagService implements FeatureFlags {
3654
- constructor(private config: Map<string, FlagConfig>) {}
3655
-
3656
- isEnabled(flag: string, context?: Context): boolean {
3657
- const config = this.config.get(flag);
3658
- if (!config) return false;
3659
-
3660
- if (config.percentage) {
3661
- return this.isInPercentage(context?.userId, config.percentage);
3662
- }
3663
-
3664
- return config.enabled;
3665
- }
3666
-
3667
- private isInPercentage(userId: string | undefined, percentage: number): boolean {
3668
- if (!userId) return false;
3669
- const hash = this.hashUserId(userId);
3670
- return (hash % 100) < percentage;
3671
- }
3672
- }
3673
- ```
3674
-
3675
- ## Best Practices
3676
-
3677
- - Remove toggles after feature is stable
3678
- - Use clear naming conventions
3679
- - Log toggle decisions for debugging
3680
- - Test both toggle states
3681
- - Limit number of active toggles
3682
- - Document toggle purpose and expiration
3683
-
3684
-
3685
- ---
3686
-
3687
-
3688
- ## DevOps
3689
-
3690
- # CI/CD Practices
3691
-
3692
- ## Continuous Integration
3693
-
3694
- Run on every commit:
3695
-
3696
- ```yaml
3697
- # .github/workflows/ci.yml
3698
- name: CI
3699
- on: [push, pull_request]
3700
-
3701
- jobs:
3702
- build:
3703
- runs-on: ubuntu-latest
3704
- steps:
3705
- - uses: actions/checkout@v4
3706
- - uses: actions/setup-node@v4
3707
- with:
3708
- node-version: '20'
3709
- cache: 'npm'
3710
- - run: npm ci
3711
- - run: npm run lint
3712
- - run: npm run typecheck
3713
- - run: npm test
3714
- - run: npm run build
3715
- ```
3716
-
3717
- ## Continuous Deployment
3718
-
3719
- Deploy automatically after CI passes:
3720
-
3721
- ```yaml
3722
- deploy:
3723
- needs: build
3724
- if: github.ref == 'refs/heads/main'
3725
- runs-on: ubuntu-latest
3726
- steps:
3727
- - uses: actions/checkout@v4
3728
- - run: npm ci
3729
- - run: npm run build
3730
- - run: npm run deploy
3731
- env:
3732
- DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
3733
- ```
3734
-
3735
- ## Deployment Strategies
3736
-
3737
- ### Blue-Green Deployment
3738
- Run two identical environments, switch traffic instantly.
3739
-
3740
- ### Canary Releases
3741
- Route small percentage of traffic to new version first.
3742
-
3743
- ### Rolling Updates
3744
- Gradually replace instances with new version.
3745
-
3746
- ## Best Practices
3747
-
3748
- - Run fast tests first, slow tests later
3749
- - Cache dependencies between runs
3750
- - Use matrix builds for multiple versions/platforms
3751
- - Keep secrets in secure storage
3752
- - Automate database migrations
3753
- - Include rollback procedures
3754
- - Monitor deployments with health checks
3755
- - Use feature flags for safer releases
3756
-
3757
-
3758
- # Observability
3759
-
3760
- ## Overview
3761
-
3762
- Observability is the ability to understand the internal state of a system by examining its outputs. In modern distributed systems, it goes beyond simple monitoring to include logging, metrics, and tracing.
3763
-
3764
- ## Three Pillars
3765
-
3766
- ### 1. Structured Logging
3767
-
3768
- Logs should be machine-readable (JSON) and contain context.
3769
-
3770
- ```json
3771
- // ✅ Good: Structured JSON logging
3772
- {
3773
- "level": "info",
3774
- "message": "Order processed",
3775
- "orderId": "ord_123",
3776
- "userId": "user_456",
3777
- "amount": 99.99,
3778
- "durationMs": 145,
3779
- "status": "success"
3780
- }
3781
- ```
3782
- ```text
3783
- // ❌ Bad: Unstructured text
3784
- "Order processed: ord_123 for user_456"
3785
- ```
3786
-
3787
- ### 2. Metrics
3788
-
3789
- Aggregatable data points for identifying trends and health.
3790
-
3791
- - **Counters**: Total requests, error counts (`http_requests_total`)
3792
- - **Gauges**: Queue depth, memory usage (`memory_usage_bytes`)
3793
- - **Histograms**: Request latency distribution (`http_request_duration_seconds`)
3794
-
3795
- ### 3. Distributed Tracing
3796
-
3797
- Tracking requests as they propagate through services.
3798
-
3799
- - **Trace ID**: Unique ID for the entire request chain
3800
- - **Span ID**: Unique ID for a specific operation
3801
- - **Context Propagation**: Passing IDs between services (e.g., W3C Trace Context)
3802
-
3803
- ## Implementation Strategy
3804
-
3805
- ### OpenTelemetry (OTel)
3806
-
3807
- Use OpenTelemetry as the vendor-neutral standard for collecting telemetry data.
3808
-
3809
- ```text
3810
- # Initialize OpenTelemetry SDK
3811
- SDK.Configure({
3812
- TraceExporter: Console/OTLP,
3813
- Instrumentations: [Http, Database, Grpc]
3814
- })
3815
- SDK.Start()
3816
- ```
3817
-
3818
- ### Health Checks
3819
-
3820
- Expose standard health endpoints:
3821
-
3822
- - `/health/live`: Is the process running? (Liveness)
3823
- - `/health/ready`: Can it accept traffic? (Readiness)
3824
- - `/health/startup`: Has it finished initializing? (Startup)
3825
-
3826
- ## Alerting Best Practices
3827
-
3828
- - **Alert on Symptoms, not Causes**: "High Error Rate" (Symptom) vs "CPU High" (Cause).
3829
- - **Golden Signals**: Latency, Traffic, Errors, Saturation.
3830
- - **Actionable**: Every alert should require a specific human action.
3831
-
3832
-
3833
- ---
3834
-
3835
-
3836
- ## Best Practices
3837
-
3838
- # Planning Best Practices
3839
-
3840
- ## Plan Before Implementation
3841
-
3842
- **ALWAYS design and plan before writing code:**
3843
-
3844
- 1. **Understand Requirements**
3845
- - Clarify the goal and scope
3846
- - Identify constraints and dependencies
3847
- - Ask questions about ambiguous requirements
3848
-
3849
- 2. **Break Down Into Phases**
3850
- - Divide work into logical phases
3851
- - Define deliverables for each phase
3852
- - Prioritize phases by value and dependencies
3853
-
3854
- 3. **Design First**
3855
- - Sketch architecture and data flow
3856
- - Identify components and interfaces
3857
- - Consider edge cases and error scenarios
3858
-
3859
- 4. **Get User Approval**
3860
- - Present the plan to stakeholders
3861
- - Explain trade-offs and alternatives
3862
- - Wait for approval before implementation
3863
-
3864
- ## Never Make Assumptions
3865
-
3866
- **CRITICAL: When in doubt, ASK:**
3867
-
3868
- ```typescript
3869
- // ❌ BAD: Assuming what user wants
3870
- async function processOrder(orderId: string) {
3871
- // Assuming we should send email, but maybe not?
3872
- await sendConfirmationEmail(orderId);
3873
- // Assuming payment is already captured?
3874
- await fulfillOrder(orderId);
3875
- }
3876
-
3877
- // ✅ GOOD: Clarify requirements first
3878
- // Q: Should we send confirmation email at this stage?
3879
- // Q: Is payment already captured or should we capture it here?
3880
- // Q: What happens if fulfillment fails?
3881
- ```
3882
-
3883
- **Ask about:**
3884
- - Expected behavior in edge cases
3885
- - Error handling strategy
3886
- - Performance requirements
3887
- - Security considerations
3888
- - User experience preferences
3889
-
3890
- ## Plan in Phases
3891
-
3892
- **Structure work into clear phases:**
3893
-
3894
- ### Phase 1: Foundation
3895
- - Set up project structure
3896
- - Configure tooling and dependencies
3897
- - Create basic types and interfaces
3898
-
3899
- ### Phase 2: Core Implementation
3900
- - Implement main business logic
3901
- - Add error handling
3902
- - Write unit tests
3903
-
3904
- ### Phase 3: Integration
3905
- - Connect components
3906
- - Add integration tests
3907
- - Handle edge cases
3908
-
3909
- ### Phase 4: Polish
3910
- - Performance optimization
3911
- - Documentation
3912
- - Final review
3913
-
3914
- **Checkpoint after each phase:**
3915
- - Demo functionality
3916
- - Get feedback
3917
- - Adjust plan if needed
3918
-
3919
- ## Planning Template
3920
-
3921
- ```markdown
3922
- ## Goal
3923
- [What are we building and why?]
3924
-
3925
- ## Requirements
3926
- - [ ] Requirement 1
3927
- - [ ] Requirement 2
3928
- - [ ] Requirement 3
3929
-
3930
- ## Questions for Clarification
3931
- 1. [Question about requirement X]
3932
- 2. [Question about edge case Y]
3933
- 3. [Question about preferred approach for Z]
3934
-
3935
- ## Proposed Approach
3936
- [Describe the solution]
3937
-
3938
- ## Phases
3939
- 1. **Phase 1**: [Description]
3940
- - Task 1
3941
- - Task 2
3942
-
3943
- 2. **Phase 2**: [Description]
3944
- - Task 1
3945
- - Task 2
3946
-
3947
- ## Risks & Mitigation
3948
- - **Risk**: [Description]
3949
- **Mitigation**: [How to handle]
3950
-
3951
- ## Alternatives Considered
3952
- - **Option A**: [Pros/Cons]
3953
- - **Option B**: [Pros/Cons]
3954
- - **Chosen**: Option A because [reason]
3955
- ```
3956
-
3957
- ## Communication Principles
3958
-
3959
- 1. **Ask Early**: Don't wait until you're stuck
3960
- 2. **Be Specific**: "Should error X retry or fail immediately?"
3961
- 3. **Propose Options**: "Would you prefer A or B?"
3962
- 4. **Explain Trade-offs**: "Fast but risky vs. Slow but safe"
3963
- 5. **Document Decisions**: Record what was decided and why
3964
-
3965
- ## Anti-Patterns
3966
-
3967
- ❌ **Don't:**
3968
- - Start coding without understanding requirements
3969
- - Assume you know what the user wants
3970
- - Skip the planning phase to "save time"
3971
- - Make architectural decisions without discussion
3972
- - Proceed with unclear requirements
3973
-
3974
- ✅ **Do:**
3975
- - Ask questions when requirements are vague
3976
- - Create a plan and get it approved
3977
- - Break work into reviewable phases
3978
- - Document decisions and reasoning
3979
- - Communicate early and often
3980
-
3981
-
3982
- # Documentation Organization
3983
-
3984
- ## Keep Root Clean
3985
-
3986
- **RULE: Documentation must NOT clutter the project root.**
3987
-
3988
- ```
3989
- ❌ BAD: Root folder mess
3990
- project/
3991
- ├── README.md
3992
- ├── ARCHITECTURE.md
3993
- ├── API_DOCS.md
3994
- ├── DEPLOYMENT.md
3995
- ├── TROUBLESHOOTING.md
3996
- ├── USER_GUIDE.md
3997
- ├── DATABASE_SCHEMA.md
3998
- ├── TESTING_GUIDE.md
3999
- └── ... (20 more .md files)
4000
-
4001
- ✅ GOOD: Organized structure
4002
- project/
4003
- ├── README.md (overview only)
4004
- ├── docs/
4005
- │ ├── architecture/
4006
- │ ├── api/
4007
- │ ├── deployment/
4008
- │ └── guides/
4009
- └── src/
4010
- ```
4011
-
4012
- ## Documentation Structure
4013
-
4014
- **Standard documentation folder:**
4015
-
4016
- ```
4017
- docs/
4018
- ├── architecture/
4019
- │ ├── overview.md
4020
- │ ├── decisions/ # Architecture Decision Records
4021
- │ │ ├── 001-database-choice.md
4022
- │ │ └── 002-api-design.md
4023
- │ └── diagrams/
4024
-
4025
- ├── api/
4026
- │ ├── endpoints.md
4027
- │ ├── authentication.md
4028
- │ └── examples/
4029
-
4030
- ├── guides/
4031
- │ ├── getting-started.md
4032
- │ ├── development.md
4033
- │ ├── deployment.md
4034
- │ └── troubleshooting.md
4035
-
4036
- ├── features/ # Organize by feature
4037
- │ ├── user-auth/
4038
- │ │ ├── overview.md
4039
- │ │ ├── implementation.md
4040
- │ │ └── testing.md
4041
- │ ├── payments/
4042
- │ └── notifications/
4043
-
4044
- └── planning/ # Active work planning
4045
- ├── memory-lane.md # Context preservation
4046
- ├── current-phase.md # Active work
4047
- └── next-steps.md # Backlog
4048
- ```
4049
-
4050
- ## Memory Lane Document
4051
-
4052
- **CRITICAL: Maintain context across sessions**
4053
-
4054
- ### Purpose
4055
- When AI context limit is reached, reload from memory lane to restore working context.
4056
-
4057
- ### Structure
4058
-
4059
- ```markdown
4060
- # Memory Lane - Project Context
4061
-
4062
- ## Last Updated
4063
- 2024-12-10 15:30
4064
-
4065
- ## Current Objective
4066
- Implementing user authentication system with OAuth2 support
4067
-
4068
- ## Recent Progress
4069
- - ✅ Set up database schema (2024-12-08)
4070
- - ✅ Implemented user registration (2024-12-09)
4071
- - 🔄 Working on: OAuth2 integration (2024-12-10)
4072
- - ⏳ Next: Session management
4073
-
4074
- ## Key Decisions
4075
- 1. **Database**: PostgreSQL chosen for ACID compliance
4076
- 2. **Auth Strategy**: OAuth2 + JWT tokens
4077
- 3. **Session Store**: Redis for performance
4078
-
4079
- ## Important Files
4080
- - `src/auth/oauth.ts` - OAuth2 implementation (IN PROGRESS)
4081
- - `src/models/user.ts` - User model and validation
4082
- - `docs/architecture/decisions/003-auth-system.md` - Full context
4083
-
4084
- ## Active Questions
4085
- 1. Should we support refresh tokens? (Pending user decision)
4086
- 2. Token expiry: 1h or 24h? (Pending user decision)
4087
-
4088
- ## Technical Context
4089
- - Using Passport.js for OAuth
4090
- - Google and GitHub providers configured
4091
- - Callback URLs: /auth/google/callback, /auth/github/callback
4092
-
4093
- ## Known Issues
4094
- - OAuth redirect not working in development (investigating)
4095
- - Need to add rate limiting to prevent abuse
4096
-
4097
- ## Next Session
4098
- 1. Fix OAuth redirect issue
4099
- 2. Implement refresh token rotation
4100
- 3. Add comprehensive auth tests
4101
- ```
4102
-
4103
- ### Update Frequency
4104
- - Update after each significant milestone
4105
- - Update before context limit is reached
4106
- - Update when switching between features
4107
-
4108
- ## Context Reload Strategy
4109
-
4110
- **For AI Tools with Hooks:**
4111
-
4112
- Create a hook to reload memory lane on startup:
4113
-
4114
- ```json
4115
- {
4116
- "hooks": {
4117
- "startup": {
4118
- "command": "cat docs/planning/memory-lane.md"
4119
- }
4120
- }
4121
- }
4122
- ```
4123
-
4124
- **For AI Tools with Agents:**
4125
-
4126
- Create a context restoration agent:
4127
-
4128
- ```markdown
4129
- # Context Restoration Agent
4130
-
4131
- Task: Read and summarize current project state
4132
-
4133
- Sources:
4134
- 1. docs/planning/memory-lane.md
4135
- 2. docs/architecture/decisions/ (recent ADRs)
4136
- 3. git log --oneline -10 (recent commits)
4137
-
4138
- Output: Concise summary of where we are and what's next
4139
- ```
4140
-
4141
- ## Feature Documentation
4142
-
4143
- **Organize by feature/scope, not by type:**
4144
-
4145
- ```
4146
- ❌ BAD: Organized by document type
4147
- docs/
4148
- ├── specifications/
4149
- │ ├── auth.md
4150
- │ ├── payments.md
4151
- │ └── notifications.md
4152
- ├── implementations/
4153
- │ ├── auth.md
4154
- │ ├── payments.md
4155
- │ └── notifications.md
4156
- └── tests/
4157
- ├── auth.md
4158
- └── payments.md
4159
-
4160
- ✅ GOOD: Organized by feature
4161
- docs/features/
4162
- ├── auth/
4163
- │ ├── specification.md
4164
- │ ├── implementation.md
4165
- │ ├── api.md
4166
- │ └── testing.md
4167
- ├── payments/
4168
- │ ├── specification.md
4169
- │ ├── implementation.md
4170
- │ └── providers.md
4171
- └── notifications/
4172
- ├── specification.md
4173
- └── channels.md
4174
- ```
4175
-
4176
- **Benefits:**
4177
- - All related docs in one place
4178
- - Easy to find feature-specific information
4179
- - Natural scope boundaries
4180
- - Easier to maintain
4181
-
4182
- ## Planning Documents
4183
-
4184
- **Active planning should be in docs/planning/:**
4185
-
4186
- ```
4187
- docs/planning/
4188
- ├── memory-lane.md # Context preservation
4189
- ├── current-sprint.md # Active work
4190
- ├── backlog.md # Future work
4191
- └── spike-results/ # Research findings
4192
- ├── database-options.md
4193
- └── auth-libraries.md
4194
- ```
4195
-
4196
- ## Documentation Principles
4197
-
4198
- 1. **Separate folder**: All docs in `docs/` directory
4199
- 2. **Organize by scope**: Group by feature, not document type
4200
- 3. **Keep root clean**: Only README.md in project root
4201
- 4. **Maintain memory lane**: Update regularly for context preservation
4202
- 5. **Link related docs**: Use relative links between related documents
4203
-
4204
- ## README Guidelines
4205
-
4206
- **Root README should be concise:**
4207
-
4208
- ```markdown
4209
- # Project Name
4210
-
4211
- Brief description
4212
-
4213
- ## Quick Start
4214
- [Link to docs/guides/getting-started.md]
4215
-
4216
- ## Documentation
4217
- - [Architecture](docs/architecture/overview.md)
4218
- - [API Docs](docs/api/endpoints.md)
4219
- - [Development Guide](docs/guides/development.md)
4220
-
4221
- ## Contributing
4222
- [Link to CONTRIBUTING.md or docs/guides/contributing.md]
4223
- ```
4224
-
4225
- **Keep it short, link to detailed docs.**
4226
-
4227
- ## Anti-Patterns
4228
-
4229
- ❌ **Don't:**
4230
- - Put 10+ markdown files in project root
4231
- - Mix documentation types in same folder
4232
- - Forget to update memory lane before context expires
4233
- - Create documentation without clear organization
4234
- - Duplicate information across multiple docs
4235
-
4236
- ✅ **Do:**
4237
- - Use `docs/` directory for all documentation
4238
- - Organize by feature/scope
4239
- - Maintain memory lane for context preservation
4240
- - Link related documents together
4241
- - Update docs as code evolves
4242
-
4243
-
4244
- # Code Review Practices
4245
-
4246
- ## Review Checklist
4247
-
4248
- - [ ] Code follows project style guidelines
4249
- - [ ] No obvious bugs or logic errors
4250
- - [ ] Error handling is appropriate
4251
- - [ ] Tests cover new functionality
4252
- - [ ] No security vulnerabilities introduced
4253
- - [ ] Performance implications considered
4254
- - [ ] Documentation updated if needed
4255
-
4256
- ## Giving Feedback
4257
-
4258
- **Good:**
4259
- ```
4260
- Consider using `Array.find()` here instead of `filter()[0]` -
4261
- it's more readable and stops at the first match.
4262
- ```
4263
-
4264
- **Bad:**
4265
- ```
4266
- This is wrong.
4267
- ```
4268
-
4269
- ## PR Description Template
4270
-
4271
- ```markdown
4272
- ## Summary
4273
- Brief description of changes
4274
-
4275
- ## Changes
4276
- - Added X feature
4277
- - Fixed Y bug
4278
- - Refactored Z
4279
-
4280
- ## Testing
4281
- - [ ] Unit tests added
4282
- - [ ] Manual testing performed
4283
-
4284
- ## Screenshots (if UI changes)
4285
- ```
4286
-
4287
- ## Best Practices
4288
-
4289
- - Review promptly (within 24 hours)
4290
- - Focus on logic and design, not style (use linters)
4291
- - Ask questions rather than make demands
4292
- - Praise good solutions
4293
- - Keep PRs small and focused
4294
- - Use "nitpick:" prefix for minor suggestions
4295
- - Approve with minor comments when appropriate
4296
-
4297
-
4298
- # Refactoring Patterns
4299
-
4300
- ## Common Code Smells
4301
-
4302
- ### Long Method
4303
- Split into smaller, focused functions.
4304
-
4305
- ```typescript
4306
- // Before
4307
- function processOrder(order: Order) {
4308
- // 100 lines of code...
4309
- }
4310
-
4311
- // After
4312
- function processOrder(order: Order) {
4313
- validateOrder(order);
4314
- calculateTotals(order);
4315
- applyDiscounts(order);
4316
- saveOrder(order);
4317
- }
4318
- ```
4319
-
4320
- ### Duplicate Code
4321
- Extract common logic.
4322
-
4323
- ```typescript
4324
- // Before
4325
- function getAdminUsers() {
4326
- return users.filter(u => u.role === 'admin' && u.active);
4327
- }
4328
- function getModeratorUsers() {
4329
- return users.filter(u => u.role === 'moderator' && u.active);
4330
- }
4331
-
4332
- // After
4333
- function getActiveUsersByRole(role: string) {
4334
- return users.filter(u => u.role === role && u.active);
4335
- }
4336
- ```
4337
-
4338
- ### Primitive Obsession
4339
- Use value objects.
4340
-
4341
- ```typescript
4342
- // Before
4343
- function sendEmail(email: string) { /* ... */ }
4344
-
4345
- // After
4346
- class Email {
4347
- constructor(private value: string) {
4348
- if (!this.isValid(value)) throw new Error('Invalid email');
4349
- }
4350
- }
4351
- function sendEmail(email: Email) { /* ... */ }
4352
- ```
4353
-
4354
- ### Feature Envy
4355
- Move method to class it uses most.
4356
-
4357
- ```typescript
4358
- // Before - Order is accessing customer too much
4359
- class Order {
4360
- getDiscount() {
4361
- return this.customer.isPremium() ?
4362
- this.customer.premiumDiscount :
4363
- this.customer.regularDiscount;
4364
- }
4365
- }
4366
-
4367
- // After
4368
- class Customer {
4369
- getDiscount(): number {
4370
- return this.isPremium() ? this.premiumDiscount : this.regularDiscount;
4371
- }
4372
- }
4373
- ```
4374
-
4375
- ## Safe Refactoring Steps
4376
-
4377
- 1. Ensure tests pass before refactoring
4378
- 2. Make one small change at a time
4379
- 3. Run tests after each change
4380
- 4. Commit frequently
4381
- 5. Refactor in separate commits from feature work
4382
-
4383
- ## Best Practices
4384
-
4385
- - Refactor when adding features, not separately
4386
- - Keep refactoring commits separate
4387
- - Use IDE refactoring tools when available
4388
- - Write tests before refactoring if missing
4389
-
4390
-
4391
- # Version Control Patterns
4392
-
4393
- ## Branching Strategies
4394
-
4395
- ### GitHub Flow
4396
- Simple: main + feature branches.
4397
-
4398
- ```
4399
- main ─────●─────●─────●─────●─────
4400
- \ /
4401
- feature ●───●───●
4402
- ```
4403
-
4404
- ### Git Flow
4405
- For scheduled releases: main, develop, feature, release, hotfix.
4406
-
4407
- ```
4408
- main ─────●─────────────●─────
4409
- \ /
4410
- release ●─────────●
4411
- \ /
4412
- develop ●───●───●───●───●───●───
4413
- \ /
4414
- feature ●───●
4415
- ```
4416
-
4417
- ## Commit Messages
4418
-
4419
- ```
4420
- feat: add user authentication
4421
-
4422
- - Implement JWT-based auth
4423
- - Add login/logout endpoints
4424
- - Include password hashing
4425
-
4426
- Closes #123
4427
- ```
4428
-
4429
- **Prefixes:**
4430
- - `feat:` - New feature
4431
- - `fix:` - Bug fix
4432
- - `refactor:` - Code change that doesn't fix bug or add feature
4433
- - `docs:` - Documentation only
4434
- - `test:` - Adding tests
4435
- - `chore:` - Maintenance tasks
4436
-
4437
- ## Best Practices
4438
-
4439
- - Keep commits atomic and focused
4440
- - Write descriptive commit messages
4441
- - Pull/rebase before pushing
4442
- - Never force push to shared branches
4443
- - Use pull requests for code review
4444
- - Delete merged branches
4445
- - Tag releases with semantic versions
4446
-
4447
-
4448
- ---
4449
-
4450
-
4451
- ## Design Patterns
4452
-
4453
- # Base Patterns
4454
-
4455
- ## Value Object
4456
-
4457
- Immutable object defined by its value, not identity.
4458
-
4459
- ```typescript
4460
- class Email {
4461
- private readonly value: string;
4462
-
4463
- constructor(email: string) {
4464
- if (!this.isValid(email)) throw new Error('Invalid email');
4465
- this.value = email.toLowerCase();
4466
- }
4467
-
4468
- equals(other: Email): boolean {
4469
- return this.value === other.value;
4470
- }
4471
- }
4472
-
4473
- class Money {
4474
- constructor(
4475
- public readonly amount: number,
4476
- public readonly currency: Currency
4477
- ) {
4478
- Object.freeze(this);
4479
- }
4480
-
4481
- add(other: Money): Money {
4482
- this.assertSameCurrency(other);
4483
- return new Money(this.amount + other.amount, this.currency);
4484
- }
4485
- }
4486
- ```
4487
-
4488
- ## Special Case (Null Object)
4489
-
4490
- Replace null checks with polymorphism.
4491
-
4492
- ```typescript
4493
- abstract class Customer {
4494
- abstract getDiscount(): number;
4495
- }
4496
-
4497
- class RealCustomer extends Customer {
4498
- getDiscount(): number { return 0.1; }
4499
- }
4500
-
4501
- class GuestCustomer extends Customer {
4502
- getDiscount(): number { return 0; } // No discount
4503
- }
4504
-
4505
- // No null checks needed
4506
- const customer = repo.findById(id) || new GuestCustomer();
4507
- const discount = customer.getDiscount();
4508
- ```
4509
-
4510
- ## Registry
4511
-
4512
- Global access point for services.
4513
-
4514
- ```typescript
4515
- class ServiceRegistry {
4516
- private static services = new Map<string, any>();
4517
-
4518
- static register<T>(key: string, service: T): void {
4519
- this.services.set(key, service);
4520
- }
4521
-
4522
- static get<T>(key: string): T {
4523
- return this.services.get(key);
4524
- }
4525
- }
4526
-
4527
- // Prefer dependency injection over registry
4528
- ```
4529
-
4530
- ## Plugin
4531
-
4532
- Extend behavior without modifying core code.
4533
-
4534
- ```typescript
4535
- interface ValidationPlugin {
4536
- validate(user: User): ValidationResult;
4537
- }
4538
-
4539
- class UserValidator {
4540
- private plugins: ValidationPlugin[] = [];
4541
-
4542
- registerPlugin(plugin: ValidationPlugin): void {
4543
- this.plugins.push(plugin);
4544
- }
4545
-
4546
- validate(user: User): ValidationResult[] {
4547
- return this.plugins.map(p => p.validate(user));
4548
- }
4549
- }
4550
- ```
4551
-
4552
- ## Best Practices
4553
-
4554
- - Use Value Objects to avoid primitive obsession
4555
- - Make Value Objects immutable
4556
- - Use Special Case instead of null checks
4557
- - Prefer dependency injection over Registry
4558
-
4559
-
4560
- # Data Access Patterns
4561
-
4562
- ## Repository
4563
-
4564
- Collection-like interface for domain objects.
4565
-
4566
- ```typescript
4567
- interface UserRepository {
4568
- findById(id: string): Promise<User | null>;
4569
- findByEmail(email: string): Promise<User | null>;
4570
- save(user: User): Promise<void>;
4571
- delete(user: User): Promise<void>;
4572
- }
4573
-
4574
- class PostgreSQLUserRepository implements UserRepository {
4575
- async findById(id: string): Promise<User | null> {
4576
- const row = await this.db.queryOne('SELECT * FROM users WHERE id = $1', [id]);
4577
- return row ? this.mapToUser(row) : null;
4578
- }
4579
- }
4580
- ```
4581
-
4582
- ## Data Mapper
4583
-
4584
- Complete separation between domain and persistence.
4585
-
4586
- ```typescript
4587
- class UserMapper {
4588
- toDomain(row: DbRow): User {
4589
- return new User(row.id, row.name, new Email(row.email));
4590
- }
4591
-
4592
- toDatabase(user: User): DbRow {
4593
- return { id: user.id, name: user.name, email: user.email.toString() };
4594
- }
4595
- }
4596
- ```
4597
-
4598
- ## Unit of Work
4599
-
4600
- Track changes and commit together.
4601
-
4602
- ```typescript
4603
- class UnitOfWork {
4604
- private newObjects = new Set<any>();
4605
- private dirtyObjects = new Set<any>();
4606
-
4607
- registerNew(obj: any): void { this.newObjects.add(obj); }
4608
- registerDirty(obj: any): void { this.dirtyObjects.add(obj); }
4609
-
4610
- async commit(): Promise<void> {
4611
- await this.db.beginTransaction();
4612
- try {
4613
- for (const obj of this.newObjects) await this.insert(obj);
4614
- for (const obj of this.dirtyObjects) await this.update(obj);
4615
- await this.db.commit();
4616
- } catch (e) {
4617
- await this.db.rollback();
4618
- throw e;
4619
- }
4620
- }
4621
- }
4622
- ```
4623
-
4624
- ## Identity Map
4625
-
4626
- Ensure each object loaded only once per session.
4627
-
4628
- ```typescript
4629
- class IdentityMap {
4630
- private map = new Map<string, any>();
4631
-
4632
- get(id: string): any | null { return this.map.get(id) || null; }
4633
- put(id: string, obj: any): void { this.map.set(id, obj); }
4634
- }
4635
- ```
4636
-
4637
- ## Best Practices
4638
-
4639
- - Return domain objects from repositories
4640
- - Use one repository per aggregate root
4641
- - Keep repositories focused on persistence
4642
- - Don't leak database details into domain
4643
-
4644
-
4645
- # Domain Logic Patterns
4646
-
4647
- ## Transaction Script
4648
-
4649
- Procedural approach - one procedure per operation.
4650
-
4651
- ```typescript
4652
- async function transferMoney(fromId: string, toId: string, amount: number) {
4653
- const db = await Database.connect();
4654
- await db.beginTransaction();
4655
-
4656
- try {
4657
- const from = await db.query('SELECT * FROM accounts WHERE id = $1', [fromId]);
4658
- if (from.balance < amount) throw new Error('Insufficient funds');
4659
-
4660
- await db.execute('UPDATE accounts SET balance = balance - $1 WHERE id = $2', [amount, fromId]);
4661
- await db.execute('UPDATE accounts SET balance = balance + $1 WHERE id = $2', [amount, toId]);
4662
- await db.commit();
4663
- } catch (e) {
4664
- await db.rollback();
4665
- throw e;
4666
- }
4667
- }
4668
- ```
4669
-
4670
- **Use for:** Simple apps, CRUD, reports.
4671
-
4672
- ## Domain Model
4673
-
4674
- Rich objects with behavior.
4675
-
4676
- ```typescript
4677
- class Account {
4678
- constructor(private balance: Money, private overdraftLimit: Money) {}
4679
-
4680
- withdraw(amount: Money): void {
4681
- if (!this.canWithdraw(amount)) throw new InsufficientFundsError();
4682
- this.balance = this.balance.subtract(amount);
4683
- }
4684
-
4685
- transfer(amount: Money, recipient: Account): void {
4686
- this.withdraw(amount);
4687
- recipient.deposit(amount);
4688
- }
4689
- }
4690
- ```
4691
-
4692
- **Use for:** Complex business rules, rich domains.
4693
-
4694
- ## Service Layer
4695
-
4696
- Application boundary coordinating domain objects.
4697
-
4698
- ```typescript
4699
- class AccountService {
4700
- constructor(
4701
- private accountRepo: AccountRepository,
4702
- private unitOfWork: UnitOfWork
4703
- ) {}
4704
-
4705
- async transfer(fromId: string, toId: string, amount: Money): Promise<void> {
4706
- const from = await this.accountRepo.findById(fromId);
4707
- const to = await this.accountRepo.findById(toId);
4708
-
4709
- from.transfer(amount, to); // Domain logic
4710
-
4711
- await this.accountRepo.save(from);
4712
- await this.accountRepo.save(to);
4713
- await this.unitOfWork.commit();
4714
- }
4715
- }
4716
- ```
4717
-
4718
- **Use for:** API boundaries, multiple clients, transaction coordination.
4719
-
4720
- ## Best Practices
4721
-
4722
- - Choose pattern based on complexity
4723
- - Service layer orchestrates, domain model contains logic
4724
- - Keep services thin, domain objects rich
4725
- - Combine Domain Model + Service Layer for complex apps
4726
-
4727
-
4728
- # Gang of Four Patterns
4729
-
4730
- ## Creational
4731
-
4732
- ### Factory Method
4733
- ```typescript
4734
- interface Logger { log(msg: string): void; }
4735
-
4736
- abstract class Application {
4737
- abstract createLogger(): Logger;
4738
- run(): void { this.createLogger().log('Started'); }
4739
- }
4740
-
4741
- class DevApp extends Application {
4742
- createLogger(): Logger { return new ConsoleLogger(); }
4743
- }
4744
- ```
4745
-
4746
- ### Builder
4747
- ```typescript
4748
- const query = new QueryBuilder()
4749
- .from('users')
4750
- .select('id', 'name')
4751
- .where('active = true')
4752
- .limit(10)
4753
- .build();
4754
- ```
4755
-
4756
- ## Structural
4757
-
4758
- ### Adapter
4759
- ```typescript
4760
- class PaymentAdapter implements PaymentProcessor {
4761
- constructor(private legacy: OldPaymentSystem) {}
4762
-
4763
- async process(amount: number): Promise<boolean> {
4764
- return this.legacy.makePayment(amount);
4765
- }
4766
- }
4767
- ```
4768
-
4769
- ### Decorator
4770
- ```typescript
4771
- interface Coffee { cost(): number; }
4772
-
4773
- class MilkDecorator implements Coffee {
4774
- constructor(private coffee: Coffee) {}
4775
- cost(): number { return this.coffee.cost() + 2; }
4776
- }
4777
-
4778
- let coffee: Coffee = new SimpleCoffee();
4779
- coffee = new MilkDecorator(coffee);
4780
- ```
4781
-
4782
- ### Facade
4783
- ```typescript
4784
- class ComputerFacade {
4785
- start(): void {
4786
- this.cpu.freeze();
4787
- this.memory.load(0, this.hdd.read(0, 1024));
4788
- this.cpu.execute();
4789
- }
4790
- }
4791
- ```
4792
-
4793
- ## Behavioral
4794
-
4795
- ### Strategy
4796
- ```typescript
4797
- interface SortStrategy { sort(data: number[]): number[]; }
4798
-
4799
- class Sorter {
4800
- constructor(private strategy: SortStrategy) {}
4801
- sort(data: number[]): number[] { return this.strategy.sort(data); }
4802
- }
4803
- ```
4804
-
4805
- ### Observer
4806
- ```typescript
4807
- class Stock {
4808
- private observers: Observer[] = [];
4809
-
4810
- attach(o: Observer): void { this.observers.push(o); }
4811
- notify(): void { this.observers.forEach(o => o.update(this)); }
4812
-
4813
- setPrice(price: number): void {
4814
- this.price = price;
4815
- this.notify();
4816
- }
4817
- }
4818
- ```
4819
-
4820
- ### Command
4821
- ```typescript
4822
- interface Command { execute(): void; undo(): void; }
4823
-
4824
- class AppendCommand implements Command {
4825
- constructor(private editor: Editor, private text: string) {}
4826
- execute(): void { this.editor.append(this.text); }
4827
- undo(): void { this.editor.delete(this.text.length); }
4828
- }
4829
- ```
4830
-
4831
- ## Best Practices
4832
-
4833
- - Use patterns to solve specific problems, not everywhere
4834
- - Combine patterns when appropriate
4835
- - Favor composition over inheritance
4836
- - Keep implementations simple
4837
-
4838
-
4839
- ---
4840
-
4841
-
4842
- ---
4843
- *Generated by aicgen*