@malamute/ai-rules 1.0.0 → 1.2.0
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/README.md +270 -121
- package/bin/cli.js +5 -2
- package/configs/_shared/.claude/rules/conventions/documentation.md +324 -0
- package/configs/_shared/.claude/rules/conventions/git.md +265 -0
- package/configs/_shared/.claude/rules/{performance.md → conventions/performance.md} +1 -1
- package/configs/_shared/.claude/rules/conventions/principles.md +334 -0
- package/configs/_shared/.claude/rules/devops/ci-cd.md +262 -0
- package/configs/_shared/.claude/rules/devops/docker.md +275 -0
- package/configs/_shared/.claude/rules/devops/nx.md +194 -0
- package/configs/_shared/.claude/rules/domain/backend/api-design.md +203 -0
- package/configs/_shared/.claude/rules/lang/csharp/async.md +220 -0
- package/configs/_shared/.claude/rules/lang/csharp/csharp.md +314 -0
- package/configs/_shared/.claude/rules/lang/csharp/linq.md +210 -0
- package/configs/_shared/.claude/rules/lang/python/async.md +337 -0
- package/configs/_shared/.claude/rules/lang/python/celery.md +476 -0
- package/configs/_shared/.claude/rules/lang/python/config.md +339 -0
- package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/database/sqlalchemy.md +6 -1
- package/configs/_shared/.claude/rules/lang/python/deployment.md +523 -0
- package/configs/_shared/.claude/rules/lang/python/error-handling.md +330 -0
- package/configs/_shared/.claude/rules/lang/python/migrations.md +421 -0
- package/configs/_shared/.claude/rules/lang/python/python.md +172 -0
- package/configs/_shared/.claude/rules/lang/python/repository.md +383 -0
- package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/testing.md +2 -69
- package/configs/_shared/.claude/rules/lang/typescript/async.md +447 -0
- package/configs/_shared/.claude/rules/lang/typescript/generics.md +356 -0
- package/configs/_shared/.claude/rules/lang/typescript/typescript.md +212 -0
- package/configs/_shared/.claude/rules/quality/error-handling.md +48 -0
- package/configs/_shared/.claude/rules/quality/logging.md +45 -0
- package/configs/_shared/.claude/rules/quality/observability.md +240 -0
- package/configs/_shared/.claude/rules/quality/testing-patterns.md +65 -0
- package/configs/_shared/.claude/rules/security/secrets-management.md +222 -0
- package/configs/_shared/.claude/skills/analysis/explore/SKILL.md +257 -0
- package/configs/_shared/.claude/skills/analysis/security-audit/SKILL.md +184 -0
- package/configs/_shared/.claude/skills/dev/api-endpoint/SKILL.md +126 -0
- package/configs/_shared/.claude/{commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
- package/configs/_shared/.claude/{commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
- package/configs/_shared/.claude/{commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
- package/configs/_shared/.claude/skills/infra/deploy/SKILL.md +139 -0
- package/configs/_shared/.claude/skills/infra/docker/SKILL.md +95 -0
- package/configs/_shared/.claude/skills/infra/migration/SKILL.md +158 -0
- package/configs/_shared/.claude/skills/nx/nx-affected/SKILL.md +72 -0
- package/configs/_shared/.claude/skills/nx/nx-lib/SKILL.md +375 -0
- package/configs/_shared/CLAUDE.md +52 -149
- package/configs/angular/.claude/rules/{components.md → core/components.md} +69 -15
- package/configs/angular/.claude/rules/core/resource.md +285 -0
- package/configs/angular/.claude/rules/core/signals.md +323 -0
- package/configs/angular/.claude/rules/http.md +338 -0
- package/configs/angular/.claude/rules/routing.md +291 -0
- package/configs/angular/.claude/rules/ssr.md +312 -0
- package/configs/angular/.claude/rules/state/signal-store.md +408 -0
- package/configs/angular/.claude/rules/{state.md → state/state.md} +2 -2
- package/configs/angular/.claude/rules/testing.md +7 -7
- package/configs/angular/.claude/rules/ui/aria.md +422 -0
- package/configs/angular/.claude/rules/ui/forms.md +424 -0
- package/configs/angular/.claude/rules/ui/pipes-directives.md +335 -0
- package/configs/angular/.claude/settings.json +1 -0
- package/configs/angular/.claude/skills/ngrx-slice/SKILL.md +362 -0
- package/configs/angular/.claude/skills/signal-store/SKILL.md +445 -0
- package/configs/angular/CLAUDE.md +24 -216
- package/configs/dotnet/.claude/rules/background-services.md +552 -0
- package/configs/dotnet/.claude/rules/configuration.md +426 -0
- package/configs/dotnet/.claude/rules/ddd.md +447 -0
- package/configs/dotnet/.claude/rules/dependency-injection.md +343 -0
- package/configs/dotnet/.claude/rules/mediatr.md +320 -0
- package/configs/dotnet/.claude/rules/middleware.md +489 -0
- package/configs/dotnet/.claude/rules/result-pattern.md +363 -0
- package/configs/dotnet/.claude/rules/validation.md +388 -0
- package/configs/dotnet/.claude/settings.json +21 -3
- package/configs/dotnet/CLAUDE.md +53 -286
- package/configs/fastapi/.claude/rules/background-tasks.md +254 -0
- package/configs/fastapi/.claude/rules/dependencies.md +170 -0
- package/configs/{python → fastapi}/.claude/rules/fastapi.md +61 -1
- package/configs/fastapi/.claude/rules/lifespan.md +274 -0
- package/configs/fastapi/.claude/rules/middleware.md +229 -0
- package/configs/fastapi/.claude/rules/pydantic.md +433 -0
- package/configs/fastapi/.claude/rules/responses.md +251 -0
- package/configs/fastapi/.claude/rules/routers.md +202 -0
- package/configs/fastapi/.claude/rules/security.md +222 -0
- package/configs/fastapi/.claude/rules/testing.md +251 -0
- package/configs/fastapi/.claude/rules/websockets.md +298 -0
- package/configs/fastapi/.claude/settings.json +33 -0
- package/configs/fastapi/CLAUDE.md +144 -0
- package/configs/flask/.claude/rules/blueprints.md +208 -0
- package/configs/flask/.claude/rules/cli.md +285 -0
- package/configs/flask/.claude/rules/configuration.md +281 -0
- package/configs/flask/.claude/rules/context.md +238 -0
- package/configs/flask/.claude/rules/error-handlers.md +278 -0
- package/configs/flask/.claude/rules/extensions.md +278 -0
- package/configs/flask/.claude/rules/flask.md +171 -0
- package/configs/flask/.claude/rules/marshmallow.md +206 -0
- package/configs/flask/.claude/rules/security.md +267 -0
- package/configs/flask/.claude/rules/testing.md +284 -0
- package/configs/flask/.claude/settings.json +33 -0
- package/configs/flask/CLAUDE.md +166 -0
- package/configs/nestjs/.claude/rules/common-patterns.md +300 -0
- package/configs/nestjs/.claude/rules/filters.md +376 -0
- package/configs/nestjs/.claude/rules/interceptors.md +317 -0
- package/configs/nestjs/.claude/rules/middleware.md +321 -0
- package/configs/nestjs/.claude/rules/modules.md +26 -0
- package/configs/nestjs/.claude/rules/pipes.md +351 -0
- package/configs/nestjs/.claude/rules/websockets.md +451 -0
- package/configs/nestjs/.claude/settings.json +16 -2
- package/configs/nestjs/CLAUDE.md +57 -215
- package/configs/nextjs/.claude/rules/api-routes.md +358 -0
- package/configs/nextjs/.claude/rules/authentication.md +355 -0
- package/configs/nextjs/.claude/rules/components.md +52 -0
- package/configs/nextjs/.claude/rules/data-fetching.md +249 -0
- package/configs/nextjs/.claude/rules/database.md +400 -0
- package/configs/nextjs/.claude/rules/middleware.md +303 -0
- package/configs/nextjs/.claude/rules/routing.md +324 -0
- package/configs/nextjs/.claude/rules/seo.md +350 -0
- package/configs/nextjs/.claude/rules/server-actions.md +353 -0
- package/configs/nextjs/.claude/rules/state/zustand.md +6 -6
- package/configs/nextjs/.claude/settings.json +5 -0
- package/configs/nextjs/CLAUDE.md +69 -331
- package/package.json +23 -9
- package/src/cli.js +220 -0
- package/src/config.js +29 -0
- package/src/index.js +13 -0
- package/src/installer.js +361 -0
- package/src/merge.js +116 -0
- package/src/tech-config.json +29 -0
- package/src/utils.js +96 -0
- package/configs/python/.claude/rules/flask.md +0 -332
- package/configs/python/.claude/settings.json +0 -18
- package/configs/python/CLAUDE.md +0 -273
- package/src/install.js +0 -315
- /package/configs/_shared/.claude/rules/{accessibility.md → domain/frontend/accessibility.md} +0 -0
- /package/configs/_shared/.claude/rules/{security.md → security/security.md} +0 -0
- /package/configs/_shared/.claude/skills/{debug → dev/debug}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{learning → dev/learning}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{spec → dev/spec}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{review → git/review}/SKILL.md +0 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.ts"
|
|
4
|
+
- "**/*.tsx"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# TypeScript Generics & Advanced Types
|
|
8
|
+
|
|
9
|
+
## Generic Basics
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// Generic function
|
|
13
|
+
function identity<T>(value: T): T {
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Generic with constraint
|
|
18
|
+
function getLength<T extends { length: number }>(item: T): number {
|
|
19
|
+
return item.length;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Multiple type parameters
|
|
23
|
+
function pair<K, V>(key: K, value: V): [K, V] {
|
|
24
|
+
return [key, value];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Generic interface
|
|
28
|
+
interface Repository<T> {
|
|
29
|
+
find(id: string): Promise<T | null>;
|
|
30
|
+
save(item: T): Promise<T>;
|
|
31
|
+
delete(id: string): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Generic class
|
|
35
|
+
class Cache<T> {
|
|
36
|
+
private items = new Map<string, T>();
|
|
37
|
+
|
|
38
|
+
get(key: string): T | undefined {
|
|
39
|
+
return this.items.get(key);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
set(key: string, value: T): void {
|
|
43
|
+
this.items.set(key, value);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Constraints
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// Extends constraint
|
|
52
|
+
function merge<T extends object, U extends object>(a: T, b: U): T & U {
|
|
53
|
+
return { ...a, ...b };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// keyof constraint
|
|
57
|
+
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
|
|
58
|
+
return obj[key];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Constructor constraint
|
|
62
|
+
function createInstance<T>(ctor: new () => T): T {
|
|
63
|
+
return new ctor();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Multiple constraints
|
|
67
|
+
function process<T extends Serializable & Validatable>(item: T): void {
|
|
68
|
+
item.validate();
|
|
69
|
+
item.serialize();
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Utility Types
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
interface User {
|
|
77
|
+
id: string;
|
|
78
|
+
name: string;
|
|
79
|
+
email: string;
|
|
80
|
+
password: string;
|
|
81
|
+
createdAt: Date;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Partial - all properties optional
|
|
85
|
+
type UpdateUserDto = Partial<User>;
|
|
86
|
+
// { id?: string; name?: string; ... }
|
|
87
|
+
|
|
88
|
+
// Required - all properties required
|
|
89
|
+
type CompleteUser = Required<User>;
|
|
90
|
+
|
|
91
|
+
// Pick - select specific properties
|
|
92
|
+
type UserCredentials = Pick<User, 'email' | 'password'>;
|
|
93
|
+
// { email: string; password: string }
|
|
94
|
+
|
|
95
|
+
// Omit - exclude properties
|
|
96
|
+
type PublicUser = Omit<User, 'password'>;
|
|
97
|
+
// { id: string; name: string; email: string; createdAt: Date }
|
|
98
|
+
|
|
99
|
+
// Record - key-value mapping
|
|
100
|
+
type UserRoles = Record<string, 'admin' | 'user' | 'guest'>;
|
|
101
|
+
// { [key: string]: 'admin' | 'user' | 'guest' }
|
|
102
|
+
|
|
103
|
+
// Readonly - immutable
|
|
104
|
+
type ImmutableUser = Readonly<User>;
|
|
105
|
+
|
|
106
|
+
// Extract - extract union members
|
|
107
|
+
type StringOrNumber = string | number | boolean;
|
|
108
|
+
type OnlyStrings = Extract<StringOrNumber, string>; // string
|
|
109
|
+
|
|
110
|
+
// Exclude - remove union members
|
|
111
|
+
type NoStrings = Exclude<StringOrNumber, string>; // number | boolean
|
|
112
|
+
|
|
113
|
+
// NonNullable - remove null/undefined
|
|
114
|
+
type MaybeString = string | null | undefined;
|
|
115
|
+
type DefiniteString = NonNullable<MaybeString>; // string
|
|
116
|
+
|
|
117
|
+
// ReturnType - get function return type
|
|
118
|
+
function getUser() { return { id: '1', name: 'John' }; }
|
|
119
|
+
type UserReturn = ReturnType<typeof getUser>;
|
|
120
|
+
// { id: string; name: string }
|
|
121
|
+
|
|
122
|
+
// Parameters - get function parameters
|
|
123
|
+
type GetUserParams = Parameters<typeof getUser>; // []
|
|
124
|
+
|
|
125
|
+
// Awaited - unwrap Promise
|
|
126
|
+
type ResolvedUser = Awaited<Promise<User>>; // User
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Discriminated Unions
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// GOOD - discriminated union with literal type
|
|
133
|
+
type Result<T> =
|
|
134
|
+
| { success: true; data: T }
|
|
135
|
+
| { success: false; error: string };
|
|
136
|
+
|
|
137
|
+
function handleResult<T>(result: Result<T>): T | null {
|
|
138
|
+
if (result.success) {
|
|
139
|
+
return result.data; // TypeScript knows data exists
|
|
140
|
+
}
|
|
141
|
+
console.error(result.error); // TypeScript knows error exists
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// API response pattern
|
|
146
|
+
type ApiResponse<T> =
|
|
147
|
+
| { status: 'loading' }
|
|
148
|
+
| { status: 'success'; data: T }
|
|
149
|
+
| { status: 'error'; error: Error };
|
|
150
|
+
|
|
151
|
+
function renderResponse<T>(response: ApiResponse<T>) {
|
|
152
|
+
switch (response.status) {
|
|
153
|
+
case 'loading':
|
|
154
|
+
return <Spinner />;
|
|
155
|
+
case 'success':
|
|
156
|
+
return <Data data={response.data} />;
|
|
157
|
+
case 'error':
|
|
158
|
+
return <Error error={response.error} />;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Event pattern
|
|
163
|
+
type AppEvent =
|
|
164
|
+
| { type: 'USER_LOGIN'; userId: string }
|
|
165
|
+
| { type: 'USER_LOGOUT' }
|
|
166
|
+
| { type: 'ITEM_ADDED'; itemId: string; quantity: number };
|
|
167
|
+
|
|
168
|
+
function handleEvent(event: AppEvent) {
|
|
169
|
+
switch (event.type) {
|
|
170
|
+
case 'USER_LOGIN':
|
|
171
|
+
return login(event.userId);
|
|
172
|
+
case 'USER_LOGOUT':
|
|
173
|
+
return logout();
|
|
174
|
+
case 'ITEM_ADDED':
|
|
175
|
+
return addItem(event.itemId, event.quantity);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Type Guards
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
// Type predicate (is)
|
|
184
|
+
function isString(value: unknown): value is string {
|
|
185
|
+
return typeof value === 'string';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function isUser(value: unknown): value is User {
|
|
189
|
+
return (
|
|
190
|
+
typeof value === 'object' &&
|
|
191
|
+
value !== null &&
|
|
192
|
+
'id' in value &&
|
|
193
|
+
'email' in value
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Usage
|
|
198
|
+
function process(value: unknown) {
|
|
199
|
+
if (isString(value)) {
|
|
200
|
+
console.log(value.toUpperCase()); // value is string
|
|
201
|
+
}
|
|
202
|
+
if (isUser(value)) {
|
|
203
|
+
console.log(value.email); // value is User
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Assertion function (asserts)
|
|
208
|
+
function assertDefined<T>(value: T | null | undefined): asserts value is T {
|
|
209
|
+
if (value === null || value === undefined) {
|
|
210
|
+
throw new Error('Value is not defined');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Usage
|
|
215
|
+
function getUser(id: string): User | null {
|
|
216
|
+
return db.find(id);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const user = getUser('123');
|
|
220
|
+
assertDefined(user); // Throws if null
|
|
221
|
+
console.log(user.name); // user is User, not User | null
|
|
222
|
+
|
|
223
|
+
// in operator narrowing
|
|
224
|
+
interface Cat { meow(): void }
|
|
225
|
+
interface Dog { bark(): void }
|
|
226
|
+
|
|
227
|
+
function speak(animal: Cat | Dog) {
|
|
228
|
+
if ('meow' in animal) {
|
|
229
|
+
animal.meow();
|
|
230
|
+
} else {
|
|
231
|
+
animal.bark();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Const Assertions
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
// Without as const - types are widened
|
|
240
|
+
const config = {
|
|
241
|
+
endpoint: '/api',
|
|
242
|
+
methods: ['GET', 'POST'],
|
|
243
|
+
};
|
|
244
|
+
// type: { endpoint: string; methods: string[] }
|
|
245
|
+
|
|
246
|
+
// With as const - literal types preserved
|
|
247
|
+
const config = {
|
|
248
|
+
endpoint: '/api',
|
|
249
|
+
methods: ['GET', 'POST'],
|
|
250
|
+
} as const;
|
|
251
|
+
// type: { readonly endpoint: '/api'; readonly methods: readonly ['GET', 'POST'] }
|
|
252
|
+
|
|
253
|
+
// Useful for union types from arrays
|
|
254
|
+
const ROLES = ['admin', 'user', 'guest'] as const;
|
|
255
|
+
type Role = typeof ROLES[number]; // 'admin' | 'user' | 'guest'
|
|
256
|
+
|
|
257
|
+
// Object values as union
|
|
258
|
+
const STATUS = {
|
|
259
|
+
PENDING: 'pending',
|
|
260
|
+
ACTIVE: 'active',
|
|
261
|
+
INACTIVE: 'inactive',
|
|
262
|
+
} as const;
|
|
263
|
+
type Status = typeof STATUS[keyof typeof STATUS];
|
|
264
|
+
// 'pending' | 'active' | 'inactive'
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Conditional Types
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
// Basic conditional
|
|
271
|
+
type IsString<T> = T extends string ? true : false;
|
|
272
|
+
|
|
273
|
+
type A = IsString<string>; // true
|
|
274
|
+
type B = IsString<number>; // false
|
|
275
|
+
|
|
276
|
+
// Infer keyword - extract types
|
|
277
|
+
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
|
|
278
|
+
|
|
279
|
+
type X = UnwrapPromise<Promise<string>>; // string
|
|
280
|
+
type Y = UnwrapPromise<number>; // number
|
|
281
|
+
|
|
282
|
+
// Extract array element type
|
|
283
|
+
type ArrayElement<T> = T extends (infer E)[] ? E : never;
|
|
284
|
+
|
|
285
|
+
type El = ArrayElement<string[]>; // string
|
|
286
|
+
|
|
287
|
+
// Function return type extraction
|
|
288
|
+
type GetReturn<T> = T extends (...args: any[]) => infer R ? R : never;
|
|
289
|
+
|
|
290
|
+
// Distributive conditional types
|
|
291
|
+
type ToArray<T> = T extends any ? T[] : never;
|
|
292
|
+
|
|
293
|
+
type Distributed = ToArray<string | number>;
|
|
294
|
+
// string[] | number[] (not (string | number)[])
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Mapped Types
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
// Make all properties optional
|
|
301
|
+
type Optional<T> = {
|
|
302
|
+
[K in keyof T]?: T[K];
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// Make all properties readonly
|
|
306
|
+
type Immutable<T> = {
|
|
307
|
+
readonly [K in keyof T]: T[K];
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
// Transform property types
|
|
311
|
+
type Stringify<T> = {
|
|
312
|
+
[K in keyof T]: string;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// Key remapping (4.1+)
|
|
316
|
+
type Getters<T> = {
|
|
317
|
+
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
interface Person {
|
|
321
|
+
name: string;
|
|
322
|
+
age: number;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
type PersonGetters = Getters<Person>;
|
|
326
|
+
// { getName: () => string; getAge: () => number }
|
|
327
|
+
|
|
328
|
+
// Filter properties by type
|
|
329
|
+
type OnlyStrings<T> = {
|
|
330
|
+
[K in keyof T as T[K] extends string ? K : never]: T[K];
|
|
331
|
+
};
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## Template Literal Types
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
// Basic template literal
|
|
338
|
+
type Greeting = `Hello, ${string}!`;
|
|
339
|
+
|
|
340
|
+
// Event names
|
|
341
|
+
type EventName = `on${Capitalize<'click' | 'focus' | 'blur'>}`;
|
|
342
|
+
// 'onClick' | 'onFocus' | 'onBlur'
|
|
343
|
+
|
|
344
|
+
// API routes
|
|
345
|
+
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
346
|
+
type Route = '/users' | '/posts';
|
|
347
|
+
type Endpoint = `${HttpMethod} ${Route}`;
|
|
348
|
+
// 'GET /users' | 'GET /posts' | 'POST /users' | ...
|
|
349
|
+
|
|
350
|
+
// CSS units
|
|
351
|
+
type CSSUnit = 'px' | 'em' | 'rem' | '%';
|
|
352
|
+
type CSSValue = `${number}${CSSUnit}`;
|
|
353
|
+
|
|
354
|
+
const width: CSSValue = '100px'; // OK
|
|
355
|
+
const height: CSSValue = '50%'; // OK
|
|
356
|
+
```
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.ts"
|
|
4
|
+
- "**/*.tsx"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# TypeScript Code Style Rules
|
|
8
|
+
|
|
9
|
+
## Strict Mode Required
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"strict": true,
|
|
14
|
+
"noImplicitAny": true,
|
|
15
|
+
"strictNullChecks": true
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Type Guidelines
|
|
20
|
+
|
|
21
|
+
### Interfaces vs Types
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// Interface for object shapes
|
|
25
|
+
interface User {
|
|
26
|
+
id: string;
|
|
27
|
+
name: string;
|
|
28
|
+
email: string;
|
|
29
|
+
createdAt: Date;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Type for unions/intersections
|
|
33
|
+
type Status = 'pending' | 'active' | 'inactive';
|
|
34
|
+
type Result<T> = { success: true; data: T } | { success: false; error: string };
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### NEVER Use `any`
|
|
38
|
+
|
|
39
|
+
**`any` is forbidden.** It disables type checking and defeats the purpose of TypeScript.
|
|
40
|
+
|
|
41
|
+
| Instead of `any` | Use |
|
|
42
|
+
|------------------|-----|
|
|
43
|
+
| Unknown data | `unknown` + type guard |
|
|
44
|
+
| Object with unknown keys | `Record<string, unknown>` |
|
|
45
|
+
| Array of unknown | `unknown[]` |
|
|
46
|
+
| Function args | Generics `<T>` |
|
|
47
|
+
| JSON response | Define interface or use `unknown` |
|
|
48
|
+
| Third-party lib | `@types/*` or declare module |
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// BAD - NEVER do this
|
|
52
|
+
function process(data: any) { }
|
|
53
|
+
const response = await fetch(url) as any;
|
|
54
|
+
const items: any[] = [];
|
|
55
|
+
|
|
56
|
+
// GOOD - unknown + type guard
|
|
57
|
+
function process(data: unknown) {
|
|
58
|
+
if (isValidData(data)) {
|
|
59
|
+
// data is now typed
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// GOOD - explicit type
|
|
64
|
+
function process(data: UserInput) { }
|
|
65
|
+
|
|
66
|
+
// GOOD - generic
|
|
67
|
+
function transform<T>(data: T): T { }
|
|
68
|
+
|
|
69
|
+
// GOOD - Record for dynamic keys
|
|
70
|
+
const cache: Record<string, unknown> = {};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Exception**: Only when interfacing with untyped legacy code, with explicit justification:
|
|
74
|
+
```typescript
|
|
75
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Legacy API v1, see TECH-123
|
|
76
|
+
const legacyResponse = await oldApi.fetch() as any;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Explicit Return Types on Public Functions
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// BAD - inferred return type
|
|
83
|
+
export function getUser(id: string) {
|
|
84
|
+
return db.users.findById(id);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// GOOD - explicit return type
|
|
88
|
+
export function getUser(id: string): Promise<User | null> {
|
|
89
|
+
return db.users.findById(id);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Naming Conventions
|
|
94
|
+
|
|
95
|
+
### Be Explicit - No Cryptic Names
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// BAD - cryptic names
|
|
99
|
+
const c = getConfig();
|
|
100
|
+
users.filter(u => u.a);
|
|
101
|
+
const d = new Date();
|
|
102
|
+
const tmp = calculate();
|
|
103
|
+
|
|
104
|
+
// GOOD - explicit names
|
|
105
|
+
const appConfig = getConfig();
|
|
106
|
+
users.filter(user => user.isActive);
|
|
107
|
+
const createdAt = new Date();
|
|
108
|
+
const totalAmount = calculate();
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Named Constants Over Magic Numbers
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// BAD - magic numbers
|
|
115
|
+
if (password.length < 8) { }
|
|
116
|
+
setTimeout(fn, 86400000);
|
|
117
|
+
|
|
118
|
+
// GOOD - named constants
|
|
119
|
+
const MIN_PASSWORD_LENGTH = 8;
|
|
120
|
+
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
|
121
|
+
|
|
122
|
+
if (password.length < MIN_PASSWORD_LENGTH) { }
|
|
123
|
+
setTimeout(fn, ONE_DAY_MS);
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Lint Disable Rules
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// FORBIDDEN - no justification
|
|
130
|
+
// eslint-disable-next-line
|
|
131
|
+
const x = something;
|
|
132
|
+
|
|
133
|
+
// ALLOWED - with explicit reason + ticket
|
|
134
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Legacy API returns any, see TECH-456
|
|
135
|
+
const legacyData = await legacyApi.fetch();
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Function Guidelines
|
|
139
|
+
|
|
140
|
+
### Small Functions (< 30 lines)
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// BAD - too long, multiple responsibilities
|
|
144
|
+
async function processOrder(order: Order) {
|
|
145
|
+
// 50+ lines of validation, calculation, saving, emailing...
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// GOOD - single responsibility
|
|
149
|
+
async function processOrder(order: Order): Promise<OrderResult> {
|
|
150
|
+
validateOrder(order);
|
|
151
|
+
const total = calculateTotal(order);
|
|
152
|
+
const savedOrder = await saveOrder(order, total);
|
|
153
|
+
await sendConfirmation(savedOrder);
|
|
154
|
+
return savedOrder;
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Max Nesting: 3 Levels - Use Early Returns
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
// BAD - deep nesting
|
|
162
|
+
function process(user: User | null) {
|
|
163
|
+
if (user) {
|
|
164
|
+
if (user.isActive) {
|
|
165
|
+
if (user.hasPermission) {
|
|
166
|
+
// do something
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// GOOD - early returns
|
|
173
|
+
function process(user: User | null) {
|
|
174
|
+
if (!user) return;
|
|
175
|
+
if (!user.isActive) return;
|
|
176
|
+
if (!user.hasPermission) return;
|
|
177
|
+
|
|
178
|
+
// do something
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Error Handling
|
|
183
|
+
|
|
184
|
+
### Never Swallow Errors Silently
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// BAD - silent failure
|
|
188
|
+
try {
|
|
189
|
+
await saveUser(user);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
// empty catch
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// GOOD - log with context
|
|
195
|
+
try {
|
|
196
|
+
await saveUser(user);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
logger.error('Failed to save user', { userId: user.id, error });
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### User-Facing vs Internal Errors
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// User-facing: clear, actionable message
|
|
207
|
+
throw new BadRequestError('Email address is already registered');
|
|
208
|
+
|
|
209
|
+
// Internal: detailed logs, generic user message
|
|
210
|
+
logger.error('Database constraint violation', { error, userId });
|
|
211
|
+
throw new InternalError('Unable to complete registration');
|
|
212
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Error Handling Principles
|
|
7
|
+
|
|
8
|
+
## Core Rules
|
|
9
|
+
|
|
10
|
+
- **Fail fast, fail loud** - Don't swallow errors silently
|
|
11
|
+
- **Use typed errors** with error codes
|
|
12
|
+
- **Separate user-facing from internal errors**
|
|
13
|
+
- **Always log with context** before throwing
|
|
14
|
+
|
|
15
|
+
## Error Categories
|
|
16
|
+
|
|
17
|
+
| Type | HTTP Code | When to Use |
|
|
18
|
+
|------|-----------|-------------|
|
|
19
|
+
| Validation | 400 | Invalid input format |
|
|
20
|
+
| Authentication | 401 | Missing/invalid credentials |
|
|
21
|
+
| Authorization | 403 | Insufficient permissions |
|
|
22
|
+
| Not Found | 404 | Resource doesn't exist |
|
|
23
|
+
| Conflict | 409 | Duplicate resource |
|
|
24
|
+
| Internal | 500 | Unexpected server error |
|
|
25
|
+
|
|
26
|
+
## Error Response Format
|
|
27
|
+
|
|
28
|
+
Consistent API error responses should include:
|
|
29
|
+
- Error code (machine-readable)
|
|
30
|
+
- Message (human-readable)
|
|
31
|
+
- Details/context (optional)
|
|
32
|
+
- Request ID (for correlation)
|
|
33
|
+
|
|
34
|
+
## Anti-Patterns
|
|
35
|
+
|
|
36
|
+
- Empty catch blocks
|
|
37
|
+
- Logging without rethrowing
|
|
38
|
+
- Exposing stack traces to users
|
|
39
|
+
- Generic "Something went wrong" without logging details
|
|
40
|
+
- Swallowing errors in fire-and-forget operations
|
|
41
|
+
|
|
42
|
+
## Best Practices
|
|
43
|
+
|
|
44
|
+
- Create error hierarchy with base error class
|
|
45
|
+
- Include enough context for debugging
|
|
46
|
+
- Use Result/Either pattern for expected failures
|
|
47
|
+
- Implement retry with exponential backoff for transient errors
|
|
48
|
+
- Centralize error handling (middleware/interceptor)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Logging Principles
|
|
7
|
+
|
|
8
|
+
## Log Levels
|
|
9
|
+
|
|
10
|
+
| Level | When to Use |
|
|
11
|
+
|-------|-------------|
|
|
12
|
+
| `debug` | Development details, variable values |
|
|
13
|
+
| `info` | Business events, state changes |
|
|
14
|
+
| `warn` | Recoverable issues, deprecations |
|
|
15
|
+
| `error` | Failures requiring attention |
|
|
16
|
+
|
|
17
|
+
## What to Log
|
|
18
|
+
|
|
19
|
+
- Application lifecycle (startup, shutdown)
|
|
20
|
+
- Business events (order placed, user registered)
|
|
21
|
+
- External calls with duration (HTTP, DB)
|
|
22
|
+
- Errors with full context
|
|
23
|
+
|
|
24
|
+
## What NOT to Log
|
|
25
|
+
|
|
26
|
+
- Passwords
|
|
27
|
+
- Auth tokens
|
|
28
|
+
- Credit card numbers
|
|
29
|
+
- PII (SSN, personal data)
|
|
30
|
+
- Full request bodies (may contain secrets)
|
|
31
|
+
|
|
32
|
+
## Best Practices
|
|
33
|
+
|
|
34
|
+
- **Structured logging** (JSON) in production
|
|
35
|
+
- **Include context**: userId, requestId, traceId
|
|
36
|
+
- **Redact sensitive fields** before logging
|
|
37
|
+
- **Correlation IDs** across async operations
|
|
38
|
+
- **Log at boundaries**: API entry/exit, external calls
|
|
39
|
+
|
|
40
|
+
## Log Message Guidelines
|
|
41
|
+
|
|
42
|
+
- Use consistent format across the codebase
|
|
43
|
+
- Include actionable information
|
|
44
|
+
- Avoid logging the same event multiple times
|
|
45
|
+
- Don't log expected/handled errors at ERROR level
|