AgentSkillsCN

Type Safety

类型安全

SKILL.md

Type Safety Skill

Ensuring runtime and compile-time type safety in TypeScript

🎯 STRICT MODE CONFIGURATION

Recommended tsconfig.json

json
{
  "compilerOptions": {
    // Enable all strict checks
    "strict": true,
    
    // Individual strict flags (all included in strict)
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "useUnknownInCatchVariables": true,
    "alwaysStrict": true,
    
    // Additional safety checks
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    
    // Module safety
    "isolatedModules": true,
    "verbatimModuleSyntax": true
  }
}

Key Strict Options Explained

OptionEffect
strictNullChecksnull and undefined are distinct types
noImplicitAnyError on inferred any type
noUncheckedIndexedAccessArray/object index returns T | undefined
exactOptionalPropertyTypes?: means missing, not undefined

🛡️ RUNTIME VALIDATION

Using Zod

typescript
import { z } from 'zod';

// Define schema
const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().int().positive().optional(),
  role: z.enum(['user', 'admin', 'moderator']),
  createdAt: z.date(),
});

// Infer type from schema
type User = z.infer<typeof UserSchema>;

// Validate at runtime
function createUser(input: unknown): User {
  return UserSchema.parse(input); // Throws on invalid
}

// Safe validation
function tryCreateUser(input: unknown): User | null {
  const result = UserSchema.safeParse(input);
  return result.success ? result.data : null;
}

Using Valibot (Lighter Alternative)

typescript
import * as v from 'valibot';

const UserSchema = v.object({
  id: v.pipe(v.string(), v.uuid()),
  name: v.pipe(v.string(), v.minLength(1), v.maxLength(100)),
  email: v.pipe(v.string(), v.email()),
  role: v.picklist(['user', 'admin', 'moderator']),
});

type User = v.InferOutput<typeof UserSchema>;

// Validate
const result = v.safeParse(UserSchema, input);
if (result.success) {
  const user = result.output; // Type-safe User
}

🔒 UNKNOWN VS ANY

typescript
// ❌ any - No type checking
function processAny(data: any) {
  // All these compile but may crash at runtime
  data.foo.bar.baz();
  data.nonexistent();
  const x: number = data; // No error!
}

// ✅ unknown - Requires narrowing
function processUnknown(data: unknown) {
  // data.foo; // ❌ Error: Object is of type 'unknown'
  
  if (typeof data === 'string') {
    return data.toUpperCase(); // ✅ Narrowed to string
  }
  
  if (isUser(data)) {
    return data.name; // ✅ Narrowed to User
  }
  
  throw new Error('Unexpected data type');
}

♾️ NEVER TYPE

Exhaustive Checks

typescript
type Status = 'pending' | 'active' | 'completed';

function handleStatus(status: Status): string {
  switch (status) {
    case 'pending':
      return 'Waiting...';
    case 'active':
      return 'In progress';
    case 'completed':
      return 'Done!';
    default:
      // TypeScript will error if we miss a case
      const _exhaustive: never = status;
      throw new Error(`Unhandled status: ${_exhaustive}`);
  }
}

// Utility for exhaustive checks
function assertNever(value: never, message?: string): never {
  throw new Error(message ?? `Unexpected value: ${value}`);
}

Never in Conditional Types

typescript
// Filter type that removes certain types
type Filter<T, U> = T extends U ? never : T;

type NonString = Filter<string | number | boolean, string>;
// Result: number | boolean

// Extract only function properties
type FunctionKeys<T> = {
  [K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];

✅ ASSERTION FUNCTIONS

typescript
// Assert condition
function assert(
  condition: unknown, 
  message?: string
): asserts condition {
  if (!condition) {
    throw new Error(message ?? 'Assertion failed');
  }
}

// Assert specific type
function assertIsString(
  value: unknown
): asserts value is string {
  if (typeof value !== 'string') {
    throw new TypeError('Expected string');
  }
}

// Usage
function processData(data: unknown) {
  assertIsString(data);
  // data is now string - TypeScript knows!
  return data.toUpperCase();
}

🔄 BRANDED TYPES

typescript
// Create distinct types for same underlying type
declare const brand: unique symbol;

type Brand<T, B extends string> = T & { [brand]: B };

// Branded string types
type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;

// Factory functions
function createUserId(id: string): UserId {
  // Validate format
  if (!id.match(/^usr_/)) {
    throw new Error('Invalid user ID format');
  }
  return id as UserId;
}

function createOrderId(id: string): OrderId {
  if (!id.match(/^ord_/)) {
    throw new Error('Invalid order ID format');
  }
  return id as OrderId;
}

// Can't mix them up!
function getUser(id: UserId) { /*...*/ }

const userId = createUserId('usr_123');
const orderId = createOrderId('ord_456');

getUser(userId);  // ✅ OK
// getUser(orderId); // ❌ Error: OrderId not assignable to UserId

📦 DISCRIMINATED UNION SAFETY

typescript
// Ensure exhaustive handling
type ApiResponse<T> =
  | { type: 'loading' }
  | { type: 'success'; data: T }
  | { type: 'error'; error: Error };

function handleResponse<T>(
  response: ApiResponse<T>,
  handlers: {
    onLoading: () => void;
    onSuccess: (data: T) => void;
    onError: (error: Error) => void;
  }
): void {
  switch (response.type) {
    case 'loading':
      return handlers.onLoading();
    case 'success':
      return handlers.onSuccess(response.data);
    case 'error':
      return handlers.onError(response.error);
    default:
      assertNever(response);
  }
}

🎯 CONST ASSERTIONS

typescript
// Without const assertion
const routes = {
  home: '/',
  about: '/about',
  users: '/users',
};
// Type: { home: string; about: string; users: string }

// With const assertion
const routes = {
  home: '/',
  about: '/about',
  users: '/users',
} as const;
// Type: { readonly home: '/'; readonly about: '/about'; readonly users: '/users' }

// Array const assertion
const statuses = ['pending', 'active', 'done'] as const;
// Type: readonly ['pending', 'active', 'done']

type Status = typeof statuses[number];
// Type: 'pending' | 'active' | 'done'

⚠️ COMMON TYPE ERRORS & FIXES

Error: Object is possibly undefined

typescript
// ❌ Problem
const user = users.find(u => u.id === id);
console.log(user.name); // Error!

// ✅ Solution 1: Optional chaining
console.log(user?.name);

// ✅ Solution 2: Guard clause
if (!user) throw new Error('User not found');
console.log(user.name);

Error: Type 'X' is not assignable to type 'Y'

typescript
// ❌ Problem
interface Cat { meow(): void }
interface Dog { bark(): void }
const pet: Cat = getDog(); // Error!

// ✅ Solution: Use union or proper type
const pet: Cat | Dog = getPet();
if ('meow' in pet) {
  pet.meow();
}

Error: Argument of type 'string' is not assignable to type 'X'

typescript
// ❌ Problem
type Color = 'red' | 'blue' | 'green';
const input: string = getInput();
const color: Color = input; // Error!

// ✅ Solution: Type guard
function isColor(value: string): value is Color {
  return ['red', 'blue', 'green'].includes(value);
}

if (isColor(input)) {
  const color: Color = input; // ✅
}

📎 QUICK REFERENCE

PatternUse Case
unknownExternal data, user input
neverExhaustive checks, impossible states
Zod/ValibotRuntime validation with type inference
Branded typesPrevent ID/string mixups
as constLiteral types, readonly objects
Assertion functionsNarrow types with validation

🔗 RELATED SKILLS