TypeScript Primer
What is TypeScript?
TypeScript is a strongly typed programming language that builds on JavaScript by adding static type definitions. In the v4-monorepo, TypeScript is the primary language for most services and libraries, providing improved developer experience, better code quality, and enhanced maintainability.
Key Concepts
Type System
TypeScript's core feature is its type system:
- Static Typing: Types are checked at compile-time, not runtime
- Type Inference: TypeScript can infer types without explicit annotations
- Type Declarations: Types can be explicitly declared for variables, functions, etc.
- Interfaces and Types: Define structures for objects, parameters, and return values
- Generics: Create reusable components that work with different types
Configuration
TypeScript's behavior is configured via tsconfig.json
files, which define:
- Target JavaScript version
- Module system (ESM, CommonJS)
- Type checking strictness
- File inclusions/exclusions
- Path aliases
TypeScript and ESM
The v4-monorepo uses TypeScript with ECMAScript Modules (ESM):
"type": "module"
in package.jsonimport
/export
syntax for modules- Path extensions required in imports
How TypeScript is Used in This Repository
Project Structure
In the v4-monorepo:
-
Configuration:
-
Root
tsconfig.base.json
with shared settings - Project-specific tsconfig files that extend the base
-
Strict type checking enabled
-
File Organization:
-
Source files in
src/
directories - Type definitions in
types/
directories or alongside code -
Tests in
tests/
directories -
Module System:
- ECMAScript Modules (ESM) throughout
- Full path imports with
.js
extensions (for ESM compatibility) - Path aliases for cleaner imports
TypeScript Standards
The repository enforces:
-
Strictness:
-
strict: true
in tsconfig - No implicit
any
types -
No unnecessary type assertions
-
Code Quality:
-
Type-safe API boundaries
- Proper error handling
-
Zod for runtime validation
-
Type Definitions:
- Explicit interface definitions
- Shared types in library packages
- Minimal use of
any
TypeScript Configuration
Base Configuration
The root tsconfig.base.json
provides shared settings:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"paths": {
// Path aliases for project imports
}
}
}
Project Configuration
Each project extends the base configuration:
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Special Configurations
- App tsconfig: For application code (
tsconfig.app.json
) - Test tsconfig: For test code (
tsconfig.spec.json
) - Build tsconfig: For optimized builds (
tsconfig.build.json
)
Common TypeScript Patterns
Type-Safe API Boundaries
API definitions use explicit interfaces:
interface UserRequest {
username: string;
email: string;
}
interface UserResponse {
id: string;
username: string;
email: string;
createdAt: Date;
}
async function createUser(req: UserRequest): Promise<UserResponse> {
// Implementation...
}
Zod for Runtime Validation
Zod is used to validate inputs:
import { z } from 'zod/v4';
const UserSchema = z.object({
username: z.string().min(3).max(50),
email: z.string().email(),
});
// Type is inferred from the schema
type User = z.infer<typeof UserSchema>;
function validateUser(data: unknown): User {
return UserSchema.parse(data);
}
Utility Types
The codebase uses TypeScript utility types for common operations:
// Extract a subset of properties
type UserPublicInfo = Pick<User, 'id' | 'username'>;
// Make properties optional
type PartialUser = Partial<User>;
// Make properties required
type RequiredUser = Required<User>;
// Exclude specific types
type NonNullableUser = NonNullable<User | null | undefined>;
ES-Toolkit Integration
The repository uses ES-Toolkit as the primary utility library for JavaScript/TypeScript operations. ES-Toolkit provides high-performance, TypeScript-first utilities with excellent tree-shaking support:
import { cloneDeep, debounce, isEqual, omit, pick, throttle } from 'es-toolkit';
// Debounce expensive operations
const debouncedSearch = debounce((query: string) => {
return searchAPI(query);
}, 300);
// Deep equality checks with type safety
const hasChanged = !isEqual(previousState, currentState);
// Object manipulation with type preservation
const publicUser = pick(user, ['id', 'username', 'email']);
const sanitizedUser = omit(user, ['password', 'internalId']);
// Deep cloning for immutable updates
const updatedState = cloneDeep(currentState);
Key Benefits:
- Performance: 2-3x faster than Lodash
- Bundle Size: ~97% smaller than Lodash when tree-shaken
- TypeScript: Native TypeScript with excellent type inference
- Modern: Uses modern JavaScript features and ESM modules
See ADR-20250606: Migration from Lodash to ES-Toolkit for more details.
TypeScript Tools and Utilities
Type Checking
Run type checking for a project:
Check all TypeScript projects:
Type Strictness
Check TypeScript strictness issues:
Fix TypeScript strictness issues:
Excess Property Checks
Check for excess properties:
Fix excess property issues:
Unused Code Detection
Find unused variables and imports:
Remove unused code:
Troubleshooting TypeScript
Common Issues
Issue | Solution |
---|---|
"Cannot find module" | Check import path, add .js extension for ESM |
"Type errors" | Fix type issues or add appropriate type annotations |
"No matching overload" | Check function parameter types and constraints |
"Property does not exist" | Fix property access or add appropriate type definitions |
"TypeScript version mismatch" | Ensure consistent TypeScript version across packages |
Type Assertions
When TypeScript can't infer a type correctly, use type assertions:
// Type assertion (use sparingly)
const user = jsonData as User;
// Type assertion with unknown intermediate
const user = (jsonData as unknown) as User;