Instruction file imported from sinhong2011/openpandata-web (
.cursor/rules/typescript-safety.mdc). Copyright stays with the author.
OpenPandata Web - TypeScript Type Safety Rules
Project Overview
This is a Next.js 15 project with TypeScript. These rules ensure strict type safety and eliminate the use of any types throughout the codebase.
TypeScript Type Safety Rules
MANDATORY: Never Use any Type
- NEVER use the
anytype in any part of the codebase - ALWAYS use proper TypeScript types for better type safety
- ALL function parameters, return types, and variables must be properly typed
- ALL external data and API responses must be validated with proper types
Type Hierarchy for Unknown Data
Use unknown Instead of any
// ✅ CORRECT: Using unknown for uncertain data
function processData(data: unknown): string {
if (typeof data === 'string') {
return data
}
return 'Invalid data'
}
// ❌ FORBIDDEN: Using any
function processData(data: any): string {
return data.toString()
}
Proper Error Type Handling
// ✅ CORRECT: Define error interfaces
export interface ErrorLike {
message?: string
name?: string
stack?: string
status?: number
[key: string]: unknown
}
export interface ValidationError extends ErrorLike {
validationErrors?: Record<string, string[]>
formDataKeys?: string[]
attemptedEmail?: string
}
export interface SupabaseError extends ErrorLike {
status?: number
name?: string
}
// ✅ CORRECT: Type-safe error handling
function handleError(error: ErrorLike | string | unknown): void {
const errorObj = typeof error === 'string'
? { message: error, name: 'StringError' }
: error as ErrorLike
console.error(errorObj?.message || 'Unknown error')
}
// ❌ FORBIDDEN: Using any for errors
function handleError(error: any): void {
console.error(error.message)
}
Function Parameter Types
Strict Parameter Typing
// ✅ CORRECT: Properly typed parameters
export async function logError(
actionType: string,
context: string,
error: ErrorLike | string | unknown,
additionalData?: Record<string, unknown>
): Promise<void> {
// Implementation
}
// ✅ CORRECT: Generic functions with constraints
export async function measureAndLog<T>(
actionType: string,
operation: string,
fn: () => Promise<T>
): Promise<T> {
// Implementation
}
// ❌ FORBIDDEN: Using any for parameters
export async function logError(
actionType: string,
context: string,
error: any,
additionalData?: any
): Promise<void> {
// Implementation
}
Object and Record Types
// ✅ CORRECT: Specific object interfaces
export interface LogContext {
timestamp: string
context: string
error?: {
message: string
name: string
stack: string
[key: string]: unknown
}
request: {
userAgent: string
ip: string
forwardedFor: string
realIp: string
}
[key: string]: unknown
}
// ✅ CORRECT: Record types for dynamic objects
type UserPreferences = Record<string, unknown>
type FormData = Record<string, string | number | boolean>
// ❌ FORBIDDEN: Using any for objects
type UserData = {
[key: string]: any
}
API Response and External Data Handling
Type Guards and Validation
// ✅ CORRECT: Type guards for runtime validation
function isErrorLike(value: unknown): value is ErrorLike {
return (
typeof value === 'object' &&
value !== null &&
('message' in value || 'name' in value || 'stack' in value)
)
}
function isValidUser(data: unknown): data is { id: string; email: string } {
return (
typeof data === 'object' &&
data !== null &&
'id' in data &&
'email' in data &&
typeof (data as { id: unknown }).id === 'string' &&
typeof (data as { email: unknown }).email === 'string'
)
}
// ✅ CORRECT: Safe API response handling
async function fetchUserData(id: string): Promise<{ id: string; email: string } | null> {
try {
const response = await fetch(`/api/users/${id}`)
const data: unknown = await response.json()
if (isValidUser(data)) {
return data
}
return null
} catch (error) {
console.error('Failed to fetch user:', error)
return null
}
}
// ❌ FORBIDDEN: Assuming API response types
async function fetchUserData(id: string): Promise<any> {
const response = await fetch(`/api/users/${id}`)
return response.json() // No validation
}
Event Handlers and Callbacks
Proper Event Typing
// ✅ CORRECT: Properly typed event handlers
function handleSubmit(event: React.FormEvent<HTMLFormElement>): void {
event.preventDefault()
// Handle form submission
}
function handleClick(event: React.MouseEvent<HTMLButtonElement>): void {
// Handle button click
}
function handleChange(event: React.ChangeEvent<HTMLInputElement>): void {
// Handle input change
}
// ✅ CORRECT: Custom callback types
type SuccessCallback = (data: { message: string; id: string }) => void
type ErrorCallback = (error: ErrorLike) => void
function performAction(
onSuccess: SuccessCallback,
onError: ErrorCallback
): void {
// Implementation
}
// ❌ FORBIDDEN: Using any for events or callbacks
function handleSubmit(event: any): void {
event.preventDefault()
}
function performAction(onSuccess: any, onError: any): void {
// Implementation
}
Third-Party Library Integration
Proper Library Type Handling
// ✅ CORRECT: Type external library responses
import { z } from 'zod'
const UserSchema = z.object({
id: z.string(),
email: z.string().email(),
name: z.string().optional()
})
type User = z.infer<typeof UserSchema>
function validateUser(data: unknown): User | null {
try {
return UserSchema.parse(data)
} catch {
return null
}
}
// ✅ CORRECT: Type Supabase responses
import { PostgrestError } from '@supabase/supabase-js'
interface SupabaseResponse<T> {
data: T | null
error: PostgrestError | null
}
async function fetchUsers(): Promise<User[]> {
const { data, error }: SupabaseResponse<User[]> = await supabase
.from('users')
.select('*')
if (error) {
throw new Error(error.message)
}
return data || []
}
// ❌ FORBIDDEN: Assuming library types
async function fetchUsers(): Promise<any> {
const response = await supabase.from('users').select('*')
return response.data
}
Utility Functions and Helpers
Type-Safe Utility Functions
// ✅ CORRECT: Generic utility with constraints
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
const result = {} as Pick<T, K>
keys.forEach(key => {
result[key] = obj[key]
})
return result
}
// ✅ CORRECT: Type-safe object manipulation
function omit<T, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
const result = { ...obj }
keys.forEach(key => {
delete result[key]
})
return result
}
// ✅ CORRECT: Safe type conversion
function toNumber(value: unknown): number | null {
if (typeof value === 'number') return value
if (typeof value === 'string') {
const num = Number(value)
return isNaN(num) ? null : num
}
return null
}
// ❌ FORBIDDEN: Using any in utilities
function pick(obj: any, keys: string[]): any {
const result: any = {}
keys.forEach(key => {
result[key] = obj[key]
})
return result
}
Configuration and Environment Variables
Type-Safe Environment Handling
// ✅ CORRECT: Environment variable validation
const envSchema = z.object({
DATABASE_URL: z.string().url(),
NEXTAUTH_SECRET: z.string().min(1),
NEXTAUTH_URL: z.string().url(),
NODE_ENV: z.enum(['development', 'production', 'test'])
})
export const env = envSchema.parse(process.env)
// ✅ CORRECT: Configuration with proper types
interface AppConfig {
database: {
url: string
maxConnections: number
}
auth: {
secret: string
sessionTimeout: number
}
}
const config: AppConfig = {
database: {
url: env.DATABASE_URL,
maxConnections: 10
},
auth: {
secret: env.NEXTAUTH_SECRET,
sessionTimeout: 3600
}
}
// ❌ FORBIDDEN: Untyped configuration
const config: any = {
database: process.env.DATABASE_URL,
auth: process.env.NEXTAUTH_SECRET
}
FORBIDDEN Practices
- ❌ Using
anytype anywhere in the codebase - ❌ Type assertions without proper validation:
data as SomeType - ❌ Ignoring TypeScript errors with
@ts-ignore - ❌ Using
Objector{}types for complex objects - ❌ Untyped function parameters or return values
- ❌ Assuming external API response structures
- ❌ Using
anyfor event handlers or callbacks - ❌ Unvalidated environment variables or configuration
REQUIRED Practices
- ✅ Always use
unknowninstead ofanyfor uncertain data - ✅ Define proper interfaces for all data structures
- ✅ Use type guards for runtime validation
- ✅ Implement proper error type hierarchies
- ✅ Use generic types with appropriate constraints
- ✅ Validate external data with schemas (Zod, etc.)
- ✅ Type all function parameters and return values
- ✅ Use proper event types for React handlers
- ✅ Implement type-safe utility functions
- ✅ Validate environment variables and configuration
Type Safety Checklist
- No
anytypes used anywhere - All function parameters are properly typed
- All return types are explicitly defined
- External data is validated with type guards
- Error handling uses proper error interfaces
- Event handlers use correct React event types
- API responses are validated before use
- Environment variables are type-checked
- Generic functions have appropriate constraints
- Object types use specific interfaces, not
anyor{}
Code Review Guidelines
When reviewing code, ensure:
- No
anytypes: Search for and eliminate allanyusage - Proper error handling: Errors should use defined interfaces
- Type validation: External data must be validated
- Generic constraints: Generic types should have meaningful constraints
- Interface definitions: Complex objects should have proper interfaces
- Type guards: Runtime type checking for uncertain data
- Event typing: React events should use proper types
- API safety: External API calls should validate responses
Remember: If TypeScript can't infer the type safely, define it explicitly. If the type is truly unknown, use unknown and validate it at runtime.