TypeScript Rules for Cursor Editor

3 Rules
Programming Language
.mdc Format

About These Rules

These TypeScript coding rules are specifically formatted for the Cursor Editor as .mdc files, focusing on type safety, modern TypeScript patterns, and best practices. Each rule is designed to help you write more maintainable TypeScript code while leveraging Cursor's AI capabilities for enhanced development.

1. Type Safety and Modern TypeScript

Implement comprehensive type safety and modern TypeScript patterns for better code reliability and maintainability. This approach ensures better type checking and takes advantage of TypeScript's advanced type system.

typescript-type-safety.mdc
// Type definitions
type UserRole = 'admin' | 'user' | 'guest';

interface UserPreferences {
    theme: 'light' | 'dark';
    notifications: boolean;
}

interface User {
    id: string;
    name: string;
    email: string;
    role: UserRole;
    createdAt: Date;
    preferences?: UserPreferences;
}

// Generic type utility
type ApiResponse = {
    data: T;
    status: number;
    message: string;
};

// Type-safe service
class UserService {
    private apiUrl: string;

    constructor(apiUrl: string) {
        this.apiUrl = apiUrl;
    }

    async getUser(userId: string): Promise> {
        try {
            // Simulated API call
            const userData: User = {
                id: userId,
                name: 'John Doe',
                email: '[email protected]',
                role: 'user',
                createdAt: new Date(),
                preferences: {
                    theme: 'dark',
                    notifications: true
                }
            };

            return {
                data: userData,
                status: 200,
                message: 'User retrieved successfully'
            };
        } catch (error) {
            throw new Error(`Failed to fetch user: ${error instanceof Error ? error.message : String(error)}`);
        }
    }

    async updateUser(
        userId: string,
        updates: Partial
    ): Promise> {
        try {
            // Simulated API call
            const userData: User = {
                id: userId,
                name: updates.name ?? 'John Doe',
                email: updates.email ?? '[email protected]',
                role: updates.role ?? 'user',
                createdAt: new Date(),
                preferences: updates.preferences ?? {
                    theme: 'dark',
                    notifications: true
                }
            };

            return {
                data: userData,
                status: 200,
                message: 'User updated successfully'
            };
        } catch (error) {
            throw new Error(`Failed to update user: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
}

// Example usage
async function main() {
    const userService = new UserService('https://api.example.com');

    try {
        // Get user
        const response = await userService.getUser('123');
        console.log('User:', response.data);

        // Update user
        const updates = {
            name: 'Jane Doe',
            role: 'admin' as UserRole
        };
        const updateResponse = await userService.updateUser('123', updates);
        console.log('Updated user:', updateResponse.data);
    } catch (error) {
        console.error('Error:', error instanceof Error ? error.message : String(error));
    }
}

// Run the example
main().catch(console.error);

Why This Rule Matters

  • Enables better type safety and error prevention
  • Improves code reliability and maintainability
  • Facilitates better IDE support and autocompletion
  • Reduces runtime errors
  • Enables better code documentation

Using with Cursor

Cursor's AI capabilities can help you:

  • Suggest type definitions
  • Generate interfaces
  • Implement generic types
  • Improve type safety
  • Fix type errors

2. Async Patterns and Error Handling

Implement modern async patterns and robust error handling for better performance and maintainability. This approach helps Cursor's AI better understand async contexts and provide more accurate debugging assistance.

typescript-async-patterns.mdc
// Custom error types
class AppError extends Error {
    constructor(
        message: string,
        public code: string,
        public details?: Record
    ) {
        super(message);
        this.name = 'AppError';
    }
}

class ValidationError extends AppError {
    constructor(message: string, details?: Record) {
        super(message, 'VALIDATION_ERROR', details);
        this.name = 'ValidationError';
    }
}

class APIError extends AppError {
    constructor(message: string, code: string, details?: Record) {
        super(message, code, details);
        this.name = 'APIError';
    }
}

// Async retry decorator
function asyncRetry(
    maxAttempts: number = 3,
    delay: number = 1000,
    backoff: number = 2,
    exceptions: Array Error> = [Error]
) {
    return function (
        target: any,
        propertyKey: string,
        descriptor: PropertyDescriptor
    ) {
        const originalMethod = descriptor.value;

        descriptor.value = async function (...args: any[]): Promise {
            let lastError: Error | null = null;

            for (let attempt = 0; attempt < maxAttempts; attempt++) {
                try {
                    return await originalMethod.apply(this, args);
                } catch (error) {
                    lastError = error instanceof Error ? error : new Error(String(error));
                    
                    if (!exceptions.some(ex => error instanceof ex) || attempt === maxAttempts - 1) {
                        break;
                    }

                    await new Promise(resolve => 
                        setTimeout(resolve, delay * Math.pow(backoff, attempt))
                    );
                }
            }

            throw lastError;
        };

        return descriptor;
    };
}

// Async resource manager
class AsyncResource {
    private logger: Console;

    constructor(private name: string) {
        this.logger = console;
    }

    async acquire(): Promise {
        this.logger.info(`Acquiring ${this.name}`);
        await new Promise(resolve => setTimeout(resolve, 100)); // Simulate resource acquisition
    }

    async release(): Promise {
        this.logger.info(`Releasing ${this.name}`);
        await new Promise(resolve => setTimeout(resolve, 100)); // Simulate resource release
    }
}

// Example usage
class UserService {
    private dbUrl: string;
    private logger: Console;

    constructor(dbUrl: string) {
        this.dbUrl = dbUrl;
        this.logger = console;
    }

    @asyncRetry()
    async getUser(userId: string): Promise> {
        const resource = new AsyncResource('database');
        await resource.acquire();

        try {
            // Simulated database query
            await new Promise(resolve => setTimeout(resolve, 100));

            if (!userId) {
                throw new ValidationError('User ID is required', { userId });
            }

            return {
                id: userId,
                name: 'John Doe',
                email: '[email protected]'
            };
        } catch (error) {
            this.logger.error(`Failed to get user: ${error instanceof Error ? error.message : String(error)}`);
            throw new APIError(
                'Failed to get user',
                'DATABASE_ERROR',
                { userId, error: error instanceof Error ? error.message : String(error) }
            );
        } finally {
            await resource.release();
        }
    }

    @asyncRetry()
    async updateUser(
        userId: string,
        updates: Record
    ): Promise> {
        const resource = new AsyncResource('database');
        await resource.acquire();

        try {
            // Simulated database update
            await new Promise(resolve => setTimeout(resolve, 100));

            if (!userId) {
                throw new ValidationError('User ID is required', { userId });
            }

            return {
                id: userId,
                ...updates,
                updatedAt: new Date()
            };
        } catch (error) {
            this.logger.error(`Failed to update user: ${error instanceof Error ? error.message : String(error)}`);
            throw new APIError(
                'Failed to update user',
                'DATABASE_ERROR',
                { userId, updates, error: error instanceof Error ? error.message : String(error) }
            );
        } finally {
            await resource.release();
        }
    }
}

// Example usage
async function main() {
    const userService = new UserService('postgresql://localhost:5432/mydb');

    try {
        // Get user
        const user = await userService.getUser('123');
        console.log('User:', user);

        // Update user
        const updates = {
            name: 'Jane Doe',
            email: '[email protected]'
        };
        const updatedUser = await userService.updateUser('123', updates);
        console.log('Updated user:', updatedUser);
    } catch (error) {
        if (error instanceof AppError) {
            console.error(`Application error: ${error.code} - ${error.message}`);
            if (error.details) {
                console.error('Details:', error.details);
            }
        } else {
            console.error('Unexpected error:', error instanceof Error ? error.message : String(error));
        }
    }
}

// Run the example
main().catch(console.error);

Why This Rule Matters

  • Enables better async operation handling
  • Improves error handling and recovery
  • Facilitates better performance
  • Reduces code complexity
  • Enables better debugging

Using with Cursor

Cursor's AI capabilities can help you:

  • Suggest async patterns
  • Generate error handling code
  • Implement retry mechanisms
  • Optimize async operations
  • Debug async issues

3. Module Organization and Testing

Implement proper module organization and testing patterns for better code maintainability and reliability. This approach helps Cursor's AI better understand code structure and provide more accurate suggestions.

typescript-module-organization.mdc
// Project structure
/*
my-project/
├── src/
│   ├── config/
│   │   ├── index.ts
│   │   └── types.ts
│   ├── models/
│   │   ├── index.ts
│   │   └── user.ts
│   ├── services/
│   │   ├── index.ts
│   │   └── user-service.ts
│   └── utils/
│       ├── index.ts
│       └── validation.ts
├── tests/
│   ├── setup.ts
│   └── user-service.test.ts
└── package.json
*/

// config/types.ts
export interface Config {
    databaseUrl: string;
    apiKey: string;
    environment: 'development' | 'production';
    logLevel: 'debug' | 'info' | 'warn' | 'error';
}

// config/index.ts
import { Config } from './types';

export function createConfig(): Config {
    return {
        databaseUrl: process.env.DATABASE_URL ?? 'postgresql://localhost:5432/mydb',
        apiKey: process.env.API_KEY ?? 'your-api-key',
        environment: (process.env.NODE_ENV as 'development' | 'production') ?? 'development',
        logLevel: 'info'
    };
}

// models/user.ts
export interface User {
    id: string;
    name: string;
    email: string;
    createdAt: Date;
}

export function createUser(data: Partial): User {
    return {
        id: data.id ?? '',
        name: data.name ?? '',
        email: data.email ?? '',
        createdAt: data.createdAt ?? new Date()
    };
}

// services/user-service.ts
import { Config } from '../config/types';
import { User, createUser } from '../models/user';
import { validateEmail } from '../utils/validation';

export class UserService {
    constructor(private config: Config) {}

    async getUser(userId: string): Promise {
        // Implementation
        return createUser({ id: userId });
    }

    async updateUser(userId: string, updates: Partial): Promise {
        // Implementation
        return createUser({ id: userId, ...updates });
    }
}

// utils/validation.ts
export function validateEmail(email: string): boolean {
    const pattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
    return pattern.test(email);
}

export function validateUserData(data: Partial): void {
    if (data.email && !validateEmail(data.email)) {
        throw new Error('Invalid email format');
    }
}

// tests/setup.ts
import { Config } from '../src/config/types';

export function createTestConfig(): Config {
    return {
        databaseUrl: 'postgresql://localhost:5432/test',
        apiKey: 'test-api-key',
        environment: 'development',
        logLevel: 'debug'
    };
}

// tests/user-service.test.ts
import { UserService } from '../src/services/user-service';
import { createTestConfig } from './setup';
import { User } from '../src/models/user';

describe('UserService', () => {
    let userService: UserService;

    beforeEach(() => {
        userService = new UserService(createTestConfig());
    });

    it('should get user', async () => {
        // Arrange
        const userId = '123';
        const expectedUser: User = {
            id: userId,
            name: 'John Doe',
            email: '[email protected]',
            createdAt: new Date()
        };

        // Act
        const result = await userService.getUser(userId);

        // Assert
        expect(result).toEqual(expectedUser);
    });

    it('should update user', async () => {
        // Arrange
        const userId = '123';
        const updates = {
            name: 'Jane Doe',
            email: '[email protected]'
        };
        const expectedUser: User = {
            id: userId,
            ...updates,
            createdAt: new Date()
        };

        // Act
        const result = await userService.updateUser(userId, updates);

        // Assert
        expect(result).toEqual(expectedUser);
    });
});

// Example usage
async function main() {
    const config = createConfig();
    const userService = new UserService(config);

    try {
        // Get user
        const user = await userService.getUser('123');
        console.log('User:', user);

        // Update user
        const updates = {
            name: 'Jane Doe',
            email: '[email protected]'
        };
        const updatedUser = await userService.updateUser('123', updates);
        console.log('Updated user:', updatedUser);
    } catch (error) {
        console.error('Error:', error instanceof Error ? error.message : String(error));
    }
}

// Run the example
main().catch(console.error);

Why This Rule Matters

  • Improves code organization
  • Enables better testing
  • Facilitates easier maintenance
  • Improves code reusability
  • Enables better dependency management

Using with Cursor

Cursor's AI capabilities can help you:

  • Suggest module organization patterns
  • Generate test cases
  • Optimize module structure
  • Improve code organization
  • Generate module documentation