typescript
Use when writing TypeScript code. Triggers on "TypeScript", "interface", "type", "generic", "type guard", "discriminated union", "strict mode", "any type", or TypeScript type questions.
Installation
Follow the repository instructions for this skill and your agent. Review the files and dependencies before adding it to your workflow.
View source ↗GuidesSkill instructions
TypeScript Best Practices
Core TypeScript Rules
- Use strict type checking
- Prefer type inference when the type is obvious
- Avoid the
anytype; useunknownwhen type is uncertain - Use meaningful variable and function names
- Use interfaces for type definitions
- Use optional chaining and nullish coalescing
- Validate inputs when necessary
- No console.logs or debug code
- Handle edge cases gracefully
- Provide meaningful error messages
Type Safety Requirements
- TypeScript strict mode enabled
- No
anytypes (useunknownif truly needed) - Use discriminated unions for complex types
- Define clear interfaces for all public APIs
Example: Discriminated Unions
// Good: Type-safe message handling
export type ChatMessage =
| { role: 'user'; content: string; timestamp: Date }
| { role: 'assistant'; content: string; timestamp: Date; model?: string }
| { role: 'system'; content: string; timestamp: Date };
// Usage
function processMessage(message: ChatMessage) {
switch (message.role) {
case 'user':
// TypeScript knows this is a user message
return handleUserMessage(message.content);
case 'assistant':
// TypeScript knows model is available here
return handleAssistantMessage(message.content, message.model);
case 'system':
return handleSystemMessage(message.content);
}
}
Example: Interface Definitions
// Public API interfaces
export interface ChatComponentInputs {
messages: ChatMessage[];
loading?: boolean;
placeholder?: string;
customClasses?: string;
}
export interface ChatComponentOutputs {
messageSubmit: EventEmitter<string>;
messageCopy: EventEmitter<string>;
}
// Internal types
interface ComponentState {
isStreaming: boolean;
currentMessage: string;
error: Error | null;
}
Example: Type Guards
// Type guard functions
export function isUserMessage(message: ChatMessage): message is UserMessage {
return message.role === 'user';
}
export function isAssistantMessage(
message: ChatMessage
): message is AssistantMessage {
return message.role === 'assistant';
}
// Usage
if (isUserMessage(message)) {
// TypeScript knows message.role is 'user'
console.log(message.content);
}
Example: Generic Utilities
// Reusable generic types
export type Nullable<T> = T | null;
export type Optional<T> = T | undefined;
export type AsyncResult<T> = Promise<T | Error>;
// Readonly wrapper
export type ReadonlyDeep<T> = {
readonly [P in keyof T]: T[P] extends object ? ReadonlyDeep<T[P]> : T[P];
};
Naming Conventions
// Interfaces: PascalCase
interface ChatMessage {}
// Types: PascalCase
type MessageRole = 'user' | 'assistant' | 'system';
// Classes: PascalCase
class ChatService {}
// Variables: camelCase
const messageList = [];
const isLoading = false;
// Constants: UPPER_SNAKE_CASE or camelCase
const MAX_MESSAGE_LENGTH = 1000;
const defaultConfig = {};
// Functions: camelCase
function processMessage() {}
// Enums: PascalCase for enum, UPPER_CASE for values
enum MessageType {
USER = 'USER',
ASSISTANT = 'ASSISTANT',
SYSTEM = 'SYSTEM',
}
Optional Chaining and Nullish Coalescing
// Optional chaining
const messageContent = conversation?.messages?.[0]?.content;
// Nullish coalescing (use ?? instead of ||)
const displayName = user.name ?? 'Anonymous';
const timeout = config.timeout ?? 5000;
// Combined
const firstMessage = conversation?.messages?.[0]?.content ?? 'No messages';
Error Handling
// Type-safe error handling
export class ChatError extends Error {
constructor(
message: string,
public code: 'NETWORK_ERROR' | 'VALIDATION_ERROR' | 'UNKNOWN_ERROR',
public details?: unknown
) {
super(message);
this.name = 'ChatError';
}
}
// Usage
try {
await sendMessage(content);
} catch (error) {
if (error instanceof ChatError) {
// Handle specific error
console.error(`Chat error: ${error.code}`, error.details);
} else if (error instanceof Error) {
// Handle generic error
console.error(error.message);
} else {
// Unknown error
console.error('An unknown error occurred');
}
}
Function Type Definitions
// Function types
type MessageHandler = (message: ChatMessage) => void;
type AsyncMessageHandler = (message: ChatMessage) => Promise<void>;
type MessageTransformer = (message: string) => string;
// Generic function types
type Comparator<T> = (a: T, b: T) => number;
type Predicate<T> = (value: T) => boolean;
type Mapper<T, U> = (value: T) => U;
Utility Type Usage
// Pick specific properties
type MessagePreview = Pick<ChatMessage, 'role' | 'content'>;
// Omit specific properties
type MessageWithoutTimestamp = Omit<ChatMessage, 'timestamp'>;
// Partial (all properties optional)
type PartialConfig = Partial<ChatConfig>;
// Required (all properties required)
type RequiredConfig = Required<ChatConfig>;
// Readonly
type ImmutableMessage = Readonly<ChatMessage>;
// Record
type MessageMap = Record<string, ChatMessage>;
// Extract union members
type UserOrAssistant = Extract<MessageRole, 'user' | 'assistant'>;
// Exclude union members
type NonSystemRole = Exclude<MessageRole, 'system'>;
Template Literal Types
// Type-safe event names
type EventName = `on${Capitalize<'message' | 'error' | 'complete'>}`;
// Results in: 'onMessage' | 'onError' | 'onComplete'
// Type-safe CSS classes
type Variant = 'primary' | 'secondary' | 'danger';
type Size = 'sm' | 'md' | 'lg';
type ButtonClass = `ai-button-${Variant}-${Size}`;
JSDoc Comments for Public APIs
/**
* Displays a chat message with appropriate styling based on the role.
*
* @example
* ```typescript
* <ai-message-bubble
* [message]="message"
* [showAvatar]="true"
* (copy)="handleCopy($event)" />
* ```
*/
@Component({
selector: 'ai-message-bubble',
})
export class MessageBubbleComponent {
/**
* The chat message to display.
*/
message = input.required<ChatMessage>();
/**
* Whether to show the user/assistant avatar.
* @default false
*/
showAvatar = input(false);
/**
* Emitted when the user clicks the copy button.
*/
copy = output<string>();
}
Avoid Common Pitfalls
// Don't use any
function processData(data: any) {
return data.value;
}
// Use unknown and type guards
function processData(data: unknown) {
if (typeof data === 'object' && data !== null && 'value' in data) {
return (data as { value: string }).value;
}
throw new Error('Invalid data format');
}
// Don't use non-null assertion unless absolutely necessary
const element = document.querySelector('.chat')!;
// Handle null case
const element = document.querySelector('.chat');
if (element) {
// Use element
}
// Don't use type assertions without reason
const message = data as ChatMessage;
// Use type guards
if (isChatMessage(data)) {
const message = data;
}
Examples
Read the usage examples in the source repository.
View source ↗Source history
- Indexed on
- 2026-03-11
- Index updated
- 2026-03-11
- Branch
- main
Index and sync dates describe this directory. View the source repository for code history.
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!