TypeScript coding standards covering strict type system, advanced types, decorators, generics, and best practices for type-safe applications. Use for TypeScript projects requiring robust type safety and maintainable code.
Quick Navigation: Level 1: Quick Start (5 min) → Level 2: Implementation (30 min) → Level 3: Mastery (Extended)
any type; use unknown for truly dynamic data/**
* Authentication service with type-safe error handling.
*/
import { hash, compare } from 'bcrypt';
import jwt from 'jsonwebtoken';
// Type definitions
interface User {
readonly id: string;
readonly username: string;
readonly email: string;
readonly passwordHash: string;
}
interface AuthResult<T> {
readonly success: boolean;
readonly data?: T;
readonly error?: Error;
}
// Custom errors
class AuthError extends Error {
constructor(message: string, public readonly code: string) {
super(message);
this.name = 'AuthError';
}
}
// Service implementation
class AuthService {
constructor(
private readonly repository: UserRepository,
private readonly jwtSecret: string
) {}
async authenticate(username: string, password: string): Promise<AuthResult<{ token: string }>> {
if (!username || !password) {
return { success: false, error: new AuthError('Credentials required', 'VALIDATION') };
}
const user = await this.repository.findByUsername(username);
if (!user || !await compare(password, user.passwordHash)) {
return { success: false, error: new AuthError('Invalid credentials', 'AUTH_FAILED') };
}
const token = jwt.sign({ userId: user.id, username: user.username }, this.jwtSecret, {
expiresIn: '1h',
});
return { success: true, data: { token } };
}
async hashPassword(password: string): Promise<string> {
return hash(password, 10);
}
}
Basic Types and Type Inference
// ✅ Good: Type inference when obvious
const username = 'alice'; // inferred as string
const count = 42; // inferred as number
const items = [1, 2, 3]; // inferred as number[]
// ✅ Good: Explicit types for clarity
function calculateTotal(items: number[]): number {
return items.reduce((sum, item) => sum + item, 0);
}
// ❌ Bad: Unnecessary explicit types
const name: string = 'bob'; // Unnecessary, inference is clear
// ✅ Good: Union types for multiple possibilities
type Status = 'pending' | 'approved' | 'rejected';
let orderStatus: Status = 'pending';
// ✅ Good: Intersection types for composition
type Timestamped = { createdAt: Date; updatedAt: Date };
type User = { id: string; name: string };
type TimestampedUser = User & Timestamped;
// ✅ Good: Readonly for immutability
interface Config {
readonly apiUrl: string;
readonly timeout: number;
}
const config: Readonly<Config> = {
apiUrl: 'https://api.example.com',
timeout: 5000,
};
Type Guards and Narrowing
// Type guard functions
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function isUser(obj: unknown): obj is User {
return (
typeof obj === 'object' &&
obj !== null &&
'id' in obj &&
'username' in obj
);
}
// Using type guards
function processValue(value: string | number) {
if (typeof value === 'string') {
// value is string here
return value.toUpperCase();
}
// value is number here
return value.toFixed(2);
}
// Discriminated unions
type Success<T> = { success: true; data: T };
type Failure = { success: false; error: Error };
type Result<T> = Success<T> | Failure;
function handleResult<T>(result: Result<T>): T | null {
if (result.success) {
// TypeScript knows result.data exists
return result.data;
}
// TypeScript knows result.error exists
console.error(result.error);
return null;
}
Utility Types and Type Manipulation
// Partial - all properties optional
interface User {
id: string;
name: string;
email: string;
age: number;
}
type PartialUser = Partial<User>;
// { id?: string; name?: string; email?: string; age?: number }
// Required - all properties required
type RequiredUser = Required<PartialUser>;
// Pick - select specific properties
type UserCredentials = Pick<User, 'id' | 'email'>;
// { id: string; email: string }
// Omit - exclude specific properties
type UserWithoutId = Omit<User, 'id'>;
// { name: string; email: string; age: number }
// Record - create object type with specific keys
type UserRoles = Record<string, 'admin' | 'user' | 'guest'>;
const roles: UserRoles = {
'alice': 'admin',
'bob': 'user',
};
// Mapped types
type Nullable<T> = { [P in keyof T]: T[P] | null };
type NullableUser = Nullable<User>;
// { id: string | null; name: string | null; ... }
// Conditional types
type IsArray<T> = T extends Array<infer U> ? U : never;
type StringArray = IsArray<string[]>; // string
type NotArray = IsArray<string>; // never
// Template literal types
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Endpoint = `/api/${string}`;
type Route = `${HTTPMethod} ${Endpoint}`;
// Examples: "GET /api/users", "POST /api/products"
Advanced Type Patterns
// Branded types for type safety
type UserId = string & { readonly __brand: 'UserId' };
type OrderId = string & { readonly __brand: 'OrderId' };
function createUserId(id: string): UserId {
return id as UserId;
}
function getUserById(id: UserId): User {
// Type-safe: only UserId can be passed
return { id, name: 'User' } as User;
}
// const userId = createUserId('123');
// const orderId = '456' as OrderId;
// getUserById(orderId); // Error: Type 'OrderId' is not assignable to 'UserId'
// Builder pattern with type safety
class UserBuilder {
private user: Partial<User> = {};
setId(id: string): this {
this.user.id = id;
return this;
}
setName(name: string): this {
this.user.name = name;
return this;
}
setEmail(email: string): this {
this.user.email = email;
return this;
}
build(): User {
if (!this.user.id || !this.user.name || !this.user.email) {
throw new Error('Missing required fields');
}
return this.user as User;
}
}
const user = new UserBuilder()
.setId('123')
.setName('Alice')
.setEmail('alice@example.com')
.build();
Generic Functions and Classes
// Generic function with constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: '1', name: 'Alice', age: 30 };
const name = getProperty(user, 'name'); // type: string
const age = getProperty(user, 'age'); // type: number
// Generic class with multiple type parameters
class Repository<T extends { id: string }> {
private items: Map<string, T> = new Map();
save(item: T): void {
this.items.set(item.id, item);
}
findById(id: string): T | undefined {
return this.items.get(id);
}
findAll(): T[] {
return Array.from(this.items.values());
}
delete(id: string): boolean {
return this.items.delete(id);
}
}
// Generic with default type parameter
interface ApiResponse<T = unknown> {
data: T;
status: number;
message: string;
}
// Generic constraints
interface Identifiable {
id: string;
}
function findById<T extends Identifiable>(items: T[], id: string): T | null {
return items.find(item => item.id === id) ?? null;
}
// Conditional generic types
type AsyncReturnType<T extends (...args: any[]) => any> =
T extends (...args: any[]) => Promise<infer R> ? R : never;
async function fetchUser(): Promise<User> {
return { id: '1', name: 'Alice' } as User;
}
type UserType = AsyncReturnType<typeof fetchUser>; // User
Class and Method Decorators
// Method decorator for logging
function Log(target: any, propertyName: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
console.log(`Calling ${propertyName} with args:`, args);
const result = await originalMethod.apply(this, args);
console.log(`${propertyName} returned:`, result);
return result;
};
return descriptor;
}
// Property decorator for validation
function MinLength(length: number) {
return function (target: any, propertyName: string) {
let value: string;
const getter = () => value;
const setter = (newValue: string) => {
if (newValue.length < length) {
throw new Error(`${propertyName} must be at least ${length} characters`);
}
value = newValue;
};
Object.defineProperty(target, propertyName, {
get: getter,
set: setter,
enumerable: true,
configurable: true,
});
};
}
// Class decorator for metadata
function Entity(tableName: string) {
return function <T extends { new(...args: any[]): {} }>(constructor: T) {
return class extends constructor {
tableName = tableName;
};
};
}
// Usage
@Entity('users')
class UserEntity {
@MinLength(3)
username: string = '';
@Log
async save(): Promise<void> {
console.log('Saving user...');
}
}
Jest with TypeScript
// user.service.test.ts
import { describe, test, expect, beforeEach, jest } from '@jest/globals';
import { AuthService, UserRepository, AuthError } from './auth.service';
import type { User } from './types';
// Mock repository
const mockRepository: jest.Mocked<UserRepository> = {
findByUsername: jest.fn(),
create: jest.fn(),
};
describe('AuthService', () => {
let authService: AuthService;
beforeEach(() => {
jest.clearAllMocks();
authService = new AuthService(mockRepository, 'test-secret');
});
describe('authenticate', () => {
test('should authenticate valid credentials', async () => {
// Arrange
const mockUser: User = {
id: '1',
username: 'testuser',
email: 'test@example.com',
passwordHash: await authService.hashPassword('password123'),
createdAt: new Date(),
};
mockRepository.findByUsername.mockResolvedValue(mockUser);
// Act
const result = await authService.authenticate('testuser', 'password123');
// Assert
expect(result.success).toBe(true);
expect(result.data).toBeDefined();
expect(result.data?.token).toBeTruthy();
expect(mockRepository.findByUsername).toHaveBeenCalledWith('testuser');
});
test('should reject invalid password', async () => {
// Arrange
const mockUser: User = {
id: '1',
username: 'testuser',
email: 'test@example.com',
passwordHash: await authService.hashPassword('correct_password'),
createdAt: new Date(),
};
mockRepository.findByUsername.mockResolvedValue(mockUser);
// Act
const result = await authService.authenticate('testuser', 'wrong_password');
// Assert
expect(result.success).toBe(false);
expect(result.error).toBeInstanceOf(AuthError);
expect(result.error?.code).toBe('AUTH_FAILED');
});
test('should handle empty credentials', async () => {
// Act
const result = await authService.authenticate('', '');
// Assert
expect(result.success).toBe(false);
expect(result.error?.name).toBe('ValidationError');
});
// Parameterized tests
test.each([
['', 'password', 'empty username'],
['username', '', 'empty password'],
['', '', 'both empty'],
])('should reject %s', async (username, password, _description) => {
const result = await authService.authenticate(username, password);
expect(result.success).toBe(false);
});
});
});
Type Testing with tsd
// types.test-d.ts - Type-level tests
import { expectType, expectError } from 'tsd';
import type { User, AuthResult, TokenPayload } from './auth.service';
// Test type inference
const user: User = {
id: '1',
username: 'alice',
email: 'alice@example.com',
passwordHash: 'hash',
createdAt: new Date(),
};
expectType<User>(user);
// Test readonly properties
expectError(user.id = '2'); // Should error: readonly property
// Test discriminated unions
const success: AuthResult<string> = { success: true, data: 'token' };
const failure: AuthResult<string> = { success: false, error: new Error() };
if (success.success) {
expectType<string>(success.data); // data should exist
}
if (!failure.success) {
expectType<Error | undefined>(failure.error); // error should exist
}
Type-Safe Error Handling
// Result type for operations that can fail
type Result<T, E = Error> =
| { success: true; value: T }
| { success: false; error: E };
// Async result wrapper
async function tryCatch<T>(
promise: Promise<T>
): Promise<Result<T>> {
try {
const value = await promise;
return { success: true, value };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error : new Error(String(error)),
};
}
}
// Custom error types with additional context
class ValidationError extends Error {
constructor(
message: string,
public readonly field: string,
public readonly value: unknown
) {
super(message);
this.name = 'ValidationError';
}
}
class DatabaseError extends Error {
constructor(
message: string,
public readonly operation: string,
public readonly cause?: Error
) {
super(message);
this.name = 'DatabaseError';
}
}
// Error handling with exhaustiveness checking
function handleError(error: ValidationError | DatabaseError | Error): string {
if (error instanceof ValidationError) {
return `Validation failed for ${error.field}: ${error.message}`;
}
if (error instanceof DatabaseError) {
return `Database operation ${error.operation} failed: ${error.message}`;
}
if (error instanceof Error) {
return `Unexpected error: ${error.message}`;
}
// Exhaustiveness check - TypeScript will error if we miss a case
const _exhaustive: never = error;
return _exhaustive;
}
TSConfig Strict Mode (see resources/configs/tsconfig.json)
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"lib": ["ES2022"],
"moduleResolution": "bundler",
"strict": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"noImplicitAny": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
}
}
ESLint Configuration (see resources/configs/.eslintrc.typescript.json)
# Setup
npm init -y
npm install --save-dev typescript @types/node
npx tsc --init
# Type checking
npx tsc --noEmit
npx tsc --watch --noEmit
# Linting
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
npx eslint --fix src/
# Testing
npm install --save-dev jest @types/jest ts-jest
npm test
npm test -- --coverage
# Build
npx tsc
npx tsc --build --clean
# Type testing
npm install --save-dev tsd
npx tsd
When implementing authentication and security features, follow NIST 800-53r5 controls:
// @nist ia-2 "User authentication"
// @nist ia-5 "Authenticator management"
async function authenticateUser(credentials: Credentials): Promise<User> {
// Implementation with MFA support
}
// @nist ac-3 "Access enforcement"
// @nist ac-6 "Least privilege"
function checkPermissions(user: User, resource: Resource): boolean {
// Role-based access control
}
// @nist sc-8 "Transmission confidentiality"
// @nist sc-13 "Cryptographic protection"
function encryptSensitiveData(data: string): string {
// Encryption implementation
}
See NIST Implementation Guide for complete guidance.
// TODO: Add basic example for typescript
// This example demonstrates core functionality
// TODO: Add advanced example for typescript
// This example shows production-ready patterns
// TODO: Add integration example showing how typescript
// works with other systems and services
See examples/typescript/ for complete working examples.
This skill integrates with:
Problem: Not testing edge cases and error conditions leads to production bugs
Solution: Implement comprehensive test coverage including:
Prevention: Enforce minimum code coverage (80%+) in CI/CD pipeline
Problem: Hardcoding values makes applications inflexible and environment-dependent
Solution: Use environment variables and configuration management:
Prevention: Use tools like dotenv, config validators, and secret scanners
Problem: Security vulnerabilities from not following established security patterns
Solution: Follow security guidelines:
Prevention: Use security linters, SAST tools, and regular dependency updates
Best Practices:
This skill has been validated with:
npx skills add williamzujkowski/typescript-coding-standards下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer