clearctx 3.0.0 → 3.1.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.
@@ -0,0 +1,932 @@
1
+ ---
2
+ name: typescript
3
+ description: Production-grade TypeScript patterns for type-first development, generics, strict mode, type narrowing, and module architecture
4
+ domain: language
5
+ keywords: [typescript, types, generics, interfaces, strict-mode, type-guards, utility-types, tsconfig]
6
+ version: 1.0.0
7
+ ---
8
+
9
+ # TypeScript Expertise Skill
10
+
11
+ ## Worker Context
12
+
13
+ You are a TypeScript expert worker. Apply these patterns to ALL code you write.
14
+
15
+ ### Type-First Development
16
+
17
+ **CRITICAL:** Define types/interfaces BEFORE implementation. Types ARE your documentation.
18
+
19
+ ```typescript
20
+ // GOOD: Type-first approach
21
+ interface User {
22
+ id: string;
23
+ email: string;
24
+ role: 'admin' | 'user';
25
+ createdAt: Date;
26
+ }
27
+
28
+ interface CreateUserRequest {
29
+ email: string;
30
+ password: string;
31
+ }
32
+
33
+ function createUser(req: CreateUserRequest): User {
34
+ // Implementation follows types
35
+ }
36
+
37
+ // BAD: Implementation-first, types retrofitted
38
+ function createUser(email, password) {
39
+ return { id: generateId(), email, role: 'user', createdAt: new Date() };
40
+ }
41
+ ```
42
+
43
+ **IMPORTANT:** Export types from module index. Shared types go in `types/` directory.
44
+
45
+ ### Interface vs Type Decision Tree
46
+
47
+ | Use Case | Use This | Why |
48
+ |----------|----------|-----|
49
+ | Object shapes (may extend later) | `interface` | Declaration merging, cleaner extends |
50
+ | Unions, intersections, primitives | `type` | Only types support `A \| B`, `A & B`, mapped types |
51
+ | Function signatures | `type` | Cleaner: `type Handler = (req: Request) => Response` |
52
+ | Class contracts | `interface` | `implements` keyword, natural fit |
53
+ | When either works | `interface` | Slightly better error messages, extendable |
54
+
55
+ ```typescript
56
+ // GOOD: Correct tool selection
57
+ interface User {
58
+ id: string;
59
+ name: string;
60
+ }
61
+
62
+ interface AdminUser extends User {
63
+ permissions: string[];
64
+ }
65
+
66
+ type Result<T> = { success: true; data: T } | { success: false; error: string };
67
+ type Handler = (req: Request) => Promise<Response>;
68
+ type Nullable<T> = T | null;
69
+
70
+ // BAD: Wrong tool for the job
71
+ type User = { // Should be interface (object shape)
72
+ id: string;
73
+ name: string;
74
+ }
75
+
76
+ interface Result<T> { // Cannot express union with interface
77
+ success: boolean;
78
+ data?: T;
79
+ error?: string;
80
+ }
81
+ ```
82
+
83
+ ### Generics
84
+
85
+ **Constrain generics with `extends`** — NEVER add generics that don't constrain anything.
86
+
87
+ ```typescript
88
+ // GOOD: Meaningful constraints
89
+ function findById<T extends { id: string }>(items: T[], id: string): T | undefined {
90
+ return items.find(item => item.id === id);
91
+ }
92
+
93
+ type ApiResponse<TData> = {
94
+ data: TData;
95
+ timestamp: number;
96
+ };
97
+
98
+ function processItems<T extends { status: string }>(
99
+ items: T[],
100
+ filter: (item: T) => boolean
101
+ ): T[] {
102
+ return items.filter(filter);
103
+ }
104
+
105
+ // BAD: Unconstrained generics (just use concrete types)
106
+ function log<T>(value: T): void { // T adds no value here
107
+ console.log(value);
108
+ }
109
+
110
+ // GOOD: No generic needed
111
+ function log(value: unknown): void {
112
+ console.log(value);
113
+ }
114
+ ```
115
+
116
+ **Use descriptive names for complex generics:** `TResponse`, `TInput`, `TEntity`, not just `T`, `U`, `V`.
117
+
118
+ ### Strict Mode Configuration
119
+
120
+ **CRITICAL:** Always enable in `tsconfig.json`:
121
+
122
+ ```json
123
+ {
124
+ "compilerOptions": {
125
+ "strict": true,
126
+ "noUncheckedIndexedAccess": true,
127
+ "exactOptionalPropertyTypes": true,
128
+ "noImplicitOverride": true,
129
+ "noImplicitReturns": true,
130
+ "noFallthroughCasesInSwitch": true
131
+ }
132
+ }
133
+ ```
134
+
135
+ **NEVER use `// @ts-ignore`** unless fixing third-party type bugs — add comment explaining why.
136
+
137
+ ```typescript
138
+ // BAD: Silencing errors
139
+ // @ts-ignore
140
+ const result = dangerousOperation();
141
+
142
+ // GOOD: Fix the type issue
143
+ const result = dangerousOperation() as Result;
144
+
145
+ // ACCEPTABLE: Third-party bug with explanation
146
+ // @ts-ignore — bug in @types/legacy-lib v3.2.1, fixed in v3.3.0
147
+ // See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/12345
148
+ import { brokenFunction } from 'legacy-lib';
149
+ ```
150
+
151
+ ### Null Safety
152
+
153
+ **CRITICAL:** Strict null checks ON. Use `??` for defaults, `?.` for optional access.
154
+
155
+ ```typescript
156
+ // GOOD: Null-safe patterns
157
+ const username = user?.profile?.name ?? 'Anonymous';
158
+ const config = loadConfig() ?? getDefaults();
159
+
160
+ type Result<T> =
161
+ | { success: true; data: T }
162
+ | { success: false; error: string };
163
+
164
+ function processResult<T>(result: Result<T>): void {
165
+ if (result.success) {
166
+ console.log(result.data); // TypeScript knows data exists
167
+ } else {
168
+ console.error(result.error); // TypeScript knows error exists
169
+ }
170
+ }
171
+
172
+ // BAD: Non-null assertions without guards
173
+ const name = user!.profile!.name; // Crashes if user/profile is null
174
+
175
+ // GOOD: Type guard first
176
+ if (user?.profile) {
177
+ const name = user.profile.name; // Safe
178
+ }
179
+ ```
180
+
181
+ **NEVER use non-null assertion `!`** without explicit type guard first.
182
+
183
+ ### Enum Patterns
184
+
185
+ **Prefer `as const` objects over enums** for tree-shaking:
186
+
187
+ ```typescript
188
+ // GOOD: const object (tree-shakable, no runtime overhead)
189
+ const STATUS = {
190
+ PENDING: 'pending',
191
+ ACTIVE: 'active',
192
+ COMPLETED: 'completed',
193
+ } as const;
194
+
195
+ type Status = typeof STATUS[keyof typeof STATUS]; // 'pending' | 'active' | 'completed'
196
+
197
+ function updateStatus(status: Status): void {
198
+ if (status === STATUS.PENDING) { /* ... */ }
199
+ }
200
+
201
+ // ACCEPTABLE: String enum when you need runtime iteration
202
+ enum Color {
203
+ Red = 'red',
204
+ Green = 'green',
205
+ Blue = 'blue',
206
+ }
207
+
208
+ // BAD: Numeric enum (ambiguous reverse mapping)
209
+ enum Status {
210
+ Pending, // 0
211
+ Active, // 1
212
+ Completed // 2
213
+ }
214
+ ```
215
+
216
+ ### Type Narrowing
217
+
218
+ **Use discriminated unions** (tagged unions with `kind`/`type` field):
219
+
220
+ ```typescript
221
+ // GOOD: Discriminated union
222
+ type ApiState<T> =
223
+ | { status: 'idle' }
224
+ | { status: 'loading' }
225
+ | { status: 'success'; data: T }
226
+ | { status: 'error'; error: string };
227
+
228
+ function render<T>(state: ApiState<T>): string {
229
+ switch (state.status) {
230
+ case 'idle':
231
+ return 'Not started';
232
+ case 'loading':
233
+ return 'Loading...';
234
+ case 'success':
235
+ return `Data: ${state.data}`; // TypeScript knows data exists
236
+ case 'error':
237
+ return `Error: ${state.error}`; // TypeScript knows error exists
238
+ }
239
+ }
240
+
241
+ // Custom type guard
242
+ function isUser(value: unknown): value is User {
243
+ return (
244
+ typeof value === 'object' &&
245
+ value !== null &&
246
+ 'id' in value &&
247
+ 'email' in value
248
+ );
249
+ }
250
+
251
+ // Use 'in' operator for narrowing
252
+ function processValue(value: string | { message: string }): string {
253
+ if (typeof value === 'string') {
254
+ return value;
255
+ }
256
+ return value.message;
257
+ }
258
+
259
+ // Use 'satisfies' for type checking without widening
260
+ const config = {
261
+ host: 'localhost',
262
+ port: 3000,
263
+ } satisfies Record<string, string | number>;
264
+
265
+ config.port; // Type is still number, not string | number
266
+ ```
267
+
268
+ **NEVER use type assertions instead of narrowing:**
269
+
270
+ ```typescript
271
+ // BAD: Type assertion
272
+ function getUser(data: unknown): User {
273
+ return data as User; // No runtime validation
274
+ }
275
+
276
+ // GOOD: Type guard
277
+ function getUser(data: unknown): User {
278
+ if (!isUser(data)) {
279
+ throw new Error('Invalid user data');
280
+ }
281
+ return data;
282
+ }
283
+ ```
284
+
285
+ ### Utility Types Reference
286
+
287
+ | Type | Use When | Example |
288
+ |------|----------|---------|
289
+ | `Partial<T>` | Optional update payload | `Partial<User>` for PATCH body |
290
+ | `Required<T>` | Ensure all fields present | Config after defaults applied |
291
+ | `Pick<T, K>` | Subset of fields | `Pick<User, 'id' \| 'name'>` |
292
+ | `Omit<T, K>` | Exclude fields | `Omit<User, 'password'>` for API response |
293
+ | `Record<K, V>` | Typed dictionaries | `Record<string, Handler>` |
294
+ | `ReturnType<F>` | Infer function return | `ReturnType<typeof getUser>` |
295
+ | `Parameters<F>` | Infer function args | `Parameters<typeof handler>[0]` |
296
+ | `Awaited<T>` | Unwrap Promise | `Awaited<ReturnType<typeof fetchUser>>` |
297
+ | `NonNullable<T>` | Exclude null/undefined | `NonNullable<string \| null>` |
298
+
299
+ ```typescript
300
+ // Common utility type patterns
301
+ type UserUpdatePayload = Partial<Omit<User, 'id' | 'createdAt'>>;
302
+
303
+ type ApiHandler = (req: Request) => Promise<Response>;
304
+ type HandlerParams = Parameters<ApiHandler>[0];
305
+ type HandlerReturn = Awaited<ReturnType<ApiHandler>>;
306
+
307
+ type UserPublic = Omit<User, 'password' | 'passwordHash'>;
308
+ type UserCache = Pick<User, 'id' | 'email' | 'role'>;
309
+
310
+ type StatusMap = Record<Status, { label: string; color: string }>;
311
+ ```
312
+
313
+ ### Module Patterns
314
+
315
+ **Barrel exports** — `index.ts` per feature:
316
+
317
+ ```typescript
318
+ // src/users/index.ts
319
+ export { createUser, updateUser, deleteUser } from './user.service';
320
+ export { UserController } from './user.controller';
321
+ export type { User, CreateUserRequest, UpdateUserRequest } from './user.types';
322
+
323
+ // Import from barrel
324
+ import { createUser, type User } from '@/users';
325
+ ```
326
+
327
+ **Path aliases** — configure in `tsconfig.json`:
328
+
329
+ ```json
330
+ {
331
+ "compilerOptions": {
332
+ "baseUrl": ".",
333
+ "paths": {
334
+ "@/*": ["src/*"],
335
+ "@/types": ["src/types"]
336
+ }
337
+ }
338
+ }
339
+ ```
340
+
341
+ **Declaration files** — for untyped third-party libs:
342
+
343
+ ```typescript
344
+ // src/types/legacy-lib.d.ts
345
+ declare module 'legacy-lib' {
346
+ export function doSomething(input: string): Promise<Result>;
347
+ export interface Result {
348
+ success: boolean;
349
+ data: unknown;
350
+ }
351
+ }
352
+ ```
353
+
354
+ **NEVER use `require()`** — always use `import`:
355
+
356
+ ```typescript
357
+ // BAD
358
+ const express = require('express');
359
+
360
+ // GOOD
361
+ import express from 'express';
362
+ import type { Request, Response } from 'express';
363
+ ```
364
+
365
+ ### Error Handling
366
+
367
+ **Typed error classes:**
368
+
369
+ ```typescript
370
+ // GOOD: Type-safe error handling
371
+ class AppError extends Error {
372
+ constructor(
373
+ public code: string,
374
+ message: string,
375
+ public statusCode: number = 500
376
+ ) {
377
+ super(message);
378
+ this.name = 'AppError';
379
+ }
380
+ }
381
+
382
+ class ValidationError extends AppError {
383
+ constructor(message: string, public fields: string[]) {
384
+ super('VALIDATION_ERROR', message, 400);
385
+ }
386
+ }
387
+
388
+ // Result pattern for expected failures
389
+ type Result<T, E = Error> =
390
+ | { ok: true; value: T }
391
+ | { ok: false; error: E };
392
+
393
+ function parseUser(data: unknown): Result<User, ValidationError> {
394
+ if (!isUser(data)) {
395
+ return { ok: false, error: new ValidationError('Invalid user', []) };
396
+ }
397
+ return { ok: true, value: data };
398
+ }
399
+ ```
400
+
401
+ **Exhaustive switch checks with `never`:**
402
+
403
+ ```typescript
404
+ type Action =
405
+ | { type: 'create'; payload: User }
406
+ | { type: 'update'; payload: Partial<User> }
407
+ | { type: 'delete'; payload: string };
408
+
409
+ function reducer(action: Action): void {
410
+ switch (action.type) {
411
+ case 'create':
412
+ return handleCreate(action.payload);
413
+ case 'update':
414
+ return handleUpdate(action.payload);
415
+ case 'delete':
416
+ return handleDelete(action.payload);
417
+ default:
418
+ // If we add a new action type and forget to handle it, TypeScript errors here
419
+ const _exhaustive: never = action;
420
+ throw new Error(`Unhandled action: ${_exhaustive}`);
421
+ }
422
+ }
423
+ ```
424
+
425
+ ### tsconfig.json Best Practices
426
+
427
+ ```json
428
+ {
429
+ "compilerOptions": {
430
+ // Modern JavaScript
431
+ "target": "ES2022",
432
+ "lib": ["ES2022"],
433
+
434
+ // Module system
435
+ "module": "NodeNext", // For Node.js
436
+ "moduleResolution": "NodeNext", // For Node.js
437
+ // OR for bundlers:
438
+ // "module": "ESNext",
439
+ // "moduleResolution": "bundler",
440
+
441
+ // Emit
442
+ "outDir": "./dist",
443
+ "declaration": true, // For libraries
444
+ "declarationMap": true,
445
+ "sourceMap": true,
446
+
447
+ // Strict checks (CRITICAL)
448
+ "strict": true,
449
+ "noUncheckedIndexedAccess": true,
450
+ "exactOptionalPropertyTypes": true,
451
+ "noImplicitOverride": true,
452
+ "noImplicitReturns": true,
453
+ "noFallthroughCasesInSwitch": true,
454
+
455
+ // Module resolution
456
+ "esModuleInterop": true,
457
+ "allowSyntheticDefaultImports": true,
458
+ "resolveJsonModule": true,
459
+
460
+ // Monorepo support
461
+ "composite": true, // For project references
462
+
463
+ // Path aliases
464
+ "baseUrl": ".",
465
+ "paths": {
466
+ "@/*": ["src/*"]
467
+ },
468
+
469
+ // Performance
470
+ "skipLibCheck": true,
471
+ "incremental": true
472
+ },
473
+ "include": ["src/**/*"],
474
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
475
+ }
476
+ ```
477
+
478
+ ## Conventions
479
+
480
+ 1. **Naming:**
481
+ - Interfaces/Types: PascalCase — `User`, `CreateUserRequest`, `ApiResponse<T>`
482
+ - Type parameters: Single letter for simple (`T`, `K`, `V`) or descriptive prefix for complex (`TData`, `TResponse`, `TEntity`)
483
+ - Enums/const objects: UPPER_CASE keys — `STATUS.PENDING`, `Color.Red`
484
+ - Files: kebab-case — `user.service.ts`, `api-client.ts`
485
+
486
+ 2. **Exports:**
487
+ - Always export types using `export type` for type-only exports
488
+ - Use barrel exports (`index.ts`) per feature module
489
+ - Shared types in `src/types/` or `@/types`
490
+
491
+ 3. **Imports:**
492
+ - Group imports: (1) external packages, (2) internal absolute imports, (3) relative imports
493
+ - Type imports: `import type { User } from './types'` when only used in type position
494
+
495
+ 4. **File structure:**
496
+ ```
497
+ src/
498
+ types/ # Shared types
499
+ index.ts
500
+ user.types.ts
501
+ api.types.ts
502
+ users/
503
+ index.ts # Barrel export
504
+ user.service.ts
505
+ user.types.ts # Feature-specific types
506
+ ```
507
+
508
+ ## Common Patterns
509
+
510
+ ### 1. API Response Types
511
+
512
+ ```typescript
513
+ // Generic API response wrapper
514
+ type ApiResponse<T> = {
515
+ data: T;
516
+ meta: {
517
+ timestamp: number;
518
+ requestId: string;
519
+ };
520
+ };
521
+
522
+ type ApiError = {
523
+ error: {
524
+ code: string;
525
+ message: string;
526
+ details?: Record<string, unknown>;
527
+ };
528
+ };
529
+
530
+ type ApiResult<T> = ApiResponse<T> | ApiError;
531
+
532
+ // Type guard
533
+ function isApiError(response: ApiResult<unknown>): response is ApiError {
534
+ return 'error' in response;
535
+ }
536
+
537
+ // Usage
538
+ async function fetchUser(id: string): Promise<User> {
539
+ const response = await fetch(`/api/users/${id}`);
540
+ const json: ApiResult<User> = await response.json();
541
+
542
+ if (isApiError(json)) {
543
+ throw new AppError(json.error.code, json.error.message);
544
+ }
545
+
546
+ return json.data;
547
+ }
548
+ ```
549
+
550
+ ### 2. Builder Pattern with Fluent Interface
551
+
552
+ ```typescript
553
+ class QueryBuilder<T> {
554
+ private filters: Array<(item: T) => boolean> = [];
555
+ private sortFn?: (a: T, b: T) => number;
556
+ private limitValue?: number;
557
+
558
+ where(predicate: (item: T) => boolean): this {
559
+ this.filters.push(predicate);
560
+ return this;
561
+ }
562
+
563
+ sortBy(compareFn: (a: T, b: T) => number): this {
564
+ this.sortFn = compareFn;
565
+ return this;
566
+ }
567
+
568
+ limit(n: number): this {
569
+ this.limitValue = n;
570
+ return this;
571
+ }
572
+
573
+ execute(items: T[]): T[] {
574
+ let result = items.filter(item =>
575
+ this.filters.every(filter => filter(item))
576
+ );
577
+
578
+ if (this.sortFn) {
579
+ result = result.sort(this.sortFn);
580
+ }
581
+
582
+ if (this.limitValue !== undefined) {
583
+ result = result.slice(0, this.limitValue);
584
+ }
585
+
586
+ return result;
587
+ }
588
+ }
589
+
590
+ // Usage
591
+ const activeUsers = new QueryBuilder<User>()
592
+ .where(u => u.role === 'admin')
593
+ .where(u => u.isActive)
594
+ .sortBy((a, b) => a.name.localeCompare(b.name))
595
+ .limit(10)
596
+ .execute(allUsers);
597
+ ```
598
+
599
+ ### 3. Discriminated Union State Machine
600
+
601
+ ```typescript
602
+ type ConnectionState =
603
+ | { status: 'disconnected' }
604
+ | { status: 'connecting'; startedAt: number }
605
+ | { status: 'connected'; socket: WebSocket; connectedAt: number }
606
+ | { status: 'error'; error: Error; lastAttempt: number };
607
+
608
+ class Connection {
609
+ private state: ConnectionState = { status: 'disconnected' };
610
+
611
+ connect(): void {
612
+ if (this.state.status === 'connected') {
613
+ return; // Already connected
614
+ }
615
+
616
+ this.state = { status: 'connecting', startedAt: Date.now() };
617
+ // ... connection logic
618
+ }
619
+
620
+ handleConnected(socket: WebSocket): void {
621
+ if (this.state.status !== 'connecting') {
622
+ throw new Error('Cannot transition to connected from ' + this.state.status);
623
+ }
624
+
625
+ this.state = { status: 'connected', socket, connectedAt: Date.now() };
626
+ }
627
+
628
+ getStatus(): string {
629
+ switch (this.state.status) {
630
+ case 'disconnected':
631
+ return 'Not connected';
632
+ case 'connecting':
633
+ return `Connecting... (${Date.now() - this.state.startedAt}ms)`;
634
+ case 'connected':
635
+ return `Connected for ${Date.now() - this.state.connectedAt}ms`;
636
+ case 'error':
637
+ return `Error: ${this.state.error.message}`;
638
+ }
639
+ }
640
+ }
641
+ ```
642
+
643
+ ### 4. Mapped Types for Transformations
644
+
645
+ ```typescript
646
+ // Make all properties of T into functions that return T[K]
647
+ type Getters<T> = {
648
+ [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
649
+ };
650
+
651
+ // Make all properties of T into functions that accept T[K]
652
+ type Setters<T> = {
653
+ [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
654
+ };
655
+
656
+ type User = {
657
+ name: string;
658
+ age: number;
659
+ };
660
+
661
+ type UserGetters = Getters<User>;
662
+ // { getName: () => string; getAge: () => number; }
663
+
664
+ type UserSetters = Setters<User>;
665
+ // { setName: (value: string) => void; setAge: (value: number) => void; }
666
+
667
+ // Practical example: Create immutable update helpers
668
+ type UpdateFunctions<T> = {
669
+ [K in keyof T as `update${Capitalize<string & K>}`]: (value: T[K]) => T;
670
+ };
671
+
672
+ function createUpdateHelpers<T>(initial: T): UpdateFunctions<T> {
673
+ const helpers = {} as UpdateFunctions<T>;
674
+
675
+ for (const key in initial) {
676
+ const capitalizedKey = key.charAt(0).toUpperCase() + key.slice(1);
677
+ const helperName = `update${capitalizedKey}` as keyof UpdateFunctions<T>;
678
+
679
+ helpers[helperName] = ((value: T[typeof key]) => ({
680
+ ...initial,
681
+ [key]: value,
682
+ })) as UpdateFunctions<T>[typeof helperName];
683
+ }
684
+
685
+ return helpers;
686
+ }
687
+ ```
688
+
689
+ ### 5. Type-Safe Event Emitter
690
+
691
+ ```typescript
692
+ type EventMap = {
693
+ 'user:created': { user: User };
694
+ 'user:updated': { user: User; changes: Partial<User> };
695
+ 'user:deleted': { userId: string };
696
+ };
697
+
698
+ class TypedEventEmitter<T extends Record<string, unknown>> {
699
+ private listeners = new Map<keyof T, Set<(data: unknown) => void>>();
700
+
701
+ on<K extends keyof T>(event: K, handler: (data: T[K]) => void): void {
702
+ if (!this.listeners.has(event)) {
703
+ this.listeners.set(event, new Set());
704
+ }
705
+ this.listeners.get(event)!.add(handler as (data: unknown) => void);
706
+ }
707
+
708
+ emit<K extends keyof T>(event: K, data: T[K]): void {
709
+ const handlers = this.listeners.get(event);
710
+ if (handlers) {
711
+ handlers.forEach(handler => handler(data));
712
+ }
713
+ }
714
+
715
+ off<K extends keyof T>(event: K, handler: (data: T[K]) => void): void {
716
+ const handlers = this.listeners.get(event);
717
+ if (handlers) {
718
+ handlers.delete(handler as (data: unknown) => void);
719
+ }
720
+ }
721
+ }
722
+
723
+ // Usage (fully type-safe)
724
+ const emitter = new TypedEventEmitter<EventMap>();
725
+
726
+ emitter.on('user:created', ({ user }) => {
727
+ console.log(`User created: ${user.name}`);
728
+ });
729
+
730
+ emitter.on('user:updated', ({ user, changes }) => {
731
+ console.log(`User ${user.id} updated:`, changes);
732
+ });
733
+
734
+ emitter.emit('user:created', { user: newUser });
735
+ // emitter.emit('user:created', { wrong: 'data' }); // Type error!
736
+ ```
737
+
738
+ ## Anti-Patterns
739
+
740
+ ### 1. ❌ Using `any` Instead of `unknown`
741
+
742
+ ```typescript
743
+ // BAD: any disables all type checking
744
+ function processData(data: any): void {
745
+ data.something.that.might.not.exist(); // No error, crashes at runtime
746
+ }
747
+
748
+ // GOOD: unknown forces type validation
749
+ function processData(data: unknown): void {
750
+ if (typeof data === 'object' && data !== null && 'id' in data) {
751
+ console.log((data as { id: string }).id);
752
+ }
753
+ }
754
+
755
+ // ACCEPTABLE: any at system boundaries with immediate validation
756
+ async function fetchData(url: string): Promise<User> {
757
+ const response = await fetch(url);
758
+ const data: any = await response.json(); // External data = any
759
+
760
+ if (!isUser(data)) { // Immediately validate
761
+ throw new Error('Invalid user data');
762
+ }
763
+
764
+ return data; // Now type-safe
765
+ }
766
+ ```
767
+
768
+ ### 2. ❌ Type Assertions Instead of Narrowing
769
+
770
+ ```typescript
771
+ // BAD: Type assertion without validation
772
+ function getUser(data: unknown): User {
773
+ return data as User; // No runtime safety
774
+ }
775
+
776
+ const config = JSON.parse(configString) as Config; // Crashes if wrong shape
777
+
778
+ // GOOD: Type guard with validation
779
+ function getUser(data: unknown): User {
780
+ if (!isUser(data)) {
781
+ throw new Error('Invalid user data');
782
+ }
783
+ return data;
784
+ }
785
+
786
+ function parseConfig(configString: string): Config {
787
+ const parsed: unknown = JSON.parse(configString);
788
+
789
+ if (!isConfig(parsed)) {
790
+ throw new Error('Invalid config format');
791
+ }
792
+
793
+ return parsed;
794
+ }
795
+ ```
796
+
797
+ ### 3. ❌ Overly Complex Generic Chains
798
+
799
+ ```typescript
800
+ // BAD: Generic inception (>3 levels deep)
801
+ type DeepGeneric<
802
+ T extends Record<string, unknown>,
803
+ K extends keyof T,
804
+ V extends T[K],
805
+ R extends V extends Array<infer U> ? U : never
806
+ > = R extends object ? Partial<R> : never;
807
+
808
+ // GOOD: Break into named steps
809
+ type ArrayElement<T> = T extends Array<infer U> ? U : never;
810
+ type ObjectOrNever<T> = T extends object ? T : never;
811
+
812
+ type ExtractedItem<T, K extends keyof T> = ObjectOrNever<ArrayElement<T[K]>>;
813
+ type PartialItem<T, K extends keyof T> = Partial<ExtractedItem<T, K>>;
814
+
815
+ // Or just use concrete types if only used once
816
+ ```
817
+
818
+ ### 4. ❌ Ignoring Strict Mode Errors
819
+
820
+ ```typescript
821
+ // BAD: Disabling strict checks
822
+ {
823
+ "compilerOptions": {
824
+ "strict": false, // Defeats the purpose of TypeScript
825
+ "noImplicitAny": false
826
+ }
827
+ }
828
+
829
+ function doSomething(data) { // Implicit any
830
+ return data.value; // No safety
831
+ }
832
+
833
+ // GOOD: Enable strict mode and fix the issues
834
+ {
835
+ "compilerOptions": {
836
+ "strict": true
837
+ }
838
+ }
839
+
840
+ function doSomething(data: { value: string }): string {
841
+ return data.value;
842
+ }
843
+ ```
844
+
845
+ ### 5. ❌ Optional Properties Instead of Discriminated Unions
846
+
847
+ ```typescript
848
+ // BAD: Ambiguous optional fields
849
+ type Result = {
850
+ success: boolean;
851
+ data?: User;
852
+ error?: string;
853
+ };
854
+
855
+ function handle(result: Result): void {
856
+ if (result.success) {
857
+ console.log(result.data.name); // TypeScript doesn't know data exists!
858
+ }
859
+ }
860
+
861
+ // GOOD: Discriminated union
862
+ type Result =
863
+ | { success: true; data: User }
864
+ | { success: false; error: string };
865
+
866
+ function handle(result: Result): void {
867
+ if (result.success) {
868
+ console.log(result.data.name); // TypeScript knows data exists
869
+ } else {
870
+ console.error(result.error); // TypeScript knows error exists
871
+ }
872
+ }
873
+ ```
874
+
875
+ ## Integration Notes
876
+
877
+ ### Multi-Session Team Context
878
+
879
+ **If you are a worker in a multi-session team:**
880
+
881
+ 1. **CRITICAL:** Call `team_check_inbox()` BEFORE starting work to receive shared artifacts and messages from other workers
882
+ 2. **Read shared conventions:** Look for `shared-conventions` artifact containing naming conventions, enum values, response formats, error formats
883
+ 3. **TypeScript applies to ALL workers:** Backend, frontend, testing workers all follow these patterns
884
+ 4. **Publish shared types:** Create and publish `shared-types` artifact containing:
885
+ - Common interfaces (`User`, `ApiResponse<T>`, etc.)
886
+ - API request/response types
887
+ - Error types and error codes
888
+ - Enum/status value definitions
889
+ 5. **Types = contract:** Types published in `shared-types` become the contract between frontend and backend workers — changes must be coordinated
890
+ 6. **Broadcast completion:** Use `team_broadcast()` when you publish new types so other workers know to re-read the artifact
891
+ 7. **Use relative paths only:** Never use absolute paths in published artifacts
892
+
893
+ ### Artifact Publication Format
894
+
895
+ When publishing `shared-types` artifact:
896
+
897
+ ```typescript
898
+ // shared-types artifact data structure
899
+ {
900
+ "files": ["src/types/user.types.ts", "src/types/api.types.ts"],
901
+ "exports": {
902
+ "user.types": ["User", "CreateUserRequest", "UpdateUserRequest"],
903
+ "api.types": ["ApiResponse", "ApiError", "Result"]
904
+ },
905
+ "conventions": {
906
+ "enumValues": {
907
+ "UserRole": ["admin", "user", "guest"],
908
+ "Status": ["pending", "active", "completed"]
909
+ },
910
+ "errorCodes": ["VALIDATION_ERROR", "NOT_FOUND", "UNAUTHORIZED"]
911
+ }
912
+ }
913
+ ```
914
+
915
+ ### Coordinating with Other Workers
916
+
917
+ - **Backend worker:** Consumes `shared-types` for request/response types, error handling
918
+ - **Frontend worker:** Consumes `shared-types` for API client types, state management
919
+ - **Testing worker:** Consumes `shared-types` for test data factories, type assertions
920
+ - **Database worker:** Shares model types via `shared-types` that backend worker uses
921
+
922
+ ### Type Changes Workflow
923
+
924
+ 1. Identify type change needed (e.g., add field to `User`)
925
+ 2. Update type definition in `shared-types` artifact source
926
+ 3. Re-publish `shared-types` artifact
927
+ 4. Broadcast to team: "Updated User type — added `phoneNumber` field"
928
+ 5. Affected workers re-read artifact and update their implementations
929
+
930
+ ---
931
+
932
+ **Remember:** TypeScript is not just type checking — it's documentation, refactoring safety, and IDE autocomplete. Invest in types upfront, reap benefits throughout the project lifecycle.