@aicgen/aicgen 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/architecture-reviewer.md +88 -0
- package/.claude/agents/guideline-checker.md +73 -0
- package/.claude/agents/security-auditor.md +108 -0
- package/.claude/guidelines/api-design.md +645 -0
- package/.claude/guidelines/architecture.md +2503 -0
- package/.claude/guidelines/best-practices.md +618 -0
- package/.claude/guidelines/code-style.md +304 -0
- package/.claude/guidelines/design-patterns.md +573 -0
- package/.claude/guidelines/devops.md +226 -0
- package/.claude/guidelines/error-handling.md +413 -0
- package/.claude/guidelines/language.md +782 -0
- package/.claude/guidelines/performance.md +706 -0
- package/.claude/guidelines/security.md +583 -0
- package/.claude/guidelines/testing.md +568 -0
- package/.claude/settings.json +98 -0
- package/.claude/settings.local.json +8 -0
- package/.env.example +23 -0
- package/.eslintrc.json +28 -0
- package/.github/workflows/release.yml +180 -0
- package/.github/workflows/test.yml +81 -0
- package/.gitmodules +3 -0
- package/.vs/ProjectSettings.json +3 -0
- package/.vs/VSWorkspaceState.json +16 -0
- package/.vs/aicgen.slnx/FileContentIndex/5f0ce2a3-fd68-4863-9e23-e428cf1794e3.vsidx +0 -0
- package/.vs/aicgen.slnx/v18/.wsuo +0 -0
- package/.vs/aicgen.slnx/v18/DocumentLayout.json +54 -0
- package/.vs/slnx.sqlite +0 -0
- package/AGENTS.md +121 -0
- package/CLAUDE.md +36 -0
- package/CONTRIBUTING.md +821 -0
- package/LICENSE +21 -0
- package/README.md +199 -0
- package/assets/icon.svg +34 -0
- package/assets/logo.svg +41 -0
- package/bun.lock +848 -0
- package/data/LICENSE +21 -0
- package/data/README.md +203 -0
- package/data/api/basics.md +292 -0
- package/data/api/index.md +8 -0
- package/data/api/pagination.md +142 -0
- package/data/api/rest.md +137 -0
- package/data/api/versioning.md +60 -0
- package/data/architecture/clean-architecture/index.md +7 -0
- package/data/architecture/clean-architecture/layers.md +111 -0
- package/data/architecture/ddd/index.md +8 -0
- package/data/architecture/ddd/strategic.md +89 -0
- package/data/architecture/ddd/tactical.md +132 -0
- package/data/architecture/event-driven/index.md +7 -0
- package/data/architecture/event-driven/messaging.md +242 -0
- package/data/architecture/event-driven/patterns.md +129 -0
- package/data/architecture/feature-toggles/index.md +7 -0
- package/data/architecture/feature-toggles/patterns.md +73 -0
- package/data/architecture/gui/index.md +7 -0
- package/data/architecture/gui/patterns.md +132 -0
- package/data/architecture/hexagonal/ports-adapters.md +132 -0
- package/data/architecture/index.md +12 -0
- package/data/architecture/layered/index.md +7 -0
- package/data/architecture/layered/layers.md +100 -0
- package/data/architecture/microservices/api-gateway.md +56 -0
- package/data/architecture/microservices/boundaries.md +80 -0
- package/data/architecture/microservices/communication.md +97 -0
- package/data/architecture/microservices/data.md +92 -0
- package/data/architecture/microservices/index.md +11 -0
- package/data/architecture/microservices/resilience.md +111 -0
- package/data/architecture/modular-monolith/boundaries.md +133 -0
- package/data/architecture/modular-monolith/structure.md +131 -0
- package/data/architecture/serverless/best-practices.md +322 -0
- package/data/architecture/serverless/index.md +7 -0
- package/data/architecture/serverless/patterns.md +80 -0
- package/data/architecture/solid/index.md +7 -0
- package/data/architecture/solid/principles.md +187 -0
- package/data/database/basics.md +365 -0
- package/data/database/design-patterns.md +68 -0
- package/data/database/index.md +8 -0
- package/data/database/indexing.md +136 -0
- package/data/database/nosql.md +223 -0
- package/data/database/schema.md +137 -0
- package/data/devops/ci-cd.md +66 -0
- package/data/devops/index.md +8 -0
- package/data/devops/observability.md +73 -0
- package/data/devops/practices.md +77 -0
- package/data/error-handling/basics.md +222 -0
- package/data/error-handling/index.md +7 -0
- package/data/error-handling/strategy.md +185 -0
- package/data/guideline-mappings.yml +1077 -0
- package/data/index.md +3 -0
- package/data/language/csharp/basics.md +210 -0
- package/data/language/csharp/testing.md +252 -0
- package/data/language/go/basics.md +158 -0
- package/data/language/go/testing.md +192 -0
- package/data/language/index.md +14 -0
- package/data/language/java/basics.md +184 -0
- package/data/language/java/testing.md +273 -0
- package/data/language/javascript/basics.md +217 -0
- package/data/language/javascript/testing.md +269 -0
- package/data/language/python/async.md +100 -0
- package/data/language/python/basics.md +100 -0
- package/data/language/python/index.md +10 -0
- package/data/language/python/testing.md +125 -0
- package/data/language/python/types.md +99 -0
- package/data/language/ruby/basics.md +227 -0
- package/data/language/ruby/testing.md +267 -0
- package/data/language/rust/basics.md +175 -0
- package/data/language/rust/testing.md +219 -0
- package/data/language/typescript/async.md +103 -0
- package/data/language/typescript/basics.md +87 -0
- package/data/language/typescript/config.md +95 -0
- package/data/language/typescript/error-handling.md +98 -0
- package/data/language/typescript/generics.md +85 -0
- package/data/language/typescript/index.md +14 -0
- package/data/language/typescript/interfaces-types.md +83 -0
- package/data/language/typescript/performance.md +103 -0
- package/data/language/typescript/testing.md +98 -0
- package/data/patterns/base-patterns.md +105 -0
- package/data/patterns/concurrency.md +87 -0
- package/data/patterns/data-access.md +83 -0
- package/data/patterns/distribution.md +86 -0
- package/data/patterns/domain-logic.md +81 -0
- package/data/patterns/gof.md +109 -0
- package/data/patterns/index.md +12 -0
- package/data/performance/async.md +148 -0
- package/data/performance/basics.md +324 -0
- package/data/performance/caching-strategies.md +68 -0
- package/data/performance/caching.md +152 -0
- package/data/performance/index.md +8 -0
- package/data/practices/code-review.md +52 -0
- package/data/practices/documentation.md +260 -0
- package/data/practices/index.md +11 -0
- package/data/practices/planning.md +142 -0
- package/data/practices/refactoring.md +91 -0
- package/data/practices/version-control.md +55 -0
- package/data/security/auth-jwt.md +159 -0
- package/data/security/headers.md +143 -0
- package/data/security/index.md +10 -0
- package/data/security/injection.md +119 -0
- package/data/security/secrets.md +148 -0
- package/data/style/index.md +8 -0
- package/data/style/naming.md +136 -0
- package/data/style/organization.md +162 -0
- package/data/templates/agents/architecture-reviewer.md +88 -0
- package/data/templates/agents/guideline-checker.md +73 -0
- package/data/templates/agents/security-auditor.md +108 -0
- package/data/templates/antigravity/rules/architecture.md.hbs +5 -0
- package/data/templates/antigravity/rules/code-style.md.hbs +5 -0
- package/data/templates/antigravity/rules/language.md.hbs +5 -0
- package/data/templates/antigravity/rules/performance.md.hbs +5 -0
- package/data/templates/antigravity/rules/security.md.hbs +5 -0
- package/data/templates/antigravity/rules/testing.md.hbs +5 -0
- package/data/templates/antigravity/workflows/add-documentation.md.hbs +23 -0
- package/data/templates/antigravity/workflows/generate-integration-tests.md.hbs +17 -0
- package/data/templates/antigravity/workflows/generate-unit-tests.md.hbs +20 -0
- package/data/templates/antigravity/workflows/performance-audit.md.hbs +24 -0
- package/data/templates/antigravity/workflows/refactor-extract-module.md.hbs +17 -0
- package/data/templates/antigravity/workflows/security-audit.md.hbs +20 -0
- package/data/templates/hooks/formatting.json +26 -0
- package/data/templates/hooks/security.json +35 -0
- package/data/templates/hooks/testing.json +17 -0
- package/data/testing/basics.md +151 -0
- package/data/testing/index.md +9 -0
- package/data/testing/integration.md +159 -0
- package/data/testing/unit-fundamentals.md +128 -0
- package/data/testing/unit-mocking.md +116 -0
- package/data/version.json +49 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +46 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/config/profiles.d.ts +4 -0
- package/dist/config/profiles.d.ts.map +1 -0
- package/dist/config/profiles.js +30 -0
- package/dist/config/profiles.js.map +1 -0
- package/dist/config/settings.d.ts +7 -0
- package/dist/config/settings.d.ts.map +1 -0
- package/dist/config/settings.js +7 -0
- package/dist/config/settings.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +58489 -0
- package/dist/index.js.map +1 -0
- package/dist/models/guideline.d.ts +15 -0
- package/dist/models/guideline.d.ts.map +1 -0
- package/dist/models/guideline.js +2 -0
- package/dist/models/guideline.js.map +1 -0
- package/dist/models/preference.d.ts +9 -0
- package/dist/models/preference.d.ts.map +1 -0
- package/dist/models/preference.js +2 -0
- package/dist/models/preference.js.map +1 -0
- package/dist/models/profile.d.ts +9 -0
- package/dist/models/profile.d.ts.map +1 -0
- package/dist/models/profile.js +2 -0
- package/dist/models/profile.js.map +1 -0
- package/dist/models/project.d.ts +13 -0
- package/dist/models/project.d.ts.map +1 -0
- package/dist/models/project.js +2 -0
- package/dist/models/project.js.map +1 -0
- package/dist/services/ai/anthropic.d.ts +7 -0
- package/dist/services/ai/anthropic.d.ts.map +1 -0
- package/dist/services/ai/anthropic.js +39 -0
- package/dist/services/ai/anthropic.js.map +1 -0
- package/dist/services/generator.d.ts +2 -0
- package/dist/services/generator.d.ts.map +1 -0
- package/dist/services/generator.js +4 -0
- package/dist/services/generator.js.map +1 -0
- package/dist/services/learner.d.ts +2 -0
- package/dist/services/learner.d.ts.map +1 -0
- package/dist/services/learner.js +4 -0
- package/dist/services/learner.js.map +1 -0
- package/dist/services/scanner.d.ts +3 -0
- package/dist/services/scanner.d.ts.map +1 -0
- package/dist/services/scanner.js +54 -0
- package/dist/services/scanner.js.map +1 -0
- package/dist/utils/errors.d.ts +15 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +27 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/file.d.ts +7 -0
- package/dist/utils/file.d.ts.map +1 -0
- package/dist/utils/file.js +32 -0
- package/dist/utils/file.js.map +1 -0
- package/dist/utils/logger.d.ts +6 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +17 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/path.d.ts +6 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +14 -0
- package/dist/utils/path.js.map +1 -0
- package/docs/planning/memory-lane.md +83 -0
- package/package.json +64 -0
- package/packaging/linux/aicgen.spec +23 -0
- package/packaging/linux/control +9 -0
- package/packaging/macos/scripts/postinstall +12 -0
- package/packaging/windows/setup.nsi +92 -0
- package/planning/BRANDING-SUMMARY.md +194 -0
- package/planning/BRANDING.md +174 -0
- package/planning/BUILD.md +186 -0
- package/planning/CHUNK-IMPLEMENTATION-PLAN.md +87 -0
- package/planning/CHUNK-TAXONOMY.md +375 -0
- package/planning/CHUNKS-COMPLETE.md +382 -0
- package/planning/DESIGN.md +313 -0
- package/planning/DYNAMIC-GUIDELINES-DESIGN.md +265 -0
- package/planning/ENTERPRISE-UX-COMPLETE.md +281 -0
- package/planning/IMPLEMENTATION-PLAN.md +20 -0
- package/planning/PHASE1-COMPLETE.md +211 -0
- package/planning/PHASE2-COMPLETE.md +350 -0
- package/planning/PHASE3-COMPLETE.md +399 -0
- package/planning/PHASE4-COMPLETE.md +361 -0
- package/planning/PHASE4.5-CHUNKS.md +462 -0
- package/planning/STRUCTURE.md +170 -0
- package/scripts/add-categories.ts +87 -0
- package/scripts/build-binary.ts +46 -0
- package/scripts/embed-data.ts +105 -0
- package/scripts/generate-version.ts +150 -0
- package/scripts/test-decompress.ts +27 -0
- package/scripts/test-extract.ts +31 -0
- package/src/__tests__/services/assistant-file-writer.test.ts +400 -0
- package/src/__tests__/services/guideline-loader.test.ts +281 -0
- package/src/__tests__/services/tarball-extraction.test.ts +125 -0
- package/src/commands/add-guideline.ts +296 -0
- package/src/commands/clear.ts +61 -0
- package/src/commands/guideline-selector.ts +123 -0
- package/src/commands/init.ts +645 -0
- package/src/commands/quick-add.ts +586 -0
- package/src/commands/remove-guideline.ts +152 -0
- package/src/commands/stats.ts +49 -0
- package/src/commands/update.ts +240 -0
- package/src/config.ts +82 -0
- package/src/embedded-data.ts +1492 -0
- package/src/index.ts +67 -0
- package/src/models/profile.ts +24 -0
- package/src/models/project.ts +43 -0
- package/src/services/assistant-file-writer.ts +612 -0
- package/src/services/config-generator.ts +150 -0
- package/src/services/config-manager.ts +70 -0
- package/src/services/data-source.ts +248 -0
- package/src/services/first-run-init.ts +148 -0
- package/src/services/guideline-loader.ts +311 -0
- package/src/services/hook-generator.ts +178 -0
- package/src/services/subagent-generator.ts +310 -0
- package/src/utils/banner.ts +66 -0
- package/src/utils/errors.ts +27 -0
- package/src/utils/file.ts +67 -0
- package/src/utils/formatting.ts +172 -0
- package/src/utils/logger.ts +89 -0
- package/src/utils/path.ts +17 -0
- package/src/utils/wizard-state.ts +132 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# TypeScript Error Handling
|
|
2
|
+
|
|
3
|
+
## Custom Error Classes
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
// ✅ Create structured error hierarchy
|
|
7
|
+
class AppError extends Error {
|
|
8
|
+
constructor(
|
|
9
|
+
message: string,
|
|
10
|
+
public statusCode: number = 500,
|
|
11
|
+
public code: string = 'INTERNAL_ERROR',
|
|
12
|
+
public details?: unknown
|
|
13
|
+
) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = this.constructor.name;
|
|
16
|
+
Error.captureStackTrace(this, this.constructor);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class NotFoundError extends AppError {
|
|
21
|
+
constructor(resource: string, id: string) {
|
|
22
|
+
super(`${resource} with id ${id} not found`, 404, 'NOT_FOUND', { resource, id });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class ValidationError extends AppError {
|
|
27
|
+
constructor(message: string, details: unknown) {
|
|
28
|
+
super(message, 400, 'VALIDATION_ERROR', details);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Async Error Handling
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// ✅ Always handle promise rejections
|
|
37
|
+
async function fetchUser(id: string): Promise<User> {
|
|
38
|
+
try {
|
|
39
|
+
const response = await api.get(`/users/${id}`);
|
|
40
|
+
return response.data;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error instanceof ApiError && error.status === 404) {
|
|
43
|
+
throw new NotFoundError('User', id);
|
|
44
|
+
}
|
|
45
|
+
throw new AppError('Failed to fetch user', 500, 'FETCH_ERROR', { userId: id });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ✅ Use wrapper for Express async handlers
|
|
50
|
+
const asyncHandler = (fn: RequestHandler) => {
|
|
51
|
+
return (req: Request, res: Response, next: NextFunction) => {
|
|
52
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Result Type Pattern
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// ✅ Explicit success/failure without exceptions
|
|
61
|
+
type Result<T, E = Error> =
|
|
62
|
+
| { success: true; value: T }
|
|
63
|
+
| { success: false; error: E };
|
|
64
|
+
|
|
65
|
+
function parseJSON<T>(json: string): Result<T, string> {
|
|
66
|
+
try {
|
|
67
|
+
return { success: true, value: JSON.parse(json) };
|
|
68
|
+
} catch {
|
|
69
|
+
return { success: false, error: 'Invalid JSON' };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Usage
|
|
74
|
+
const result = parseJSON<User>(data);
|
|
75
|
+
if (result.success) {
|
|
76
|
+
console.log(result.value.name);
|
|
77
|
+
} else {
|
|
78
|
+
console.error(result.error);
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Centralized Error Handler
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// ✅ Express error middleware
|
|
86
|
+
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
|
|
87
|
+
if (err instanceof AppError) {
|
|
88
|
+
return res.status(err.statusCode).json({
|
|
89
|
+
error: { message: err.message, code: err.code, details: err.details }
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.error('Unexpected error:', err);
|
|
94
|
+
res.status(500).json({
|
|
95
|
+
error: { message: 'Internal server error', code: 'INTERNAL_ERROR' }
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
```
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# TypeScript Generics
|
|
2
|
+
|
|
3
|
+
## Basic Generic Functions
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
// ✅ Generic function for type-safe operations
|
|
7
|
+
function first<T>(array: T[]): T | undefined {
|
|
8
|
+
return array[0];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const numbers = [1, 2, 3];
|
|
12
|
+
const firstNumber = first(numbers); // Type: number | undefined
|
|
13
|
+
|
|
14
|
+
const users = [{ name: 'John' }];
|
|
15
|
+
const firstUser = first(users); // Type: { name: string } | undefined
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Generic Interfaces
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// ✅ Generic repository pattern
|
|
22
|
+
interface Repository<T> {
|
|
23
|
+
findById(id: string): Promise<T | null>;
|
|
24
|
+
findAll(): Promise<T[]>;
|
|
25
|
+
create(entity: Omit<T, 'id'>): Promise<T>;
|
|
26
|
+
update(id: string, data: Partial<T>): Promise<T>;
|
|
27
|
+
delete(id: string): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class UserRepository implements Repository<User> {
|
|
31
|
+
async findById(id: string): Promise<User | null> {
|
|
32
|
+
return this.db.users.findUnique({ where: { id } });
|
|
33
|
+
}
|
|
34
|
+
// ... other methods
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Generic Constraints
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// ✅ Constrain generic types
|
|
42
|
+
interface HasId {
|
|
43
|
+
id: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getById<T extends HasId>(items: T[], id: string): T | undefined {
|
|
47
|
+
return items.find(item => item.id === id);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Works with any type that has an id
|
|
51
|
+
getById(users, '123');
|
|
52
|
+
getById(products, '456');
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Mapped Types
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// ✅ Create transformed types
|
|
59
|
+
type Nullable<T> = {
|
|
60
|
+
[K in keyof T]: T[K] | null;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
type NullableUser = Nullable<User>;
|
|
64
|
+
// { id: string | null; name: string | null; ... }
|
|
65
|
+
|
|
66
|
+
// ✅ Conditional types
|
|
67
|
+
type ExtractArrayType<T> = T extends Array<infer U> ? U : never;
|
|
68
|
+
|
|
69
|
+
type StringArrayElement = ExtractArrayType<string[]>; // string
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Default Generic Parameters
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// ✅ Provide defaults for flexibility
|
|
76
|
+
interface ApiResponse<T = unknown, E = Error> {
|
|
77
|
+
data?: T;
|
|
78
|
+
error?: E;
|
|
79
|
+
status: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Can use with or without type parameters
|
|
83
|
+
const response1: ApiResponse<User> = { data: user, status: 200 };
|
|
84
|
+
const response2: ApiResponse = { status: 500, error: new Error('Failed') };
|
|
85
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# TypeScript Guidelines
|
|
2
|
+
|
|
3
|
+
TypeScript-specific best practices for Claude Code.
|
|
4
|
+
|
|
5
|
+
## Chunks
|
|
6
|
+
|
|
7
|
+
- `basics.md` - Type system fundamentals and strict mode
|
|
8
|
+
- `interfaces-types.md` - Interfaces vs types, when to use each
|
|
9
|
+
- `generics.md` - Generic programming patterns
|
|
10
|
+
- `async.md` - Async/await and Promise patterns
|
|
11
|
+
- `error-handling.md` - Type-safe error handling
|
|
12
|
+
- `testing.md` - Testing TypeScript code
|
|
13
|
+
- `config.md` - tsconfig.json best practices
|
|
14
|
+
- `performance.md` - Performance optimization
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# TypeScript Types & Interfaces
|
|
2
|
+
|
|
3
|
+
## Prefer Interfaces for Public APIs
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
// ✅ Use interfaces for object shapes
|
|
7
|
+
interface User {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
email: string;
|
|
11
|
+
createdAt: Date;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// ✅ Use type aliases for unions and complex types
|
|
15
|
+
type UserRole = 'admin' | 'editor' | 'viewer';
|
|
16
|
+
type ResponseHandler = (response: Response) => void;
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Discriminated Unions
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// ✅ Use discriminated unions for variant types
|
|
23
|
+
type Result<T> =
|
|
24
|
+
| { success: true; data: T }
|
|
25
|
+
| { success: false; error: string };
|
|
26
|
+
|
|
27
|
+
function handleResult(result: Result<User>) {
|
|
28
|
+
if (result.success) {
|
|
29
|
+
console.log(result.data.name); // TypeScript knows data exists
|
|
30
|
+
} else {
|
|
31
|
+
console.error(result.error); // TypeScript knows error exists
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Utility Types
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// Use built-in utility types
|
|
40
|
+
type PartialUser = Partial<User>; // All fields optional
|
|
41
|
+
type RequiredUser = Required<User>; // All fields required
|
|
42
|
+
type ReadonlyUser = Readonly<User>; // All fields readonly
|
|
43
|
+
type UserKeys = keyof User; // 'id' | 'name' | 'email' | 'createdAt'
|
|
44
|
+
type PickedUser = Pick<User, 'id' | 'name'>; // Only id and name
|
|
45
|
+
type OmittedUser = Omit<User, 'createdAt'>; // Everything except createdAt
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Type Guards
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// ✅ Use type guards for runtime checking
|
|
52
|
+
function isUser(value: unknown): value is User {
|
|
53
|
+
return (
|
|
54
|
+
typeof value === 'object' &&
|
|
55
|
+
value !== null &&
|
|
56
|
+
'id' in value &&
|
|
57
|
+
'email' in value
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Usage
|
|
62
|
+
const data: unknown = fetchData();
|
|
63
|
+
if (isUser(data)) {
|
|
64
|
+
console.log(data.email); // TypeScript knows it's a User
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Avoid `any`
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// ❌ Never use any
|
|
72
|
+
function process(data: any) {
|
|
73
|
+
return data.name; // No type safety
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ✅ Use unknown with type guards
|
|
77
|
+
function process(data: unknown) {
|
|
78
|
+
if (isUser(data)) {
|
|
79
|
+
return data.name; // Type-safe
|
|
80
|
+
}
|
|
81
|
+
throw new Error('Invalid data');
|
|
82
|
+
}
|
|
83
|
+
```
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# TypeScript Performance
|
|
2
|
+
|
|
3
|
+
## Choose Right Data Structures
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
// ❌ Array for lookups (O(n))
|
|
7
|
+
const users: User[] = [];
|
|
8
|
+
const findUser = (id: string) => users.find(u => u.id === id);
|
|
9
|
+
|
|
10
|
+
// ✅ Map for O(1) lookups
|
|
11
|
+
const users = new Map<string, User>();
|
|
12
|
+
const findUser = (id: string) => users.get(id);
|
|
13
|
+
|
|
14
|
+
// ❌ Array for membership checks
|
|
15
|
+
const hasPermission = (perms: string[], perm: string) => perms.includes(perm);
|
|
16
|
+
|
|
17
|
+
// ✅ Set for O(1) membership
|
|
18
|
+
const hasPermission = (perms: Set<string>, perm: string) => perms.has(perm);
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Avoid N+1 Queries
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// ❌ N+1 queries
|
|
25
|
+
const getOrdersWithCustomers = async () => {
|
|
26
|
+
const orders = await db.query('SELECT * FROM orders');
|
|
27
|
+
for (const order of orders) {
|
|
28
|
+
order.customer = await db.query('SELECT * FROM customers WHERE id = ?', [order.customerId]);
|
|
29
|
+
}
|
|
30
|
+
return orders;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// ✅ Single JOIN query
|
|
34
|
+
const getOrdersWithCustomers = async () => {
|
|
35
|
+
return db.query(`
|
|
36
|
+
SELECT orders.*, customers.name as customer_name
|
|
37
|
+
FROM orders
|
|
38
|
+
JOIN customers ON orders.customer_id = customers.id
|
|
39
|
+
`);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// ✅ Using ORM with eager loading
|
|
43
|
+
const getOrdersWithCustomers = async () => {
|
|
44
|
+
return orderRepository.find({ relations: ['customer'] });
|
|
45
|
+
};
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Parallel Execution
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// ❌ Sequential (slow)
|
|
52
|
+
const getUserData = async (userId: string) => {
|
|
53
|
+
const user = await fetchUser(userId); // 100ms
|
|
54
|
+
const posts = await fetchPosts(userId); // 150ms
|
|
55
|
+
const comments = await fetchComments(userId); // 120ms
|
|
56
|
+
return { user, posts, comments }; // Total: 370ms
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// ✅ Parallel (fast)
|
|
60
|
+
const getUserData = async (userId: string) => {
|
|
61
|
+
const [user, posts, comments] = await Promise.all([
|
|
62
|
+
fetchUser(userId),
|
|
63
|
+
fetchPosts(userId),
|
|
64
|
+
fetchComments(userId)
|
|
65
|
+
]);
|
|
66
|
+
return { user, posts, comments }; // Total: 150ms
|
|
67
|
+
};
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Memoization
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const memoize = <T extends (...args: any[]) => any>(fn: T): T => {
|
|
74
|
+
const cache = new Map<string, ReturnType<T>>();
|
|
75
|
+
|
|
76
|
+
return ((...args: any[]) => {
|
|
77
|
+
const key = JSON.stringify(args);
|
|
78
|
+
if (cache.has(key)) return cache.get(key);
|
|
79
|
+
const result = fn(...args);
|
|
80
|
+
cache.set(key, result);
|
|
81
|
+
return result;
|
|
82
|
+
}) as T;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const expensiveCalc = memoize((n: number) => {
|
|
86
|
+
// Expensive computation
|
|
87
|
+
return result;
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Batch Processing
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// ✅ Process in batches
|
|
95
|
+
const processUsers = async (userIds: string[]) => {
|
|
96
|
+
const BATCH_SIZE = 50;
|
|
97
|
+
|
|
98
|
+
for (let i = 0; i < userIds.length; i += BATCH_SIZE) {
|
|
99
|
+
const batch = userIds.slice(i, i + BATCH_SIZE);
|
|
100
|
+
await Promise.all(batch.map(id => updateUser(id)));
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
```
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# TypeScript Testing
|
|
2
|
+
|
|
3
|
+
## Test Structure: Arrange-Act-Assert
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
describe('UserService', () => {
|
|
7
|
+
describe('createUser', () => {
|
|
8
|
+
it('should create user with hashed password', async () => {
|
|
9
|
+
// Arrange
|
|
10
|
+
const userData = { email: 'test@example.com', password: 'password123' };
|
|
11
|
+
const mockRepo = { save: jest.fn().mockResolvedValue({ id: '1', ...userData }) };
|
|
12
|
+
const service = new UserService(mockRepo);
|
|
13
|
+
|
|
14
|
+
// Act
|
|
15
|
+
const result = await service.createUser(userData);
|
|
16
|
+
|
|
17
|
+
// Assert
|
|
18
|
+
expect(result.id).toBe('1');
|
|
19
|
+
expect(mockRepo.save).toHaveBeenCalledWith(
|
|
20
|
+
expect.objectContaining({ email: 'test@example.com' })
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Test Observable Behavior, Not Implementation
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
// ❌ Testing implementation details
|
|
31
|
+
it('should call validateEmail method', () => {
|
|
32
|
+
const spy = jest.spyOn(service, 'validateEmail');
|
|
33
|
+
service.createUser({ email: 'test@example.com' });
|
|
34
|
+
expect(spy).toHaveBeenCalled(); // Brittle - breaks if refactored
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// ✅ Testing observable behavior
|
|
38
|
+
it('should reject invalid email', async () => {
|
|
39
|
+
await expect(
|
|
40
|
+
service.createUser({ email: 'invalid' })
|
|
41
|
+
).rejects.toThrow('Invalid email');
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Test Doubles
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// Stub: Returns canned responses
|
|
49
|
+
const stubDatabase = {
|
|
50
|
+
findUser: () => ({ id: '1', name: 'Test User' })
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Mock: Pre-programmed with expectations
|
|
54
|
+
const mockPayment = {
|
|
55
|
+
charge: jest.fn()
|
|
56
|
+
.mockResolvedValueOnce({ success: true })
|
|
57
|
+
.mockResolvedValueOnce({ success: false })
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Fake: Working implementation (not for production)
|
|
61
|
+
class FakeDatabase implements Database {
|
|
62
|
+
private data = new Map<string, any>();
|
|
63
|
+
|
|
64
|
+
async save(id: string, data: any) { this.data.set(id, data); }
|
|
65
|
+
async find(id: string) { return this.data.get(id); }
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## One Test Per Condition
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// ❌ Multiple assertions for different scenarios
|
|
73
|
+
it('should validate user input', () => {
|
|
74
|
+
expect(() => validate({ age: -1 })).toThrow();
|
|
75
|
+
expect(() => validate({ age: 200 })).toThrow();
|
|
76
|
+
expect(() => validate({ name: '' })).toThrow();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// ✅ One test per condition
|
|
80
|
+
it('should reject negative age', () => {
|
|
81
|
+
expect(() => validate({ age: -1 })).toThrow('Age must be positive');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should reject age over 150', () => {
|
|
85
|
+
expect(() => validate({ age: 200 })).toThrow('Age must be under 150');
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Keep Tests Independent
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// ✅ Each test is self-contained
|
|
93
|
+
it('should update user', async () => {
|
|
94
|
+
const user = await service.createUser({ name: 'Test' });
|
|
95
|
+
const updated = await service.updateUser(user.id, { name: 'Updated' });
|
|
96
|
+
expect(updated.name).toBe('Updated');
|
|
97
|
+
});
|
|
98
|
+
```
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Base Patterns
|
|
2
|
+
|
|
3
|
+
## Value Object
|
|
4
|
+
|
|
5
|
+
Immutable object defined by its value, not identity.
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
class Email {
|
|
9
|
+
private readonly value: string;
|
|
10
|
+
|
|
11
|
+
constructor(email: string) {
|
|
12
|
+
if (!this.isValid(email)) throw new Error('Invalid email');
|
|
13
|
+
this.value = email.toLowerCase();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
equals(other: Email): boolean {
|
|
17
|
+
return this.value === other.value;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class Money {
|
|
22
|
+
constructor(
|
|
23
|
+
public readonly amount: number,
|
|
24
|
+
public readonly currency: Currency
|
|
25
|
+
) {
|
|
26
|
+
Object.freeze(this);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
add(other: Money): Money {
|
|
30
|
+
this.assertSameCurrency(other);
|
|
31
|
+
return new Money(this.amount + other.amount, this.currency);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Special Case (Null Object)
|
|
37
|
+
|
|
38
|
+
Replace null checks with polymorphism.
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
abstract class Customer {
|
|
42
|
+
abstract getDiscount(): number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class RealCustomer extends Customer {
|
|
46
|
+
getDiscount(): number { return 0.1; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class GuestCustomer extends Customer {
|
|
50
|
+
getDiscount(): number { return 0; } // No discount
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// No null checks needed
|
|
54
|
+
const customer = repo.findById(id) || new GuestCustomer();
|
|
55
|
+
const discount = customer.getDiscount();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Registry
|
|
59
|
+
|
|
60
|
+
Global access point for services.
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
class ServiceRegistry {
|
|
64
|
+
private static services = new Map<string, any>();
|
|
65
|
+
|
|
66
|
+
static register<T>(key: string, service: T): void {
|
|
67
|
+
this.services.set(key, service);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static get<T>(key: string): T {
|
|
71
|
+
return this.services.get(key);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Prefer dependency injection over registry
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Plugin
|
|
79
|
+
|
|
80
|
+
Extend behavior without modifying core code.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
interface ValidationPlugin {
|
|
84
|
+
validate(user: User): ValidationResult;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
class UserValidator {
|
|
88
|
+
private plugins: ValidationPlugin[] = [];
|
|
89
|
+
|
|
90
|
+
registerPlugin(plugin: ValidationPlugin): void {
|
|
91
|
+
this.plugins.push(plugin);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
validate(user: User): ValidationResult[] {
|
|
95
|
+
return this.plugins.map(p => p.validate(user));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Best Practices
|
|
101
|
+
|
|
102
|
+
- Use Value Objects to avoid primitive obsession
|
|
103
|
+
- Make Value Objects immutable
|
|
104
|
+
- Use Special Case instead of null checks
|
|
105
|
+
- Prefer dependency injection over Registry
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Concurrency Patterns
|
|
2
|
+
|
|
3
|
+
## Optimistic Locking
|
|
4
|
+
|
|
5
|
+
Detect conflicts on commit using version numbers.
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
class Product {
|
|
9
|
+
constructor(
|
|
10
|
+
public id: string,
|
|
11
|
+
public name: string,
|
|
12
|
+
public version: number = 1
|
|
13
|
+
) {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class ProductRepository {
|
|
17
|
+
async save(product: Product): Promise<void> {
|
|
18
|
+
const result = await this.db.execute(
|
|
19
|
+
`UPDATE products SET name = $1, version = version + 1
|
|
20
|
+
WHERE id = $2 AND version = $3`,
|
|
21
|
+
[product.name, product.id, product.version]
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
if (result.rowCount === 0) {
|
|
25
|
+
throw new OptimisticLockException('Product was modified');
|
|
26
|
+
}
|
|
27
|
+
product.version++;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Pessimistic Locking
|
|
33
|
+
|
|
34
|
+
Lock resources before editing.
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
class LockManager {
|
|
38
|
+
async acquireLock(resourceId: string, ownerId: string): Promise<boolean> {
|
|
39
|
+
const existing = await this.db.queryOne(
|
|
40
|
+
'SELECT * FROM locks WHERE resource_id = $1 AND expires_at > NOW()',
|
|
41
|
+
[resourceId]
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
if (existing) return false;
|
|
45
|
+
|
|
46
|
+
await this.db.execute(
|
|
47
|
+
'INSERT INTO locks (resource_id, owner_id, expires_at) VALUES ($1, $2, $3)',
|
|
48
|
+
[resourceId, ownerId, new Date(Date.now() + 30000)]
|
|
49
|
+
);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async releaseLock(resourceId: string, ownerId: string): Promise<void> {
|
|
54
|
+
await this.db.execute(
|
|
55
|
+
'DELETE FROM locks WHERE resource_id = $1 AND owner_id = $2',
|
|
56
|
+
[resourceId, ownerId]
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Coarse-Grained Lock
|
|
63
|
+
|
|
64
|
+
Lock entire aggregate rather than individual entities.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
class OrderRepository {
|
|
68
|
+
async save(order: Order): Promise<void> {
|
|
69
|
+
// Lock aggregate root, all children implicitly locked
|
|
70
|
+
await this.db.execute(
|
|
71
|
+
'SELECT * FROM orders WHERE id = $1 FOR UPDATE',
|
|
72
|
+
[order.id]
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Update order and all items in single transaction
|
|
76
|
+
await this.updateOrder(order);
|
|
77
|
+
await this.updateOrderItems(order.items);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Best Practices
|
|
83
|
+
|
|
84
|
+
- Use optimistic locking for low-contention scenarios
|
|
85
|
+
- Use pessimistic locking for high-contention or critical data
|
|
86
|
+
- Always set lock timeouts
|
|
87
|
+
- Implement retry logic with exponential backoff
|