Comprehensive TypeScript guidance covering compiler configuration, advanced types, utility types, type guards, strict mode workflows, and documentation patterns; use when configuring tsconfig, designing complex generics, making illegal states unrepresentable, fixing type errors, or writing testable and maintainable type-safe APIs.
Overall
score
99%
Does it follow best practices?
Validation for skill structure
Use the type system to prevent invalid states at compile time. If an invalid combination of values cannot be represented in the type system, it cannot occur at runtime.
Bad design allows invalid states:
interface RequestState {
loading: boolean;
data?: string;
error?: Error;
}
// All of these are possible but invalid:
const invalid1: RequestState = { loading: true, data: 'result', error: new Error() };
const invalid2: RequestState = { loading: false }; // No data and no errorGood design makes invalid states unrepresentable:
type RequestState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: string }
| { status: 'error'; error: Error };
// Compiler prevents invalid combinations
function processRequest(state: RequestState) {
if (state.status === 'success') {
console.log(state.data); // ✓ data is always available
}
if (state.status === 'error') {
console.log(state.error); // ✓ error is always available
}
}Use a discriminant field to distinguish between union variants:
type PaymentMethod =
| { type: 'credit_card'; cardNumber: string; cvv: string }
| { type: 'paypal'; email: string }
| { type: 'bank_transfer'; accountNumber: string; routingNumber: string };
function processPayment(method: PaymentMethod) {
switch (method.type) {
case 'credit_card':
// TypeScript knows cardNumber and cvv are available
return chargeCreditCard(method.cardNumber, method.cvv);
case 'paypal':
// TypeScript knows email is available
return chargePaypal(method.email);
case 'bank_transfer':
// TypeScript knows accountNumber and routingNumber are available
return chargeBankTransfer(method.accountNumber, method.routingNumber);
}
}Prevent mixing incompatible IDs:
type UserId = string & { readonly __brand: 'UserId' };
type OrderId = string & { readonly __brand: 'OrderId' };
function getUser(id: UserId): Promise<User> { /* ... */ }
function getOrder(id: OrderId): Promise<Order> { /* ... */ }
// Constructor functions enforce validation
function createUserId(id: string): UserId {
if (!id.match(/^user_[0-9a-f]+$/)) {
throw new Error('Invalid user ID format');
}
return id as UserId;
}
function createOrderId(id: string): OrderId {
if (!id.match(/^order_[0-9a-f]+$/)) {
throw new Error('Invalid order ID format');
}
return id as OrderId;
}
const userId = createUserId('user_123');
const orderId = createOrderId('order_456');
getUser(userId); // ✓ OK
getUser(orderId); // ✗ Compile error: can't pass OrderId to UserId parameterBe explicit about what fields are required in different contexts:
// Creation: email and name required
type CreateUser = {
email: string;
name: string;
};
// Update: all fields optional
type UpdateUser = Partial<CreateUser>;
// Database row: all fields present including generated ones
type User = CreateUser & {
id: string;
createdAt: Date;
updatedAt: Date;
};
// Public API response: no sensitive fields
type UserResponse = Omit<User, 'email'>;Keep arrays and types in sync automatically:
const ROLES = ['admin', 'user', 'guest'] as const;
type Role = typeof ROLES[number]; // 'admin' | 'user' | 'guest'
// Adding a role to the array automatically adds it to the type
const EXTENDED_ROLES = [...ROLES, 'moderator'] as const;
type ExtendedRole = typeof EXTENDED_ROLES[number];
// Type guard based on const array
function isValidRole(role: string): role is Role {
return ROLES.includes(role as Role);
}Use never to catch unhandled cases at compile time:
type Status = 'active' | 'inactive' | 'pending';
function processStatus(status: Status): string {
switch (status) {
case 'active':
return 'processing';
case 'inactive':
return 'skipped';
case 'pending':
return 'queued';
default: {
// If we add a new status and forget to handle it,
// this will be a compile error
const _exhaustive: never = status;
throw new Error(`unhandled status: ${_exhaustive}`);
}
}
}Bad: Allows invalid states
interface FormState {
pristine: boolean;
dirty: boolean;
submitting: boolean;
submitted: boolean;
valid: boolean;
errors?: Record<string, string>;
}
// Invalid: both pristine and dirty can be true
// Invalid: can be submitting and submitted
// Invalid: can be valid but have errorsGood: Only valid states possible
type FormState =
| { status: 'pristine' }
| { status: 'editing'; errors?: Record<string, string> }
| { status: 'submitting'; errors?: Record<string, string> }
| { status: 'submitted'; result: SubmitResult }
| { status: 'error'; error: Error };
function handleSubmit(state: FormState) {
if (state.status === 'submitting' || state.status === 'submitted') {
// Can't submit while already submitting or after submitted
return;
}
if (state.status === 'editing' && state.errors) {
// Can't submit with validation errors
return;
}
// Submit logic
}Install with Tessl CLI
npx tessl i pantheon-ai/typescript-advanced@0.1.1references