| name | typescript |
| description | Use when writing TypeScript code. Triggers on "TypeScript", "interface", "type", "generic", "type guard", "discriminated union", "strict mode", "any type", or TypeScript type questions. |
| allowed-tools | ["Read","Edit","Write","Glob","Grep"] |
TypeScript Best Practices
Core TypeScript Rules
- Use strict type checking
- Prefer type inference when the type is obvious
- Avoid the
any type; use unknown when 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
any types (use unknown if truly needed)
- Use discriminated unions for complex types
- Define clear interfaces for all public APIs
Example: Discriminated Unions
export type ChatMessage =
| { role: 'user'; content: string; timestamp: Date }
| { role: 'assistant'; content: string; timestamp: Date; model?: string }
| { role: 'system'; content: string; timestamp: Date };
function processMessage(message: ChatMessage) {
switch (message.role) {
case 'user':
return handleUserMessage(message.content);
case 'assistant':
return handleAssistantMessage(message.content, message.model);
case 'system':
return handleSystemMessage(message.content);
}
}
Example: Interface Definitions
export interface ChatComponentInputs {
messages: ChatMessage[];
loading?: boolean;
placeholder?: string;
customClasses?: string;
}
export interface ChatComponentOutputs {
messageSubmit: EventEmitter<string>;
messageCopy: EventEmitter<string>;
}
interface ComponentState {
isStreaming: boolean;
currentMessage: string;
error: Error | null;
}
Example: Type Guards
export function isUserMessage(message: ChatMessage): message is UserMessage {
return message.role === 'user';
}
export function isAssistantMessage(
message: ChatMessage
): message is AssistantMessage {
return message.role === 'assistant';
}
if (isUserMessage(message)) {
console.log(message.content);
}
Example: Generic Utilities
export type Nullable<T> = T | null;
export type Optional<T> = T | undefined;
export type AsyncResult<T> = Promise<T | Error>;
export type ReadonlyDeep<T> = {
readonly [P in keyof T]: T[P] extends object ? ReadonlyDeep<T[P]> : T[P];
};
Naming Conventions
interface ChatMessage {}
type MessageRole = 'user' | 'assistant' | 'system';
class ChatService {}
const messageList = [];
const isLoading = false;
const MAX_MESSAGE_LENGTH = 1000;
const defaultConfig = {};
function processMessage() {}
enum MessageType {
USER = 'USER',
ASSISTANT = 'ASSISTANT',
SYSTEM = 'SYSTEM',
}
Optional Chaining and Nullish Coalescing
const messageContent = conversation?.messages?.[0]?.content;
const displayName = user.name ?? 'Anonymous';
const timeout = config.timeout ?? 5000;
const firstMessage = conversation?.messages?.[0]?.content ?? 'No messages';
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';
}
}
try {
await sendMessage(content);
} catch (error) {
if (error instanceof ChatError) {
console.error(`Chat error: ${error.code}`, error.details);
} else if (error instanceof Error) {
console.error(error.message);
} else {
console.error('An unknown error occurred');
}
}
Function Type Definitions
type MessageHandler = (message: ChatMessage) => void;
type AsyncMessageHandler = (message: ChatMessage) => Promise<void>;
type MessageTransformer = (message: string) => string;
type Comparator<T> = (a: T, b: T) => number;
type Predicate<T> = (value: T) => boolean;
type Mapper<T, U> = (value: T) => U;
Utility Type Usage
type MessagePreview = Pick<ChatMessage, 'role' | 'content'>;
type MessageWithoutTimestamp = Omit<ChatMessage, 'timestamp'>;
type PartialConfig = Partial<ChatConfig>;
type RequiredConfig = Required<ChatConfig>;
type ImmutableMessage = Readonly<ChatMessage>;
type MessageMap = Record<string, ChatMessage>;
type UserOrAssistant = Extract<MessageRole, 'user' | 'assistant'>;
type NonSystemRole = Exclude<MessageRole, 'system'>;
Template Literal Types
type EventName = `on${Capitalize<'message' | 'error' | 'complete'>}`;
type Variant = 'primary' | 'secondary' | 'danger';
type Size = 'sm' | 'md' | 'lg';
type ButtonClass = `ai-button-${Variant}-${Size}`;
JSDoc Comments for Public APIs
@Component({
selector: 'ai-message-bubble',
})
export class MessageBubbleComponent {
message = input.required<ChatMessage>();
showAvatar = input(false);
copy = output<string>();
}
Avoid Common Pitfalls
function processData(data: any) {
return data.value;
}
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');
}
const element = document.querySelector('.chat')!;
const element = document.querySelector('.chat');
if (element) {
}
const message = data as ChatMessage;
if (isChatMessage(data)) {
const message = data;
}